FlawOpen

flawopen.com/Command Injection/Node child_process.exec

Risque de command injection avec Node.js child_process.exec()

Halaman referensi
En termes simples

Passer une chaîne de commande à exec(), c’est remettre une phrase à un typographe et le laisser décider, selon la ponctuation, où les instructions se séparent. Si une entrée non fiable contient une syntaxe de shell, celui-ci peut la traiter comme des instructions. execFile() transmet un exécutable fixe et des arguments séparés, sans analyse par un shell par défaut.

VULNÉRABLE
const { exec } = require('node:child_process');

function inspectFile(filename) {
  return exec(`file ${filename}`);
}
CORRIGÉ
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
  });
}

Pourquoi execFile() réduit le risque de command injection

exec() démarre un shell et lui transmet une chaîne de commande. Si le code y interpole du texte contrôlé par un attaquant, le shell analyse les métacaractères comme de la syntaxe : c’est le chemin de command injection décrit par CWE-78. execFile() lance directement un exécutable précis avec un tableau d’arguments et ne démarre pas de shell par défaut ; les séparateurs ne sont donc pas réinterprétés par un shell. L’exemple associe aussi l’identifiant reçu à un chemin fixe. Cela ne rend pas tout programme cible sûr : validez ses arguments, utilisez un terminateur comme -- s’il est pris en charge et n’activez jamais shell: true pour des valeurs non fiables.

Repérer les appels exec() à risque

Recherchez exec() et execSync(), puis remontez chaque chaîne de commande jusqu’aux paramètres de requête, champs de base de données, noms de fichiers et variables d’environnement. Notez l’exécutable et les limites entre arguments. Préférez une API directe ; sinon, utilisez execFile() avec un exécutable fixe, des arguments validés, une durée limitée et une sortie plafonnée.

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

Variantes synchrones

Qu’en est-il de execSync() ?

execSync() utilise la même chaîne exécutée par un shell et présente le même risque d’injection, tout en bloquant la boucle événementielle. execFileSync() évite l’analyse par un shell par défaut, mais l’exécutable et les arguments doivent toujours être validés.

Références

À voir aussi Injection de commandes système (CWE-78) : fonctionnement et prévention dans tous les langages — flawopen.com Node.js exec() vs execFile() : différences et sécurité Java Runtime.exec() et Command Injection : que change-t-il ?