Script Valley
Web Security Fundamentals for Developers
Injection Attacks: SQL, Command, and LDAPLesson 2.3

What is OS command injection and how to avoid shell calls

command injection mechanics, child_process risks, shell metacharacters, execFile vs exec, avoiding shell, input allowlisting, shellescape

OS Command Injection

OS command injection diagram

Command injection happens when your application passes user-controlled data to a shell. It is often more severe than SQL injection because it gives the attacker direct access to the operating system.

The Vulnerable Pattern

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

// VULNERABLE — exec passes the string to /bin/sh
const filename = req.query.filename; // attacker sends: report.pdf; rm -rf /
exec(`convert ${filename} output.png`, (err, stdout) => { ... });

// Shell interprets: convert report.pdf; rm -rf / output.png
// Two commands execute

Fix 1: Use execFile with an Arguments Array

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

// execFile does NOT invoke a shell — arguments are passed directly
execFile('convert', [filename, 'output.png'], (err, stdout) => {
  if (err) return res.status(500).json({ error: 'Conversion failed' });
  res.sendFile('output.png');
});

Fix 2: Allowlist Valid Input

// Only allow alphanumeric filenames with a whitelist regex
const safe = /^[a-zA-Z0-9_-]+\.(pdf|png|jpg)$/.test(filename);
if (!safe) return res.status(400).json({ error: 'Invalid filename' });

The best defense is to avoid shell calls entirely. Most tasks that seem to require a shell (file conversion, image processing, archiving) have Node.js libraries that do the same job without spawning a shell. Use sharp instead of ImageMagick via shell, archiver instead of zip via exec.

Up next

Path traversal attacks: how attackers escape your intended directory

Sign in to track progress