Introduction
Blind SQL Injection (Blind SQLi) is a more advanced and subtle type of SQL injection vulnerability where an attacker cannot directly view the results of their injected query. Unlike traditional SQL injections, where an attacker can see error messages or query results returned in the HTTP response, Blind SQL Injection does not reveal any information from the database directly. Instead, attackers must rely on the application’s behavior, such as response time or page content changes, to infer the outcome of their queries.
This technique is often used when an application fails to properly handle SQL queries, leaving it vulnerable to attackers who can manipulate the SQL statements. The lack of direct feedback makes Blind SQL Injection more challenging to detect and exploit, but the potential consequences—such as unauthorized access to sensitive data—are just as severe.
Blind SQL Injection is generally categorized into two types:
Boolean-based Blind SQL Injection: This approach involves asking the database true or false questions and observing the application’s response to determine if the condition is true or false.
Time-based Blind SQL Injection: In this case, the attacker injects SQL queries that introduce a time delay in the database’s response. By measuring the response time, the attacker can deduce whether the condition in the query is true or false.
Both techniques allow attackers to extract sensitive information from the database without directly seeing the data returned by the queries. In this post, we’ll explore the mechanics of Blind SQL Injection, how to identify vulnerabilities, and methods to mitigate these risks.
Boolean-Based Blind Injecion
This is a type of SQL injection where the attacker sends SQL queries to the database to force the application to return different results based on whether the query returns a true or a false result. This technique does not really reveal any data from the data but it allows the attacker to infer information based on the response. This method is very useful when the application shows generic error messages or no messages at all. This can be used to enumerate the database by asking a series of true or false questions, which eventually can be used to extract information.
An example URL would be http://example.com/blind.php?id=1
this sends the query SELECT * FROM users WHERE id = 1
. The attacker may then try and inject a query that returns false http://example.com/blind.php?id=1 AND 1=2
. Where the SQL query looks like SELECT * FROM users WHERE id = 1 AND 1=2
To better understand how blind SQL injection vulnerabilities can occur in real world scenarios, let us walk through a simple example of insecure code. The following snippet represents a common pattern found in many web applications where user input is accepted and embedded without any proper validation or sanitization. While the functionality may seem harmless on the surface, it opens the door to serious vulnerabilities where the input is not handled securely. In this particular case the application does not return database errors or visible query results. However sensitive data can be extracted.
|
|
The script takes an id
parameter from the URL ?id=1
and stores it in the $user_id
variable, if there is no ID provided it defaults to 1.
We can use Docker so that we can see this examples working locally
|
|
Then we can add a script that will link the database to the web app and do all the necessary docker configurations, including also creating a network
|
|
We can also create a SQL dump as dummy data we can use in the web application
|
|
We can now visit our local web application on http://localhost:8080/blind.php
So now we can test if our query works
The query here would be
|
|
Now we can try and inject a boolean expression that will always return true 1=1
|
|
This returns with User exists
which in other words means it is true, then that confirms to us that the web application is injectable. Now is where the fun begins.
For example we can get the total characters in the database.
This query tries to get the length of characters of the database, and we equal it to 1. We get back a User not found
response which means that it is false.
|
|
If we get the number correct then the application will return a User exists
like so,
We could go ahead as well and find the name of the database as a way of enumeration.
|
|
This queries the first character of the database name, and this checks if the first character is a
if yes, then it should return User exists
and this would hint to us that the first character of the database name is a
.
Now we could create an automation script with python that will help us make this enumeration faster
|
|
Let us run the script
The script goes through all the characters provided and prints each character that returned a User exists
. The logs would look like so
We can also enumerate for usernames using an automated script in python as well where all the query is:
|
|
For the python script it looks kind of similar to the one we worked with previously, but now with a different payload.
|
|
This automated script helps us find two users, admin
and janedoe
Time-Based Blind Injection
There are times where the application returns absolutely no clues at all. Here attackers can resort to time-based attacks. Here they would still resort to using a series of questions to extract information from the database server, the difference this time is that the injected code will cause the server to delay in its response if the response is true. In the same way an immediate response is interpreted as a no. For example.
|
|
If this is true then the server will delay for 10 seconds before replying back with User exists
. Similar payloads to Boolean based injections can also be utilized with Time-based injections in order to enumerate the database for information.
Mitigating injection risks
- Use Prepared Statements (Parameterized Queries) This is the most effective method to prevent SQL injection. Prepared statements ensure that user input is treated strictly as data, not as part of the SQL syntax.
PHP with PostgreSQL Example:
|
|
- Validate and Sanitize User Input Never trust user input. Always validate and sanitize it, even when using prepared statements.
Example for numeric input:
|
|
Use whitelisting wherever possible, allowing only expected formats or characters.
- Avoid Revealing Logical Clues or Error Messages In blind SQL injection attacks, attackers rely on feedback from the application. Avoid returning different responses for true/false conditions.
Instead of this:
|
|
Use a neutral response:
|
|
Also, avoid exposing raw database errors like:
|
|
Instead, log errors internally and return a generic message.
- Disable Detailed Error Output in Production Exposing detailed errors can help attackers understand your database structure.
In PHP: set display_errors = Off Log errors to a secure file Show only a user-friendly message
|
|
- Apply the Principle of Least Privilege Ensure the database user your application connects with has only the permissions it absolutely needs.
Avoid using admin or superuser accounts for regular queries
Deny DROP
, DELETE
, ALTER
, or other high-risk permissions unless necessary
This limits the potential damage if an injection succeeds.
- Use a Web Application Firewall (WAF) A WAF can help detect and block common SQL injection patterns, such as:
Use of UNION, OR 1=1, or pg_sleep Repeated or automated requests (e.g., from sqlmap) While not a replacement for secure coding, a WAF adds an extra layer of protection and visibility.
- Log and Monitor for Suspicious Activity Regularly review logs to detect unusual patterns or probing behavior.
Look for:
Sequential requests with modified query parameters (id=1, id=1 AND 1=1, etc.) SQL keywords appearing in URLs or GET parameters Requests with automation tool signatures (sqlmap, python-requests) Integrating alerts into a monitoring system helps catch attacks early.