In a nutshell Command injection is when a web app passes your input into a system shell command. Add your own command onto the end, and the server runs it — straight remote code execution, often as whatever user the web server runs as. Injection again, but the "language" you escape into is the operating system's shell.
The root cause
Some apps shell out to do a job — ping a host, convert an image — and build that command with your input glued in:
# the app runs: ping -c 1 <your input>
# you enter: 8.8.8.8; whoami
# the shell runs TWO commands:
ping -c 1 8.8.8.8
whoami # ← yours
The ; ends the intended command and starts yours. The shell runs both — it can't tell the app's command from your piece.
Analogy — dictating to an assistant. "Print this document — and email me the whole client list." A careless assistant does both, treating your add-on as part of the job. The shell is that assistant.
Shell metacharacters — the joints you exploit
Any of these, unfiltered, is an injection point:
| Char | Effect |
|---|---|
; |
run the next command after this one |
&& |
run the next only if this one succeeds |
| |
pipe this command's output into the next |
`cmd` / $(cmd) |
run a command, substitute its output |
& |
run in the background |
Finding it
Anywhere the app seems to run a system tool with your input, append a probe and watch:
; id # does the output of id appear?
&& id
$(id)
; sleep 5 # does the response take 5s longer? (blind)
Why it's so dangerous — and where it leads
It's usually direct code execution, no clever chaining. Whatever the web server can do, you can now do — and it typically drops you as a low service account, which is exactly where privilege escalation begins:
command injection → low service shell (www-data)
+ → reverse shell for interactivity (see Reverse & bind shells)
+ → privilege escalation → root (see Linux privesc)
nslookup). If you must run a program, pass arguments as a separate list, never one concatenated string, so input can only be an argument, never a new command. Validate against an allow-list and run least-privileged. Same lesson: data stays data.