flawopen.com/Command Injection/Node child_process.exec
Риск command injection в Node.js child_process.exec()
Передать строку команды в exec() — всё равно что дать наборщику предложение и позволить пунктуации решать, где разделяются инструкции. Если недоверенный текст содержит синтаксис shell, оболочка может воспринять его как команды. execFile() передаёт фиксированную программу и отдельные аргументы, не запуская shell по умолчанию.
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
});
}
Почему execFile() снижает риск command injection
exec() запускает shell и передаёт ему строку команды. Если код подставляет в неё текст, контролируемый атакующим, оболочка разбирает метасимволы как синтаксис — это путь command injection из CWE-78. execFile() запускает конкретный исполняемый файл с массивом аргументов и по умолчанию не создаёт shell, поэтому shell-разделители не разбираются оболочкой повторно. Пример также сопоставляет переданный ID фиксированному пути. Это не делает любую целевую программу безопасной: проверяйте принимаемые ею аргументы, используйте разделитель опций вроде --, если он поддерживается, и не включайте shell: true для недоверенных значений.
Как найти небезопасные вызовы exec()
Найдите exec() и execSync(), затем проследите каждую строку команды до параметров запросов, полей БД, имён файлов и переменных окружения. Зафиксируйте исполняемый файл и границы аргументов. Предпочитайте прямой API; иначе используйте execFile() с фиксированным исполняемым файлом, проверенными аргументами, ограничением времени и объёма вывода.
grep -RInE 'exec(Sync)?[[:space:]]*\(' --include='*.js' --include='*.cjs' --include='*.mjs' --include='*.ts' .Синхронные варианты
execSync() использует ту же строку команды для shell и имеет такой же риск инъекции, а также блокирует event loop. execFileSync() по умолчанию не запускает shell, но исполняемый файл и аргументы всё равно нужно проверять.