HIGH command injectionexpress

Command Injection in Express

How Command Injection Manifests in Express

Command injection in Express applications typically occurs when user input flows into Node.js child process APIs without proper sanitization. Express's role as a web framework means it often handles HTTP parameters that developers then pass to system commands.

The most common pattern involves using child_process.exec(), execSync(), or spawn() with user-controlled data. For example, an Express route that accepts a filename parameter and passes it to a shell command creates an immediate injection vector:

 

Express-Specific Detection

Detecting command injection in Express requires both static code analysis and dynamic testing. Static analysis tools can identify dangerous patterns like direct use of child_process.exec() with user input. Look for these Express-specific patterns:

// Dangerous pattern - user input flows directly to shell
app.get('/download', (req, res) => {
  const filename = req.query.file;
  exec(`cat /files/${filename}`, (err, stdout) => {
    res.send(stdout);
  });
});

Dynamic scanning with middleBrick specifically tests Express endpoints by sending payloads designed to trigger command execution. The scanner attempts to inject semicolons, ampersands, and other shell metacharacters into request parameters and observes whether the server responds with command execution evidence.

middleBrick's black-box approach sends payloads like:

; echo 'middleBrick_test'
& echo 'middleBrick_test'
| echo 'middleBrick_test'
$(echo 'middleBrick_test')

The scanner monitors for timing differences, error messages containing injected content, or unexpected output that indicates successful injection. For Express applications specifically, middleBrick tests both URL parameters and JSON body parameters that might flow to child processes.

middleBrick's LLM security checks also detect if Express endpoints serve as interfaces to AI models, where prompt injection could lead to command execution in the model's execution environment.

Express-Specific Remediation

Express developers should replace shell-based approaches with safer alternatives. The primary fix is using child_process.execFile() or spawn() with explicit arguments arrays instead of shell commands:

// Safe approach - no shell interpretation
app.get('/download', (req, res) => {
  const filename = path.basename(req.query.file); // Prevent path traversal
  const filePath = path.join('/files', filename);
  
  // Use execFile with arguments array
  execFile('cat', [filePath], (err, stdout) => {
    if (err) return res.status(500).send('Error');
    res.send(stdout);
  });
});

Another Express-specific pattern involves using the shell-quote library to safely escape arguments when shell execution is unavoidable:

const shellQuote = require('shell-quote');

app.post('/run-command', (req, res) => {
  const { command, args } = req.body;
  
  // Validate command against whitelist
  const allowedCommands = ['ls', 'pwd', 'whoami'];
  if (!allowedCommands.includes(command)) {
    return res.status(400).send('Invalid command');
  }
  
  // Escape arguments safely
  const escapedArgs = args.map(arg => shellQuote.quote([arg]));
  const fullCommand = `${command} ${escapedArgs.join(' ')}`;
  
  exec(fullCommand, (err, stdout) => {
    if (err) return res.status(500).send('Error');
    res.send(stdout);
  });
});

For file operations commonly handled in Express routes, use Node.js fs module instead of shell commands:

// Replace shell cat with fs.readFile
app.get('/download', (req, res) => {
  const filename = path.basename(req.query.file);
  const filePath = path.join('/files', filename);
  
  fs.readFile(filePath, 'utf8', (err, data) => {
    if (err) return res.status(500).send('Error');
    res.send(data);
  });
});

Implement input validation using Express middleware to sanitize parameters before they reach command execution logic:

const sanitizeInput = (req, res, next) => {
  const dangerousChars = /[;|&$`()<>"'\s]/;
  
  for (const [key, value] of Object.entries(req.query)) {
    if (dangerousChars.test(value)) {
      return res.status(400).send('Invalid input');
    }
  }
  next();
};

app.get('/safe-download', sanitizeInput, (req, res) => {
  // Safe to use req.query.file here
  const filename = path.basename(req.query.file);
  fs.readFile(path.join('/files', filename), (err, data) => {
    res.send(data || '');
  });
});

Related CWEs: inputValidation

CWE IDNameSeverity
CWE-20Improper Input Validation HIGH
CWE-22Path Traversal HIGH
CWE-74Injection CRITICAL
CWE-77Command Injection CRITICAL
CWE-78OS Command Injection CRITICAL
CWE-79Cross-site Scripting (XSS) HIGH
CWE-89SQL Injection CRITICAL
CWE-90LDAP Injection HIGH
CWE-91XML Injection HIGH
CWE-94Code Injection CRITICAL

Frequently Asked Questions

How does middleBrick detect command injection in Express applications?
middleBrick performs black-box scanning by sending payloads containing shell metacharacters to Express endpoints. It tests URL parameters, JSON body parameters, and headers for injection vulnerabilities. The scanner looks for timing differences, error messages containing injected content, or unexpected output that indicates successful command execution. For Express specifically, it tests both GET and POST endpoints that might pass user input to child processes.
Can command injection in Express lead to remote code execution?
Yes, command injection in Express can lead to full remote code execution if the injected commands are executed with sufficient privileges. An attacker could upload reverse shells, modify system files, or execute arbitrary scripts. The severity depends on the Node.js process permissions and whether the application runs as a privileged user. Using exec() with user input is particularly dangerous because it provides full shell access.