Skip to content

Remote code execution (RCE)

CWE-94CWE-77OWASP A03:2021Updated August 29, 20265 min read

Remote code execution (RCE) is a vulnerability that lets an attacker run their own code or system commands on your server from a distance, because unsafe input reaches a command, a template, or a deserialisation routine. It is the most severe outcome an application can have: from that point on the attacker runs code with the privileges of your server.

Remote code execution, or RCE, is widely regarded as the most severe outcome a web vulnerability can have. The attacker does not merely send data to your application, but their own program code or system commands, and your server dutifully runs them. This article explains what RCE is, how such an attack unfolds in practice, what an attacker can achieve with it, and how to lock your application down against it.

What is remote code execution?

Remote code execution (RCE) is a vulnerability that lets an attacker run their own code or commands on your server from a distance. It happens the moment external input (a parameter, an uploaded file, a header) reaches a place where the application treats that input as an instruction to execute rather than as harmless data.

Picture a building with a mail slot meant only for supplying an address. Behind the slot sits a diligent assistant who does exactly what the note says. Normally he receives an address and looks up the route. But if someone writes “and then unlock every door” on the note, the overly obedient assistant does that too. In an RCE, your server is that assistant: anything that comes in through a poorly guarded opening can become an order.

The vulnerability therefore does not live in one specific technique, but in the pattern of input and executable instructions blurring together. That can happen when input reaches a system command (command injection, CWE-77), when an application evaluates a string as program code with something like eval (code injection, CWE-94), or when a vulnerable library deserialises untrusted data. The common thread is always the same: the line between data and code has disappeared.

How does an RCE attack work?

The classic route is command injection: the application builds a system command by pasting user input into a string and hands it to the shell. Take a simple diagnostic feature that checks whether a host is reachable using the ping command.

Vulnerable:

const { exec } = require("child_process");

app.get("/ping", (req, res) => {
  const host = req.query.host;
  // Input is pasted straight into the shell command
  exec("ping -c 1 " + host, (err, stdout) => {
    res.send(stdout);
  });
});

If an ordinary user enters a hostname, this works fine. But exec hands the whole string to a shell, and the shell recognises special characters. Instead of requesting /ping?host=example.com, an attacker asks for /ping?host=example.com;cat /etc/passwd. The shell reads the semicolon as a separator between two commands and simply runs the second one. With variants such as host=example.com && rm -rf /data or downloading and launching a script, this harmless diagnostic window turns into a full command line on your server.

The fix is to never let input pass through a shell. Call the program directly and supply its arguments as a separate list, so special characters lose their meaning. On top of that, validate the input against a strict pattern.

Safe:

const { execFile } = require("child_process");

app.get("/ping", (req, res) => {
  const host = req.query.host;
  // Allow only letters, digits, dots, and hyphens
  if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
    return res.status(400).send("Invalid host");
  }
  // Arguments are passed as a list, with no shell in between
  execFile("ping", ["-c", "1", host], (err, stdout) => {
    res.send(stdout);
  });
});

Now example.com;cat /etc/passwd is treated as a single, invalid hostname: there is no shell to read the semicolon as “start a new command”, and the validation rejects the input anyway. The program receives exactly the arguments you intended and nothing more, precisely the separation between command and data that makes the attack impossible.

Filtering suspicious characters out of input with a blocklist is not reliable protection: attackers have endless variants using alternative encodings, whitespace, or quotes. Call the underlying program directly with an argument list, and work from an allowlist of permitted values.

What is the impact of remote code execution?

The impact of RCE is by definition severe, because the attacker runs code with the same privileges as your application. In the mildest case they read files never meant for them: configuration files, secret keys, the source code. But it rarely stops there. With the ability to run commands, an attacker can install a persistent backdoor, encrypt data for ransom, or use the server as a launch point for further attacks.

That last point is what makes RCE so dangerous. A compromised server almost always sits inside your network and has access to things that are unreachable from outside: internal APIs, databases, other machines. From there an attacker moves laterally through the infrastructure, harvests credentials, and works towards the systems that truly matter. What started as a single vulnerable input field then ends in a full breach.

For the organisation this means a data breach, service outages, possible extortion, and the legal and reputational fallout that follows. Because the severity ranges from reading a single file to full takeover of server and network, the score runs from high to critical, and in practice a genuine RCE sits almost always at the top of that scale.

How do you detect remote code execution?

A tester looks for every place where input can influence a command, a file path, a template, or a deserialisation routine. A first check is to send along shell characters such as ;, |, or && followed by a harmless command, and watch whether the output changes. If that produces no visible result (with blind variants there is no direct response), a pentester measures the behaviour indirectly: a command that deliberately delays the response, or that triggers an outbound network request to a server they control, thereby revealing the execution.

Automated scanners and dependency checks catch many known cases, such as a vulnerable library version, but miss the RCEs hidden deep in custom logic or behind authentication. A thorough penetration test therefore combines automation with manual investigation. This is exactly the kind of weakness AssistSec looks for during a security assessment and reproduces demonstrably, so you know which input really leads to code execution.

How do you prevent remote code execution?

  • Call external programs without a shell. Use a variant that passes the program and its arguments as a separate list (such as execFile instead of exec), so special characters carry no meaning.
  • Never evaluate untrusted input as code. Avoid eval and similar constructs driven by user input, and use safe template engines that keep data and code apart.
  • Validate input with an allowlist. Decide per field which values are valid and reject the rest, instead of trying to filter out “dangerous” characters.
  • Track dependencies and patch quickly. Many RCEs come from third-party components; follow advisories and update vulnerable versions as soon as possible.
  • Apply the principle of least privilege. Run the application under an account with minimal rights and in an isolated environment, so a successful attack achieves as little as possible.
  • Be careful with deserialisation and file uploads. Do not deserialise untrusted data, and never treat an uploaded file as something that may be executed.
  • Have the code tested regularly. A targeted penetration test and code review catch the RCE paths that scanners skip.

Sources

Frequently asked questions

What is the difference between RCE and command injection?

Command injection is one of the most common routes to remote code execution: unsafe input ends up in a system command. RCE is the broader outcome (running code from a distance) which can also arise through code injection, unsafe deserialisation, or a vulnerable library.

Why is RCE so much more serious than other vulnerabilities?

Most vulnerabilities let an attacker read or change data. With RCE they run their own code on your server, with the same privileges as your application. That effectively puts everything on that machine within reach, and often the rest of the network behind it.

Can an RCE happen without me writing unsafe code myself?

Yes. Many RCEs come from a vulnerable third-party library or component. Log4Shell is the best-known example. Knowing which dependencies you run and patching them promptly is therefore just as important as writing safe code yourself.

Does a web application firewall protect against RCE?

A WAF blocks known attack patterns and is a useful extra layer, but attackers routinely bypass filters with alternative encodings or unknown payloads. The real fix lies in safe calls, input validation, and timely patching.

Related articles

Press / to search · Esc