flawopen.com/Command Injection/exec() と execFile()
Node.jsのexec()とexecFile()は何が違いますか?
exec()は文字列を受け取り、shell経由で実行します。パイプやワイルドカードを使えますが、信頼できない入力のメタ文字によってコマンドが変わる可能性もあります。execFile()は既定でshellを起動せず、指定したプログラムを引数配列で直接実行します。shellによる解析は防げますが、すべてのリスクがなくなるわけではありません。対象プログラムが引数をオプションとして解釈したり、想定外の値に対して処理を行ったりする可能性があります。
比較
exec(`ls ${dir} | grep .txt`,
(err, out) => {...})
// パイプは使えますが、メタ文字も解釈されます。
execFile('ls', [dir],
(err, out) => {...})
// shellなし。lsがdir引数をどう解釈するか検証します。
// break out of its own argument
shellの機能が本当に必要な場合
コマンドにパイプ、glob展開、リダイレクトが本当に必要なら、execFile()ではその機能を直接表現できません。Node.jsで各段階を個別に起動し、streamを接続してパイプラインを組み立てる方法を優先してください。exec()が避けられない場合は、信頼できない入力をコマンド文字列に含めないでください。shellを使わない引数配列でも、引数インジェクションは防げません。対象プログラムに応じて値を検証し、対応していれば--を使い、許可するコマンドとオプションを限定します。shell: trueは通常shellを起動しないAPIでもshellを有効にします。その場合、未検証の入力を渡してはいけません。
よくある質問
Windows のバッチファイルでは境界が変わる
Windows では .bat と .cmd ファイルを execFile() から直接起動できません。Node のドキュメントでは、exec()、cmd.exe、または shell を有効にした spawn() を使う方法が示されていますが、いずれもコマンドインタープリターの解析が再び入ります。ネイティブ実行ファイルか直接 API を優先してください。バッチファイルを呼び出す必要がある場合は、スクリプトのパスと引数を固定するか、そのスクリプト固有の契約に沿って検証してください。Node では shell: true と引数配列の併用も非推奨です(v22.15.0 および v23.11.0 以降)。
spawn()も既定ではshellを使わず、引数配列を受け取ります。実用上の主な違いは出力の扱いです。spawn()はstreamで出力し、execFile()は出力をバッファに保存します。どちらも対象プログラムが引数をオプションとして解釈することは防げないため、プログラムに応じて引数を検証してください。shell: trueを指定するとshellによる解析が再び有効になります。