Command injection is a critical vulnerability that enables attackers to execute unauthorized commands on a system. In this blog, we’ll explore both whitebox and black box approaches for detecting and these risks. Join us as we break down effective strategies to detect this vulnerability in your infrastructure. Before we dive into this topic we must understand what is a shell metachracter
and what is a payload
in terms to command injection.
Shell Metacharacters (Cybersecurity POV, With Example)
In Linux, we use ls ; whoami
to execute both commands sequentially—ls
lists files, and whoami
reveals the current user. Similarly, in command injection, attackers exploit shell metacharacters to sneak malicious payloads alongside valid HTTP parameters without breaking the application’s expected functionality. For example, if an app executes ping 127.0.0.1
, an attacker could send ping 127.0.0.1 ; cat /etc/passwd
to execute both commands while keeping the original request intact.
Definition of Payload
The payload consists of shell metacharacters (&
, &&
, |
, ||
, ;
, \n
, >
, <
, $()
) and their URL-encoded versions (%26
, %26%26
, %7C
, %7C%7C
, %3B
, %0A
, %3E
, %3C
, %24%28%29
). These characters are used to chain, execute, and manipulate commands, making them essential for testing and exploiting command injection vulnerabilities.
Black Box approach
To begin with blackbox
refers to scenario where we don’t have access to source code and in whitebox testing we have access to source code. Now the following slide from Rana Khalil
is extremely useful in understanding the black box approach.
1. OS command injection, simple case
Lab URL - https://portswigger.net/web-security/os-command-injection/lab-simple
First we fuzz for valid shellmeta-chracters
that the application takes as input. You may use the following custom wordlist and fuzz along side each input parameters to find the shell metachracter that is accepted. This wordlist contains URL encoded values as well.
/home/mccleod/custom via 🐍 v3.8.10 took 12s
❯ cat shell-metachracters.txt
&
&&
|
||
;
\n
>
<
$()
%26
%26%26
%7C
%7C%7C
%3B
%0A
%3E
%3C
%24%28%29
Now in the application, we click on every possible clicks, and we see an interesting option called check stock
, and behind the scenes we notice that this is an POST
request.
Now let’s fuzz for shell metacharacters along side productId
and storeId
, and see what happens. Add payload insertion points in the following area, and make sure you have selected battering ram
attack option. Make sure you copy and paste payloads from shell-metacharacters.txt
. And start your attack.
We see couple of interesting responses. /n
and$()
return numbers like 32
and 42
and the $
character does not return much anything but at same time does not give errors like the rest of the metacharacters.
Note that do not do this mistake of entering payload at two points in a single instance. Why because what if productId
is not vulnerable but storeId
is? in that case we have to separately fuzz for each point, as a rule of thumb remember always fuzz one parameter or one endpoint at a time.
We see interestingly ;
character gives us insight of an shell script and interesting details like the location of the script. The same holds true even ;
character is URL encoded.
Finally after a lot of trail and error we see that payloads with metacharacters like %oa
, |
and ;
work in the payload. Even the url -encoded ones
work well in this lab
With this we solve the lab. But remember when we fuzzed with ;
value as input, we found the location of an shell script? Let’s take an look at the script for better understanding on why this command injection vulnerability arises.
Now if we use cat
linux command to have a look at stockreport.sh
then we see that eval
function is used. Using eval
on unsanitized input allows arbitrary code execution, enabling an attacker to inject and execute malicious commands.
2.Lab: Blind OS command injection with time delays
Lab URL : https://portswigger.net/web-security/os-command-injection/lab-blind-time-delays
Now this lab is very much similar to previous one, this labs acts like an good reinforcement for the things and methodology we have learnt from the previous lab.
Step 1: Click all button and look for interesting requests and/or endpoints.
Step 2: Once the endpoint is found, now fuzz all input field with shell-metachracters
to find which one will work.
Step 3: Once you find the valid shell metachracter
like |
or ;
now it’s time to test for command injection. Try with payloads like ;whoami
or |whoami
Step 4: Since this lab has something to do with time delays, we try some commands like sleep
or pinging localhost
that way this entire thing runs on loops.
sleep 10
ping -c 127.0.0.1
Now looking at burp suite, unlike previous lab we don’t have check stock
but on to our right hand top corner, we see something called submit a feedback
. Let’s try that out
With trial and error, we find that email
parameter is vulnerable to command injection. But something is very wrong and is not working. Even if we URL-ENCODE
the characters like ; sleep 10
or | sleep 10
then we are not still getting the delay.
Remember we used delimited in sql injection
to comment out the errors
from the sql syntax
which we give as input? In command injection we can use #
or the hash symbol to comment out the errors. When we use #
and url encode
it with ctrl+u
then we get time delay for 10 seconds which solves our lab.
3. Blind OS command injection with output redirection
Lab URL - https://portswigger.net/web-security/os-command-injection/lab-blind-output-redirection
Some of you who will read this section might feel like this is CTFish, or you might even ask what’s the point of output redirection if I can execute my command directory in to the server? Well you got a point, and the only reason I have included this is because it forced me to think out side of the box, and made me wonder can we take command injection beyond simply executing arbitrary commands on the server? Well the answer is yes ,and in this lab we are going to execute our command, redirect the output of the commend to a text file and via LFI or local file inclusion we are going to read output. Let that sink in for a moment and if this sounds too bizarre then by the end of this section I hope it is clear.
On the first glances, we see an feedback form similar to last or previous exercise. Now we can fuzz for metachracters like |
, ||
, ;
etc, and on each parameter, and we see that in name parameter if we try something like ;whoami
or |whoami
server responds with message could not save
.
Since we don’t have access to the source code, our best bets is to consider that the application might be configured in a way where output is not displayed, unless saved. Recall that you can use >
operator or >>
in linux to save output to a text file, let’s do that here as well.
Still we are getting the same error. We have redirected the output to /var/www/images
which is given at the lab description and we are creating a new file called output.txt
and note that >
creates an new text file.
Now let’s use #
delimiter for commenting out errors. We are still getting the error, therefore our last resort is to encode things with URL so that the server understands and responds well. You can use ctrl+u
to URL decode on burp suite.
Now we see that our payload gets processed well and in case if there is any error just reset or restart the lab. Feel feel to try other shell metacharacters like &
and so on. Now how are we going to retrieve or get the file?
Well lets look round the webpage, we see a lot of images, if we right click and view the image the URL becomes something like the following below.
https://0a3c008f04204dc4821d792b006b008e.web-security-academy.net/image?filename=7.jpg
Do you see the parameter ?filename=7.jpg
? what if we change the 7.jpg
to output.txt? Will we be able to get files from that specific directory which is /var/www/images
? Let’s try that out.
When we change the filename to output.txt we see the output for whoami
command and with this we solve the lab.
White box approach
1. Looking Glass
Lab URL - https://app.hackthebox.com/challenges/looking%20glass/
At first glances are are given an website that pings IP address, and notice how realistic the output for ping is, it feels like exactly as if ping
command is executed on linux machine and the output is displayed. If ping command can be executed then what if other linux command might be executed by the server? Well let’s try it with the following command….
10.30.18.29; ls ../
We see that the with basic shell metacharacter we are able to execute any linux command. So let’s not quickly waste time and get the flag.
We can use cat
command to read out the flag for us.
10.30.18.29; cat ../flag_b1ubZ
With this we get our flag. But things are not so fun, we just got flag by basic linux command, that is definitely not interesting.
Source Code Analysis
Since we can execute commands, let’s have a look at what other files are present in the directory in which we are present.
10.30.18.29; ls
We see a file called index.php
and let’s read the source code.
Now with the help of following command we can directly read our index.php
file.
10.30.18.29; cat index.php
With the above command we get source code to index.php
. Our eyes are fixated on runTest
function which looks exactly like the following lines.
function runTest($test, $ip_address)
{
if ($test === 'ping')
{
system("ping -c4 ${ip_address}");
}
if ($test === 'traceroute')
{
system("traceroute ${ip_address}");
}
}
The PHP code is vulnerable to command injection in both its ping and traceroute features because it uses unsanitized user input directly in shell commands. Specifically, the runTest function takes the user-controlled value from $POST[‘ip_address’] and inserts it without any filtering into commands like system("ping -c4 ${ip_address}")
and system("traceroute ${ip_address}")
.
This lack of validation or sanitization means that an attacker could append shell operators (such as ;
, &&
, or |
) to the ip_address input to run arbitrary commands (for example, 8.8.8.8; rm -rf /
). Additionally, although the input field is pre-filled with the user’s IP address via <?= getUserIp() ?>
, it remains editable, allowing anyone to change it to a malicious value.
2. TimeKORP
Lab URL - https://app.hackthebox.com/challenges/TimeKORP
Now before we dive into technicalities lets have a look at possible payloads that work.
'+%0a+cat+/flag+%0a+'
'%3b+cat+/flag+||+'
'%3b+cat+/flag+%3b+%23'
'%3b+cat+/flag+%3b'
Logic for this payload is based on the source code. To sum up simply payload consists of the following things
[singlequote][space][shell-metacharacter][space]cat /flag[space][shellmetacharacter][singlequote]
Now let’s deep dive into the source code to understand why things work and why we have come up with this payload. Do remember even if this challenge is labelled as very easy
, it gets tricky, and some of the shell metacharacters like &
may not work. What’s more worse is ls
and other linux command might not give anything meaningful
Now the source code for this challenge is no longer on the hackthebox platform but Cryptocat
breaks it down for us. Feel free to check out his youtube video.
Source Code Analysis
In TimeController.php:
$format = isset($_GET['format']) ? $_GET['format'] : '%H:%M:%S';
$time = new TimeModel($format);
In TimeModel.php:
public function __construct($format)
{
$this->command = "date '+" . $format . "' 2>&1";
}
public function getTime()
{
$time = exec($this->command);
return isset($time) ? $time : '?';
}
After glancing at TimeController.php
and TimeModel.php
we come to following conclusion:
- Direct User Input in Shell Command: The user-supplied
format
parameter is concatenated directly into a shell command.
- Shell Command Execution: The code uses PHP’s
exec()
function to run the command, forming date '+[user_input]' 2>&1
.
How the Exploit Works
- When an attacker sends a payload like
+%0a+cat+/flag+%0a+
:
%0a
represents a newline.
- The command becomes:
date '+
cat /flag
' 2>&1
- Similarly, payloads like
; cat /flag ;
inject additional commands using ;
as a separator.
- Although the injection might succeed, “Permission denied” errors occur because the web app runs under restricted permissions, preventing access to sensitive files.
This is a classic command injection vulnerability (CWE-78) caused by insufficient input sanitization and validation.
3. LoveTok
Lab URL - https://app.hackthebox.com/challenges/LoveTok/walkthroughs
Now we have format
parameter in this application and we have come up with unique payload by understanding the source code. First let’s have a look at the payload. Then we will deep dive into the inner mechanics
/?format=${system($_GET[1])}&1=whoami
${system($_GET[cmd])}&cmd=ls /
We see that despite the error, we were able to execute commands like whoami
successfully into the web application.
Source Code Analysis
We are given zip code for this application and there are four main php
pages that grab our attention and they are:
a. TimeModel.php
class TimeModel
{
public function __construct($format)
{
// The format string is “sanitized” by addslashes
$this->format = addslashes($format);
// A time offset string is built using random values.
[ $d, $h, $m, $s ] = [ rand(1, 6), rand(1, 23), rand(1, 59), rand(1, 69) ];
$this->prediction = "+${d} day +${h} hour +${m} minute +${s} second";
}
public function getTime()
{
// The critical part: eval() is used to build a date string
eval('$time = date("' . $this->format . '", strtotime("' . $this->prediction . '"));');
return isset($time) ? $time : 'Something went terribly wrong';
}
}
What’s happening here in the above piece of code?
Input Handling: The constructor accepts a $format
parameter. It uses addslashes()
to escape certain characters (e.g., quotes, backslashes). However, escaping with addslashes()
is not enough when the input is later used in an eval()
context.
Dynamic Code Construction: In getTime()
, the code builds a PHP statement that calls date()
. It concatenates the (escaped) format string directly into the code string and then passes it to eval()
.
Risk: Because eval()
executes its input as PHP code, any code injection in $format
will be executed on the server.
b. TimeController.php
class TimeController
{
public function index($router)
{
// Directly taking a GET parameter and passing it to TimeModel
$format = isset($_GET['format']) ? $_GET['format'] : 'r';
$time = new TimeModel($format);
return $router->view('index', ['time' => $time->getTime()]);
}
}
What’s happening here in the above piece of code?
The controller takes the format
parameter from the URL (i.e., user input) and passes it directly to the TimeModel
without further sanitization.
This means an attacker can supply a malicious string in the format
parameter.
c. index.php and Router.php
// index.php snippet:
date_default_timezone_set('UTC');
spl_autoload_register(function ($name){
if (preg_match('/Controller$/', $name))
{
$name = "controllers/${name}";
}
else if (preg_match('/Model$/', $name))
{
$name = "models/${name}";
}
include_once "${name}.php";
});
$router = new Router();
$router->new('GET', '/', 'TimeController@index');
$response = $router->match();
die($response);
What’s happening here in the above piece of code?
The file sets up autoloading for classes, defines a basic route (mapping the root URL /
to TimeController@index
), and then processes the request.
2. How the Exploit Payload Works
The payload given is:
/?format=${system($_GET[1])}&1=whoami
Let’s break it down:
a. The Payload Components
format
parameter:
The value passed is
${system($_GET[1])}
In the context of the code, after addslashes()
, the value still ends up inside a string that is eventually passed to eval()
.
1
parameter:
This parameter is set to whoami
. It is accessed within the injected code via $_GET[1]
.
b. Injection Mechanics
String Creation:
The user-supplied format
parameter (after addslashes()
) remains essentially ${system($_GET[1])}
. Even though some characters are escaped, the dangerous code is still there.
Using eval():
When eval()
is called, it builds and executes a PHP statement that looks like:
$time = date("${system($_GET[1])}", strtotime("..."));
Because the string is double-quoted, PHP processes the ${...}
syntax, executing the code inside it.
Code Execution:
The expression ${system($_GET[1])}
triggers the system()
function with the parameter $_GET[1]
. Since the URL provides &1=whoami
, it executes system("whoami")
.
Result:
The output of the whoami
command is displayed, proving that arbitrary commands can be executed on the server.
3. Why addslashes()
Fails Here
Where to go from here?
One thing that you can do is to start doing these challenges along side as you are reading blog, and if you are someone who have already done most or all of these challenges then feel free to try a challenge called hsa
from hackthebox
which revolves around understanding the source code, and we have to apply the concept of command injection via output redirection
which we have learnt from lab 3. One of the common questions that I struggled with is how much programming or coding is required in cybersecurity? and some say we don’t but if you are planning to do web application pentesting, then learning to review and find vulnerabilities in source code is an valuable skill. That being said if definitely helps to come or have an developer background, but does not have to be always the case. Atleast one should be familiar with reading of code and understanding what is happening under the hood, or at bare minimum one should be willing to dig into the bits of code and deepen one’s understanding using google, youtube and chatgpt. Stay tuned for more interesting blogs in which we will deep dive on each vulnerabilities.