Back to glossary

SQL injection

8 min read

In application security, SQL injection is a flaw where input changes the meaning of a query the application builds, instead of being read as data inside it. Parameterised queries fix the common case, but identifiers such as table, column and sort order cannot be parameterised, and dynamic SQL inside stored procedures rebuilds the same problem.

July 24, 2026
Compartir:

How it works

An application builds a query as text and sends it to the database. If any part of that text came from the user and was not separated from the code, the database has no way to tell the difference: it parses the whole string, and whatever grammar it finds it executes. Injection is not the database being tricked. It is the database doing exactly what the text says.

String concatenation is the visible case and not the only one. The same flaw appears in dynamic SQL built inside a stored procedure, where the parameter arrives safely and is then pasted into another statement and executed. It appears in an ORM the moment a raw fragment, a filter expression or a sort field is composed from user input, which is common because ORMs cover the easy queries and developers drop to raw SQL for the hard ones. And it appears second-order: a value is stored correctly through a parameterised insert, then read back later and concatenated into a different query, which is why testing only the field where the data enters misses it.

What an attacker does next depends on the response. If results come back in the page, the query is extended to read other tables. If they do not, the answer is inferred one bit at a time from whether the page changes, or from how long the database takes to reply, or from a DNS or HTTP request the database itself is made to send outbound. That last channel is the one that still works when everything else is silent.

What goes wrong

The failure we see most is not an application with no parameterisation. It is an application that parameterises the ninety per cent it can and hand-builds the rest. Sorting is the classic: an ORDER BY clause takes a column name, a column name is an identifier, and identifiers cannot be bound as parameters. Whoever wrote it knew about placeholders and reached the edge of what placeholders can do, then filled the gap with concatenation and a comment.

Reporting and search screens are the second reliable place. They are built late, they accept filter expressions that map fairly directly onto SQL, and they are usually behind an authenticated role that a scanner never reaches. Export and admin functionality is where we find injection in applications that are otherwise clean.

The reason it survives is that the visible symptom is absent. A blind case produces no error, no stack trace and no changed page. The only difference is timing, and timing does not look like an incident to anyone watching. On an engagement we confirm it with a conditional response and a deliberate delay, and then we stop: proving read access to one row of one table is enough to write the finding. Extracting a customer database to demonstrate impact is not testing, it is the incident.

In-band, blind and out-of-band

The three names describe how the answer gets back to the attacker, not how the injection happens. The entry point and the fix are identical in all three. Only the difficulty of finding it and the noise it makes change.

In-band Blind Out-of-band
How the answer returns In the page, in results or in an error Inferred from behaviour or from response time Through a request the database makes outbound
What the attacker needs Nothing extra Many requests, one inference each Outbound DNS or HTTP from the database host
Speed Immediate Slow, hundreds or thousands of requests Fast
Chance of it being noticed Errors may be logged Volume is the only signal Egress logs, if anyone reads them
Typical reason it is missed Rare, most teams fix these No visible symptom at all Nobody monitors database egress

The practical consequence is for the defender, not the attacker. If the only thing separating you from mass extraction is that errors are suppressed, you do not have a control. Suppressing errors raises the request count and nothing else. Blocking outbound traffic from database hosts, on the other hand, removes an entire channel and is worth doing on its own merits.

Common mistakes

Believing an ORM makes it impossible. It makes the default safe. Every raw fragment, native query, dynamic filter and interpolated sort field is outside that default, and those are the parts a developer writes deliberately.

Escaping quotes instead of binding parameters. Escaping is context-dependent, character-set dependent and easy to bypass in numeric contexts where no quote is needed at all. Binding removes the question.

Parameterising the values and forgetting the identifiers. Table names, column names, sort direction and LIMIT in some engines cannot be bound. They need an allow-list that maps a user-facing token to a fixed internal name, never a filter that inspects the string.

Running the application as the database owner. The account the application connects with decides what a successful injection reaches. We routinely find one account with rights over every schema, which turns a flaw in one reporting screen into access to everything.

Treating a clean scan as coverage. Scanners test what they can reach. Second-order and authenticated back office cases need a session, a workflow and a reason to look.

How to reduce it

Bind every value, without exception, and treat each place you cannot bind as a design decision with a written answer. For identifiers, map the user’s choice to a fixed name from a list you control, so the string from the request never reaches the query text. Audit stored procedures for dynamic SQL as carefully as application code, because the parameter boundary at the procedure call is not the boundary that matters.

Then reduce what a successful injection is worth. Give the application a database account with rights to the objects it actually uses, separate read and write roles where the workload allows, and remove permissions on schemas the application never touches. Block outbound DNS and HTTP from database hosts. Where the workload is a fixed set of statements, allow-listing at the database layer is achievable and turns injection into an error.

For detection, DAST finds reflected and error-based cases cheaply; static analysis with SAST finds the concatenation in code, including in the branches a scanner never reaches. Neither finds second-order cases reliably. In the logs, the signal is not a quote character: it is one session issuing many near-identical requests whose only difference is a numeric boundary, or a query on a normally fast endpoint taking a suspiciously round number of seconds.

Where this shows up in an audit

In a web or API report, SQL injection is written against the parameter and the code path, not against the application, and it is one finding per injectable sink. The evidence we hand over is the exact request, the observable difference that proves the query changed meaning, the database account the application connects with, and the smallest possible proof of read access. We do not include extracted data in the report body.

Severity moves with what the connecting account can reach and with whether the flaw allows writing as well as reading. The same technique against a read-only reporting account with one schema is a serious finding; against an account that owns the database, and where stacked queries or file access are available, it is the top of the report, and it is frequently the route to remote code execution.

This comes up on every engagement where we test an application against its database.

FAQ

Is SQL injection still common in 2026? It is no longer the default state of a public login form, and it has not gone. It concentrates now in authenticated back office screens, reporting and export functionality, dynamic SQL inside stored procedures and legacy services behind an API gateway, which are the places automated testing tends not to reach.

Do parameterised queries fix SQL injection? They fix it wherever a value is being passed. They cannot help where an identifier is being chosen, such as a column name or sort direction, because those change the structure of the statement. Those need an allow-list. Saying “we use parameters” is not, by itself, an answer.

What is blind SQL injection? Injection where the application returns nothing useful, so the attacker recovers data one bit at a time from a difference in behaviour or in response time. It is slower and just as complete, and it is the form most likely to be sitting in a production application right now.

Can a WAF stop SQL injection? A WAF blocks known payload shapes and raises the effort. Encoding, comments and engine-specific syntax get past it, and it sees nothing at all in second-order cases where the malicious value arrived as ordinary text days earlier. It buys time; it is not a fix.

¿Quieres ver cómo trabajamos en Asperis Security?

Agenda 30 minutos con uno de nuestros especialistas. Revisamos tu stack y te decimos qué conviene probar primero.