FlawOpen

flawopen.com/Command Injection/Node child_process.exec

Risco de command injection em Node.js child_process.exec()

Página de referência
Em linguagem simples

Passar uma string de comando a exec() é como entregar uma frase a um tipógrafo e deixar a pontuação decidir onde as instruções se dividem. Se texto não confiável contiver sintaxe do shell, o shell poderá tratá-la como instrução. execFile() envia um programa fixo e argumentos separados, sem interpretar um shell por padrão.

VULNERÁVEL
const { exec } = require('node:child_process');

function inspectFile(filename) {
  return exec(`file ${filename}`);
}
CORRIGIDO
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 que execFile() reduz o risco de command injection

exec() inicia um shell e passa a ele uma string de comando. Se o código interpolar texto controlado por um atacante nessa string, o shell analisará metacaracteres como sintaxe: esse é o caminho de command injection descrito pela CWE-78. execFile() inicia um executável específico com um array de argumentos e, por padrão, não cria um shell; assim, separadores de shell não são reanalisados pelo shell. O exemplo também associa o ID recebido a um caminho fixo. Isso não torna seguro qualquer programa de destino: valide os argumentos aceitos pelo programa, use um terminador como -- quando houver suporte e nunca habilite shell: true para valores não confiáveis.

Como localizar chamadas inseguras a exec()

Busque exec() e execSync() e rastreie cada string de comando até parâmetros de requisição, campos do banco, nomes de arquivo e variáveis de ambiente. Registre o executável e os limites de cada argumento. Prefira uma API direta; caso contrário, use execFile() com executável fixo, argumentos validados, tempo de execução limitado e saída limitada.

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

Variantes síncronas

E execSync()?

execSync() usa a mesma string de comando baseada em shell e apresenta o mesmo risco de injeção, além de bloquear o event loop. execFileSync() evita a interpretação pelo shell por padrão, mas o executável e os argumentos ainda precisam ser validados.

Referências

Veja também Injeção de comandos do SO (CWE-78): como funciona e como evitar em todas as linguagens — flawopen.com Node.js exec() vs execFile(): diferenças e segurança Java Runtime.exec() e Command Injection: o que muda?