Most pentesters or OffSec students are familiar with inbound SQLi. Those are the injections that give you text back in the browser. Easier to exploit, easier to identify. But a site can still be vulnerable to SQLi even when the app gives you nothing back. It may support stacked queries. And if it does, you can own the box. Keep reading and I'll explain what that is and how to abuse it.
Let's start by defining the term. A stacked query is just two or more SQL statements separated by a semicolon, both executed in one call. Check the two queries below:
For someone who doesn't know TSQL it may look like a single query. But the semicolon breaks it down into two.
Now, both run. The problem is the app usually only shows the result of the first one. So even if your second query does something useful, you won't see it in the response.
MSSQL supports this natively. MySQL and Oracle? They don't, at least not in most driver configurations. So when you're dealing with MSSQL, stacked queries are a real attack surface even when everything looks blind.
I remember this being a huge debate back during my DBA days when I was still working for Hewlett Packard. My colleagues, Oracle DBAs, said stacked queries were bad and insecure. But at the same time, the implementation of some solutions was actually easier with TSQL because Microsoft MSSQL supports stacked queries and Linked Servers. Even today and as far as I know, this is not something Oracle does natively. To be honest, it's a curse and a blessing at the same time. And soon you'll see why.
UNION is the most common way to extract data from a web app that is vulnerable to SQLi, but it has a hard requirement. The number of columns in your injected SELECT has to match the original query exactly. You also have to get the data types right. If you don't know the query structure you're injecting into, you're guessing. And even if you get it right, UNION only works when the response actually reflects column data back to you. So no output means UNION is useless.
But stacked queries don't care about any of that. You're not appending to the original SELECT. You're starting a new statement entirely. Column count is irrelevant. Data types are irrelevant. You're just telling the server to do something after it finishes the first query.
Now, before you throw any payloads and start testing for stacked queries, you want to confirm the injection point actually supports it. And the easiest way to do that on MSSQL is with WAITFOR DELAY.
The idea is simple. You inject a second statement that tells the server to sleep for a few seconds. If the response takes that long to come back, the server executed your query. Simple.
If the response takes about five seconds longer than your baseline, you've got stacked queries. That's your confirmation.
Now, once you've confirmed it works, the path to code execution on MSSQL is pretty straightforward. You need xp_cmdshell, and by default it's been disabled since SQL Server 2005. But disabled doesn't mean gone. You can turn it back on if you have the right privileges, and that's where EXECUTE AS LOGIN comes in. Oh yeah!
The idea is to impersonate the sa login for the duration of your injected statements. For this to work, the current login needs to have been explicitly granted IMPERSONATE on sa, or it already belongs to the sysadmin role, which gets that permission. Once you're running as sa, sp_configure is available and you can flip xp_cmdshell back on. Executing xp_cmdshell requires CONTROL SERVER permission, which sa has. That's the whole chain.
rlwrap nc -lvnp 443
Now we're ready for the last two payloads. These are the ones that get us access to the underlying application or server:
whoami first to confirm. Also, if Tamper Protection is enabled, the command will appear to succeed but nothing actually changes. Defender stays on and gives you no error. In lab environments Tamper Protection is usually off. In hardened real-world targets it likely isn't.
The last step is the one doing the heavy lifting here. Notice that instead of dropping a binary to disk and executing it, you're telling PowerShell to reach out to your HTTP server, pull shell.txt directly into memory, and execute it on the spot. Nothing written to disk. It's a classic in-memory execution technique, and it beats dropping a file to disk to get your reverse shell. A file on disk gets flagged by Defender immediately.
xp_cmdshell runs as the SQL Server service account at the Windows level, not as sa. sa is a SQL login, not a Windows account. So even after impersonating sa inside SQL Server, the OS commands that xp_cmdshell spawns run under whatever Windows identity the SQL Server service is using. In a lot of lab environments that's a local admin or NT SERVICE\MSSQLSERVER. In others it's more restricted. Run whoami and see for yourself.
Stacked queries are one of those SQLi techniques that don't get enough attention. Most people see a blind injection and move on. Don't. Test the WAITFOR, measure the delta, and if the server sleeps, you're already halfway to a shell. The rest is just chaining the right payloads in the right order.