Detecting SQL Injection: What Your Security Scanner Should Check in 2026
📢 Affiliate Disclosure: This site contains affiliate links to Amazon. We earn a commission when you purchase through our links at no additional cost to you.
OWASP A03 — Injection — has been in the Top 10 since the list was created, and SQL injection is still the most common way it shows up. In 2026, SQLi accounts for more confirmed web application breaches than any other single vulnerability class. The reason is simple: developers keep building queries by concatenating strings, and scanners keep missing the injectable parameters because they only test the obvious ones.
This article covers what a proper SQL injection scan should detect in 2026: error-based detection (when the database talks back), boolean-based detection (when it doesn't), the specific payloads that matter, and how to fix every pattern we find. We'll reference SecurityClaw's new sqli-probe skill as a concrete implementation — it was built to close the A03 gap in automated scanning.
1. Why SQL Injection Still Matters in 2026
SQL injection was first documented in 1998. Twenty-eight years later, it remains the single most exploited injection flaw in web applications. Here's why:
- Legacy code is everywhere. Applications built before parameterized queries became standard practice are still running in production. Many have never been refactored.
- ORM misuse creates new injection points. Developers assume their ORM handles sanitization, then use raw query methods (
.raw(),.execute(),.extra()) that bypass it entirely. - The attack surface has expanded. Classic SQL injection now coexists with NoSQL injection (MongoDB
$where), GraphQL injection, and ORM-layer injection. A03 covers all of them. - Scanners miss it. Many security scanners only test GET parameters with a single quote and call it done. They miss POST body parameters, JSON payloads, HTTP headers, and cookie values — all of which can be injectable.
The impact of successful SQLi hasn't changed either: full database extraction, authentication bypass, data modification, and in some configurations, remote code execution via xp_cmdshell (MSSQL) or LOAD_FILE (MySQL).
2. Error-Based Detection: When the Database Talks Back
Error-based SQL injection is the easiest to detect and the most common finding in automated scans. It works when the application returns database error messages in the HTTP response.
The detection logic is straightforward:
- Inject a payload that breaks the SQL syntax (e.g., a single quote
') - Read the HTTP response body
- Search for database-specific error strings
If the response contains You have an error in your SQL syntax, you've confirmed two things: the parameter is injectable, and the backend is MySQL. That's enough for a HIGH severity finding.
Here's what error-based detection looks like in practice. A vulnerable search endpoint:
GET /search?q=test' HTTP/1.1
Returns:
You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the
right syntax to use near ''' at line 1
The application is concatenating the q parameter directly into a SQL query. The single quote breaks the string literal, and MySQL's error message leaks through to the response. This is a textbook A03 finding.
The key insight for scanner builders: you need error patterns for every major database, not just MySQL. A scanner that only checks for MySQL errors will miss PostgreSQL, MSSQL, and SQLite injection on the same endpoint.
3. Boolean-Based Detection: When Errors Are Suppressed
Production applications often suppress error messages — custom error pages, generic 500 responses, or error logging without display. Error-based detection fails here. Boolean-based detection fills the gap.
The technique:
- Fetch the page with the original parameter value (baseline)
- Inject a tautology payload:
1 OR 1=1(always true) - Inject a contradiction payload:
1 AND 1=2(always false) - Compare response lengths against the baseline
If the true payload returns a response similar to the baseline but the false payload returns a significantly different response (or vice versa), the parameter is injectable. The application is evaluating the injected SQL and changing its behavior based on the result.
SecurityClaw's sqli-probe uses a 50% response length threshold: if the injected response differs from the baseline by more than 50% of the baseline length, it flags a MEDIUM severity boolean-based finding. This threshold balances sensitivity against false positives — minor content variations (timestamps, CSRF tokens) won't trigger it, but a query that returns 0 rows instead of 100 will.
Boolean-based findings are rated MEDIUM rather than HIGH because the detection is inferential — you're measuring a side effect rather than reading a confirmed error. Manual verification is recommended before reporting to the application owner.
4. The Payloads That Matter
Not all SQLi payloads are equal. A good scanner tests a focused set that covers the major injection patterns without generating excessive traffic. Here are the payloads that matter and why:
| Payload | What It Tests | Why It Matters |
|---|---|---|
' |
String literal break | The most basic test — if a single quote causes a SQL error, the parameter is injectable. Works on every database. |
'' |
Escape handling | Two single quotes is a valid SQL escape. If ' errors but '' doesn't, it confirms the input reaches the SQL parser. |
1 OR 1=1 |
Tautology (numeric context) | Bypasses WHERE id = [input] clauses. Returns all rows instead of one. Classic authentication bypass. |
1' OR '1'='1 |
Tautology (string context) | Same as above but for string-typed parameters: WHERE name = '[input]'. The quotes balance the existing query structure. |
1; DROP TABLE-- |
Statement stacking | Tests whether the database allows multiple statements. If it does, an attacker can execute arbitrary SQL after the semicolon. The -- comments out the rest of the original query. |
1 UNION SELECT NULL-- |
UNION-based extraction | Tests column enumeration. If the UNION succeeds, the attacker can extract data from other tables by matching column counts. |
" OR ""=" |
Double-quote delimiter | Some databases and applications use double quotes for string literals. This catches injection points that single-quote payloads miss. |
These 7 payloads cover the major injection patterns: syntax breaking, escape testing, tautology bypass, statement stacking, UNION extraction, and alternate delimiters. A scanner that tests all 7 against every parameter will catch the vast majority of injectable endpoints.
5. Database Fingerprinting via Error Signatures
When error-based detection succeeds, the error message itself reveals which database is running. This is valuable intelligence for both the scanner (to refine follow-up payloads) and the security team (to assess impact).
Here are the error signatures that identify each major database:
| Database | Error Signatures |
|---|---|
| MySQL | You have an error in your SQL syntax, mysql_fetch, Warning.*mysql_ |
| PostgreSQL | pg_query(), unterminated quoted string, ERROR: syntax error at or near |
| MSSQL | Unclosed quotation mark, Microsoft OLE DB Provider for SQL Server, [Microsoft][ODBC SQL Server Driver] |
| SQLite | SQLite3::query(), near "...": syntax error, SQLITE_ERROR |
A scanner should check for all of these patterns, not just one database. Many web applications use different databases for different services — the main app might run PostgreSQL while an internal admin panel uses SQLite. Testing only for MySQL errors would miss both.
SecurityClaw's sqli-probe checks 14 distinct error patterns across all 4 databases plus generic SQL error strings. When a match is found, the finding includes the identified database type, which helps the security team prioritize remediation — a MySQL injection on a customer-facing endpoint is more urgent than a SQLite injection on an internal tool.
6. How to Fix SQL Injection
Every SQLi vulnerability has the same root cause: user input is concatenated into a SQL query string instead of being passed as a parameter. The fix is always the same pattern — parameterized queries — but the implementation varies by language and framework.
The Vulnerable Pattern (Don't Do This)
# Python — VULNERABLE
cursor.execute("SELECT * FROM users WHERE name = '" + username + "'")
# PHP — VULNERABLE
$result = mysqli_query($conn, "SELECT * FROM users WHERE name = '$username'");
# Java — VULNERABLE
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name = '" + username + "'");
The Fixed Pattern (Do This)
# Python — SAFE (parameterized)
cursor.execute("SELECT * FROM users WHERE name = %s", (username,))
# PHP — SAFE (prepared statement)
$stmt = $conn->prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $username);
$stmt->execute();
# Java — SAFE (PreparedStatement)
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
Defense in Depth
Parameterized queries are the primary fix, but defense in depth means layering additional protections:
- Input validation. Whitelist expected input types. If a parameter should be a numeric ID, reject anything that isn't a number before it reaches the query layer.
- ORM with parameterization. Use an ORM (SQLAlchemy, Hibernate, Eloquent) that parameterizes queries by default. But never use the ORM's raw query escape hatch (
.raw(),.extra()) with user input. - Least-privilege database accounts. The application's database user should only have SELECT, INSERT, UPDATE, DELETE on the tables it needs. Never use the root/admin database account. This limits the damage if injection succeeds.
- Suppress error messages. Never display raw database errors to users. Log them server-side and return a generic error page. This doesn't prevent injection, but it prevents error-based data extraction.
- Web Application Firewall (WAF). A WAF can block common SQLi payloads at the network edge. It's not a substitute for fixing the code, but it buys time while remediation is in progress.
7. Scanner Checklist: What Your Tool Should Cover
If you're evaluating a security scanner's SQL injection detection capabilities, here's what to look for:
| Capability | Why It Matters | sqli-probe |
|---|---|---|
| Error-based detection | Catches the most obvious and highest-confidence SQLi findings | ✅ 14 patterns across 4 databases |
| Boolean-based detection | Catches injection when error messages are suppressed | ✅ Response length comparison with 50% threshold |
| Multiple payload types | Different injection contexts require different payloads | ✅ 7 payloads covering syntax break, tautology, UNION, stacking |
| Multi-database signatures | Applications use different databases; scanner must detect all | ✅ MySQL, PostgreSQL, MSSQL, SQLite |
| Database fingerprinting | Knowing the DB type helps prioritize and plan remediation | ✅ Reports DB type with each finding |
| Parameter enumeration | Tests every parameter, not just the first one | ✅ All URL query parameters tested |
| Severity classification | Error-based (confirmed) should be rated higher than boolean-based (inferred) | ✅ HIGH for error-based, MEDIUM for boolean-based |
| Non-destructive | Scanner should detect, not exploit — no actual data extraction or table drops | ✅ Passive detection only (requires_approval: false) |
Bottom Line
SQL injection is a solved problem in theory — parameterized queries have existed for decades. In practice, it remains the most exploited injection flaw because legacy code persists, ORMs get misused, and scanners don't test thoroughly enough.
A proper SQLi scanner needs both error-based and boolean-based detection, multiple payload types covering different injection contexts, error signatures for every major database, and the discipline to test every parameter on every endpoint. SecurityClaw's sqli-probe skill implements exactly this — 7 payloads, 14 error patterns, 4 database signatures, and boolean-based fallback with a calibrated threshold.
If your current scanner only sends a single quote to GET parameters and calls it done, you're missing injectable endpoints. The fix is straightforward: parameterized queries everywhere, input validation as a second layer, least-privilege database accounts, and suppressed error messages. None of this is new. The challenge is doing it consistently across every query in every application.