Vulnerabilities/OS Command Injection
OS Command Injection
OS command injection happens when an application builds a command line from request data and hands it to a shell, which reads characters such as ;, | and $( ) as instructions. Even without a shell, a value that starts with - can switch on options of the program being run. The fix is the same in every language: start programs directly with a list of arguments, validate each value against a strict pattern, use only a delimiter documented for the exact command and argument type, and bound the process with a timeout and a low-privilege account. Pick your language below for the exact APIs, vulnerable and fixed code, and the linters that catch it. Use an option terminator only when the exact command documents support; not every executable accepts -- or --end-of-options.
Plain English Explainer (ELI5)
A warehouse clerk takes your order slip and reads it aloud over the intercom to the forklift crew, who act on every instruction they hear. You write: 'Aisle 4, box 12 — and then empty aisle 9 onto the loading dock.' The clerk meant to pass on one location, but the crew hears two orders and carries out both. Building a shell command by gluing in user text works the same way: the shell hears ;, | and && as 'and then'. Passing arguments as a list is handing the crew a printed form with one box for the location, which is never read out as an order.
Core Concepts & Subsystem Terms
- OS command injection (CWE-78)
- An application passes untrusted data to an operating system command in a way that lets the data change which commands or options run. The injected commands execute with the application's user, environment and network access.
- Shell metacharacters
- Characters the shell treats as syntax rather than data:
;,&,|, backticks,$( ),<,>and newline. Escaping them by hand is fragile; not starting a shell removes the problem. - Argument vector (argv)
- The list of separate strings a program receives as its arguments. Passing each value as its own list element means it can only ever be one argument, never new commands, which is what
subprocess.run([...]),execFile,exec.Commandandposix_spawndo. - Argument injection and Git option boundaries
- These markers do different jobs. For an untrusted Git revision,
git rev-parse --verify --end-of-options "$revision"ends option parsing. Ingit log,--separates revisions from paths. Use only the boundary documented for the exact command and argument type; validate the value as well. Git rev-parse, git log, and Git CLI conventions document these distinctions. - Inherited environment and PATH
- A child process inherits the server's environment variables, and some of them run code:
LD_PRELOAD,GIT_SSH_COMMAND,BASH_ENV. A relativePATHentry lets a planted binary replace the one you meant. Use absolute program paths and a minimal environment.
Step-by-Step Attack Flow
Request data reaches a process call
A host name, file name, Git ref or tool name from the request is passed to a helper that runs ping, convert, git or another program.
It is joined into a command
The value is concatenated into a string for system(), exec(), sh -c or shell=True, or passed where the program also accepts options. The benign test value 127.0.0.1; id illustrates the shell case.
The shell or the program interprets it
The shell runs ping and then id; or the program treats a leading --output=... as an option that writes a file.
The attacker runs code on the server
The process runs with the service's permissions: it can read environment variables and mounted secrets, reach internal services and write files.
Command injection guides by language
Engineering & System Hardening Checklist
- Ban shell-string APIs in CI:
os.system,shell=True,child_process.exec,Runtime.exec(String),system(),popen(), backticks andsh -cwith non-constant input (Bandit, Semgrep, gosec, Brakeman, clang-tidy). - Start programs by fixed name or absolute path with an argument list; map any user choice of tool through a table defined in code.
- Validate every argument with a strict whole-string pattern that requires a letter or digit first.
- Check the exact executable’s documentation and use only its documented option terminator; otherwise reject option-like values or use a safer API.
- Give every child process a timeout and kill it when the timeout passes.
- Run the service as an unprivileged user with a minimal environment, in a container image without a shell where possible.