FlawOpen

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()?

execSync() использует ту же строку команды для shell и имеет такой же риск инъекции, а также блокирует event loop. execFileSync() по умолчанию не запускает shell, но исполняемый файл и аргументы всё равно нужно проверять.

Источники

См. также Внедрение команд ОС (CWE-78): как это работает и как предотвратить на любом языке — flawopen.com Node.js exec() и execFile(): различия и безопасность Java Runtime.exec() и Command Injection: в чём разница?