FlawOpen

flawopen.com/Command Injection/exec() vs execFile()

exec() vs. execFile() no Node.js: qual é a diferença?

Guia de referência
Resposta curta

exec() recebe uma string e a executa em um shell: pipes e curingas funcionam, mas metacaracteres em entradas não confiáveis podem alterar o comando. execFile() inicia diretamente o programa indicado com uma lista de argumentos e, por padrão, sem shell. Isso impede o parsing pelo shell, mas não elimina todos os riscos: o programa chamado ainda pode interpretar um argumento como opção ou agir sobre um valor inesperado.

Comparação

exec() — usa shell
exec(`ls ${dir} | grep .txt`,
  (err, out) => {...})
// pipes funcionam, mas metacaracteres também.
execFile() — sem shell por padrão
execFile('ls', [dir],
  (err, out) => {...})
// metacaracteres ficam como dados; valide o significado dos argumentos de ls
// break out of its own argument

Quando você realmente precisa de recursos do shell

Se o comando realmente exige pipes, expansão de curingas ou redirecionamento, execFile() não expressa esses recursos diretamente. Prefira montar o pipeline no próprio Node.js, iniciando cada etapa separadamente e conectando os streams. Se exec() for inevitável, mantenha dados não confiáveis fora da string do comando. Lembre-se também de que argumentos sem shell não impedem argument injection: valide cada valor conforme o programa chamado, use -- quando ele oferecer esse suporte e permita apenas comandos e opções esperados. A opção shell: true habilita o shell em APIs que normalmente não o usam; nesse modo, nunca passe entrada não sanitizada.

Perguntas frequentes

Arquivos batch do Windows mudam a fronteira de segurança

No Windows, arquivos .bat e .cmd não podem ser iniciados diretamente com execFile(). O Node documenta que eles podem ser chamados por exec(), cmd.exe ou spawn() com shell habilitado; esses caminhos reintroduzem a análise pelo interpretador de comandos. Prefira um executável nativo ou uma API direta. Se for inevitável chamar um batch, mantenha fixos o caminho do script e os argumentos ou valide-os conforme o contrato exato desse script. O Node também desaconselha passar um array de argumentos com shell: true (desde v22.15.0 e v23.11.0).

spawn() é igual a execFile()?

spawn() também não usa shell por padrão e aceita uma lista de argumentos. A diferença prática é principalmente o tratamento da saída: spawn() transmite streams, enquanto execFile() armazena a saída em buffer. Nenhuma das duas impede que o programa interprete um argumento como opção; valide os argumentos para esse programa. Definir shell: true reintroduz o parsing do shell.

Referências

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