flawopen.com/Command Injection/Node child_process.exec
Riesgo de command injection en Node.js child_process.exec()
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.
const { exec } = require('node:child_process');
function inspectFile(filename) {
return exec(`file ${filename}`);
}
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
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.