Stacked Queries: Running Multiple Statements Through One Injection
Stacked-query injection terminates the intended statement and appends another one. Learn why driver protocol and privileges matter, how a harmless delay proves batching, and how to prevent it.
1; WAITFOR DELAY '0:0:2' --At a glance
CWE-89- Interpreter
- SQL database engine
- Required condition
- The driver and database accept multiple statements in the vulnerable execution path.
- Potential impact
- Independent read, write, administration, or destructive statements may run.
- Primary defense
- Parameterized queries, single-statement execution, and minimal database privileges.
Safe practice: use benign proof only, in a disposable local lab or on a system you are explicitly authorized to test.
On this page
Most SQL injection bends an existing query — a UNION bolted onto a SELECT, a boolean appended to a WHERE. Stacked queries do something blunter. By terminating the original statement with a semicolon and writing a second one after it, an attacker stops borrowing the application's query and runs an arbitrary statement of their own. A bug that looked read-only suddenly becomes a way to write, delete, or — on the right engine — execute operating-system commands.
This post covers how statement stacking works, the driver and engine combinations that actually allow it, and why it turns the severity of an injection up sharply.
When this technique applies
The SQL text must be injectable, the database and client protocol must accept more than one statement in the same execution, and the appended statement must be valid under the application's database privileges. A semicolon that merely causes a syntax error does not prove stacking.
Support is not a property of the database name alone. Driver mode, prepared versus simple protocol, connection settings and API method all affect whether multiple statements are accepted. Test the deployed combination, not a generic matrix copied from another stack.
How it happens
The injection point is the usual concatenation. What stacking adds is a database driver that will execute more than one statement from a single string. Take a status-update endpoint that splices an id straight into the query:
$id = $_GET['id']; $db->exec("UPDATE orders SET seen = 1 WHERE id = $id");
The application intends exactly one UPDATE. But the id is unbound, so an attacker can close that statement and start another:
1; WAITFOR DELAY '0:0:2' --
On SQL Server, if the driver hands both statements to the database, the order is marked seen and the batch pauses for two seconds. A matched no-delay control distinguishes the appended statement from ordinary latency. Nothing is created, changed beyond the intended update, or deleted by the proof.
Why stacking changes impact
Other SQL injection techniques usually operate inside the original statement. Stacking can introduce an independent statement type: a read-only-looking endpoint may reach an INSERT, UPDATE, data-definition operation or administrative procedure if the database account has those rights. Some engines expose optional operating-system integration to highly privileged roles, making least privilege especially important.
Stacking does not automatically grant write or administrative access. It lets the attacker attempt another statement with exactly the privileges already granted to the application account.
When stacking actually works
Statement stacking is not universal; it depends on what the driver sends in one call.
| Platform | Typical behavior to verify |
|---|---|
| SQL Server | T-SQL batches are commonly accepted by direct command APIs |
| PostgreSQL | Simple-query protocol can carry multiple statements; extended prepared execution is stricter |
| MySQL | Multi-statements are commonly disabled unless explicitly enabled by the client |
| SQLite | Typical APIs prepare or step one statement at a time |
These are review hints, not guarantees. A wrapper can split strings itself, a connection flag can enable batching, and a framework upgrade can change protocol behavior.
Safe verification and false signals
- Use a fixed, reversible or timing-only statement in an isolated lab.
- Pair it with a no-delay control and repeat at low request volume.
- Confirm the first statement still behaves as expected; a syntax error alone is insufficient.
- Do not create accounts, alter permissions, enable administrative features or touch real data.
- Record the exact driver, version, protocol and connection setting with the result.
The fix: parameterize and remove the privilege
Binding the input closes stacking the same way it closes every other variant — there is no statement boundary to write past when the value is a parameter:
using var cmd = new SqlCommand( "UPDATE orders SET seen = 1 WHERE id = @id", connection); cmd.Parameters.Add("@id", SqlDbType.Int).Value = id;
With a parameter, the entire supplied value is treated as one invalid id, not a second statement. Reinforce it by choosing data-access APIs that forbid multi-statements and by running under a least-privilege database account: a read path should not hold schema-changing rights, broad cross-table writes or access to operating-system integration.
How SelfSec finds it
SelfSec's SQL injection engine covers 19 database families with error-based, boolean, time-based, UNION, stacked and out-of-band techniques. For stacked queries it tests the deployed engine, driver, protocol and connection behavior as one combination rather than assuming support from the database name alone. It reports the issue only when the second statement produces repeatable evidence and includes a reproduction request. Core scan processing runs locally on 127.0.0.1; managed OOB confirmation uses the documented remote callback flow when enabled.
References
SelfSec is intended strictly for authorized security testing of systems you own or are explicitly permitted to assess.
Do both things about SQL Injection
SelfSec covers this class from both sides — the scanner confirms it in your own app, the firewall blocks it in front of your origin while the fix ships.