Safeguard
Vulnerabilities

SQL Injection Detection: How Scanners Actually Find It

SQL injection detected in a scan report can mean very different things depending on whether it came from a static trace or a live dynamic test — here's how each actually works.

Safeguard Research Team
Research
6 min read

SQL injection detected in a scan result means a tool found a path where untrusted input reaches a database query without proper parameterization — either by statically tracing data flow through source code, or by dynamically sending crafted payloads at a running application and observing the response. These are genuinely different detection methods with different strengths, and understanding which one flagged a given finding changes how much you should trust it and what to check first.

How does static analysis detect SQL injection?

A static analysis (SAST) tool parses source code into a representation it can trace data flow through, marks any value originating from user input (a request parameter, a header, a form field) as "tainted," and follows that taint through the codebase to see if it reaches a "sink" — in this case, a database query — without passing through a sanitizing or parameterizing function first. If tainted data reaches db.query("SELECT * FROM users WHERE id = " + userId) without ever being parameterized, that's a SQL injection finding, flagged before the code ever runs. The strength here is coverage — a static scanner checks every code path, including ones rarely exercised in testing — but it can also produce false positives on code paths that are technically vulnerable in isolation but practically unreachable, or where sanitization happens outside the scanner's visibility (a validation layer in a proxy, for instance).

How does a dynamic scanner detect SQL injection differently?

A DAST tool doesn't read source code at all — it interacts with a running application the way an actual attacker would, sending a sql injection test payload (a single quote, a boolean-based condition like ' OR '1'='1, a time-based payload designed to trigger a measurable delay) into every input field, query parameter, and header it can find, then watches how the application actually responds. A returned database error message, a change in response content that reveals a boolean condition flipped, or a response that takes measurably longer when a SLEEP()-style payload is injected are all signals the application processed the injected SQL rather than safely handling it as data. The advantage here is confidence: a dynamic finding proves exploitability against the live application as deployed, not just a theoretical code path.

What does a real SQL injection attack example actually look like?

Our academy walkthroughs use variations of this pattern precisely because it's still the most common real-world example. The classic textbook case is a login form where the query is built as a string: SELECT * FROM users WHERE username = ' plus user input plus ' AND password = ' plus more user input. Submitting a username of admin' -- closes the quote early and comments out the rest of the query, which can authenticate as admin without ever supplying a valid password, since everything after -- is treated as a SQL comment and ignored. Union-based injection is another common pattern, where an attacker appends UNION SELECT to pull data from a completely different table than the query intended, effectively turning a product-search feature into a way to read a users table's password hashes.

Why do static and dynamic detection sometimes disagree on the same code?

Because they're testing different things. Static analysis can flag a code path as vulnerable even if, in the live deployed application, a web application firewall or an upstream input validation layer neutralizes the payload before it ever reaches that code — the vulnerability exists in the code, but exploitation is blocked at another layer. Dynamic testing can miss a vulnerable path entirely if the crawler never discovers or exercises that particular endpoint during the scan. Neither method alone gives complete coverage, which is why mature SAST and DAST programs run both rather than picking one: static analysis for full code coverage including rarely-exercised paths, dynamic testing for confirmed, exploitable, real-world proof against the running system.

How do you actually prevent SQL injection, regardless of how it's detected?

Parameterized queries (prepared statements) are the fix that actually closes the vulnerability class, because they separate the SQL command structure from the data values — the database driver handles escaping correctly regardless of what characters appear in the input, rather than relying on the application to sanitize every possible malicious string itself. ORMs generally parameterize by default, which is a large part of why teams that use one consistently see fewer injection findings than teams building raw SQL strings by hand. Input validation (rejecting obviously malformed input before it reaches a query) is a reasonable second layer, but it's not a substitute for parameterization — validation can be bypassed by encoding tricks or edge cases a static rule didn't anticipate, while parameterization removes the injection vector structurally.

What should you do the moment a scanner says "SQL injection detected"?

Confirm whether the finding came from static or dynamic analysis, since that changes your next step: a static finding needs you to check whether the flagged code path is actually reachable with attacker-controlled input in production, while a dynamic finding has already proven reachability and needs an immediate fix, not a reachability debate. Either way, the fix is almost always the same — replace string-concatenated SQL with a parameterized query or prepared statement — and it's worth checking whether the same unsafe pattern appears anywhere else in the codebase, since a query built via string concatenation in one file is rarely the only instance of that habit in a project.

FAQ

Can SQL injection be fully prevented by input validation alone? No — validation reduces the attack surface but isn't a complete fix, since attackers can find encoding or edge cases a validation rule didn't anticipate. Parameterized queries close the vulnerability structurally rather than relying on catching every malicious pattern.

Do ORMs make an application immune to SQL injection? Mostly, for queries built through the ORM's standard query builder, but most ORMs also offer an escape hatch for raw SQL, and that raw-SQL path reintroduces the exact same risk if user input is concatenated into it directly.

Is a static SQL injection finding always a real vulnerability? Not necessarily — it's a real code-level issue, but whether it's exploitable depends on whether attacker-controlled input can actually reach that code path in the deployed application, which is exactly what dynamic testing is good at confirming.

How fast can a SQL injection be fixed once detected? Often within a single pull request, since the fix is typically swapping a string-concatenated query for a parameterized one — the harder part is usually finding every instance of the pattern across a large, older codebase, not fixing any one of them.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.