FlawOpen

flawopen.com/Command Injection/Node child_process.exec

Riesgo de command injection en Node.js child_process.exec()

Página de referência
En palabras sencillas

Pasar una cadena de comando a exec() es como dar una frase a un tipógrafo y dejar que la puntuación decida dónde se separan las instrucciones. Si el texto no confiable contiene sintaxis del shell, este puede interpretarla como instrucciones. execFile() envía un programa fijo y argumentos separados, sin analizar un shell de forma predeterminada.

VULNERABLE
const { exec } = require('node:child_process');

function inspectFile(filename) {
  return exec(`file ${filename}`);
}
CORREGIDO
const { execFile } = require('node:child_process');

const allowed = new Map([
  ['report-1', '/srv/reports/1.pdf'],
  ['report-2', '/srv/reports/2.pdf']
]);

function inspectReport(reportId) {
  const path = allowed.get(reportId);
  if (!path) throw new Error('Unknown report');
  return execFile('/usr/bin/file', ['--', path], {
    shell: false,
    timeout: 3000,
    maxBuffer: 64 * 1024
  });
}

Por qué execFile() reduce el riesgo de command injection

exec() inicia un shell y le pasa una cadena de comando. Si el código interpola texto controlado por un atacante en esa cadena, el shell analiza los metacaracteres como sintaxis: esa es la ruta de command injection descrita por CWE-78. execFile() inicia un ejecutable concreto con una matriz de argumentos y, de forma predeterminada, no crea un shell; por eso, el shell no vuelve a analizar sus separadores. El ejemplo también asigna el ID recibido a una ruta fija. Esto no hace seguro cualquier programa de destino: valida los argumentos que acepta, usa un terminador como -- si es compatible y nunca habilites shell: true para valores no confiables.

Cómo encontrar llamadas inseguras a exec()

Busca exec() y execSync() y rastrea cada cadena de comando hasta parámetros de solicitudes, campos de base de datos, nombres de archivo y variables de entorno. Anota el ejecutable y los límites de cada argumento. Prefiere una API directa; si no es posible, usa execFile() con un ejecutable fijo, argumentos validados, tiempo de ejecución limitado y salida acotada.

grep -RInE 'exec(Sync)?[[:space:]]*\(' --include='*.js' --include='*.cjs' --include='*.mjs' --include='*.ts' .

Variantes síncronas

¿Y execSync()?

execSync() usa la misma cadena basada en shell y presenta el mismo riesgo de inyección; además, bloquea el event loop. execFileSync() evita el análisis del shell de forma predeterminada, pero también debes validar el ejecutable y los argumentos.

Referencias

Véase también Inyección de comandos del SO (CWE-78): cómo funciona y cómo evitarla en todos los lenguajes — flawopen.com Node.js exec() vs execFile(): diferencias y seguridad Java Runtime.exec() y Command Injection: qué cambia