Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
verify-ghsa-c4j6-fc7j-m34r — OOB verifier for GHSA-c4j6-fc7j-m34r / CVE-2026-44578 (Next.js WebSocket-upgrade SSRF) | Kitploit
Tools/GitHubGitHub/panchocosil/verify-ghsa-c4j6-fc7j-m34r
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration TestingRed Teaming
GitHubpanchocosil/verify-ghsa-c4j6-fc7j-m34r

verify-ghsa-c4j6-fc7j-m34r

OOB verifier for GHSA-c4j6-fc7j-m34r / CVE-2026-44578 (Next.js WebSocket-upgrade SSRF)

View Repository
74 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

verify-ghsa-c4j6-fc7j-m34r

In-band verifier for GHSA-c4j6-fc7j-m34r / CVE-2026-44578 — Server-Side Request Forgery in Next.js via WebSocket upgrade requests.

⚠️ For authorized security testing only. You are responsible for ensuring you have permission to test every target you pass to this script.

The vulnerability

FieldValue
CVECVE-2026-44578
GHSAGHSA-c4j6-fc7j-m34r
CWECWE-918 (SSRF)
CVSS v3.18.6 (High) — AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
Affectednext >=13.4.13 <15.5.16, >=16.0.0 <16.2.5
Patched15.5.16, 16.2.5
Fix commitc4f69086
Not affectedVercel-hosted; output: "export"; deployments behind a reverse proxy that does not forward Upgrade

How the bug actually works (verified empirically against 15.5.15 vs 15.5.16)

  1. An attacker opens a TCP connection to a self-hosted Next.js process and sends an HTTP/1.1 WebSocket upgrade whose request-URI is an absolute URL:

    GET http://anything/<path> HTTP/1.1
    Host: <target>
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    
  2. In resolveRoutes, the URL contains // (every absolute URI does), which matches the "normalize repeated slashes" branch. That branch returns early with { finished: true, statusCode: 308, parsedUrl: <mangled> }. The normalizer collapses http://host/path into http:/host/path (one slash).

  3. In router-server.ts, the pre-patch upgrade handler ignored finished/statusCode and only checked parsedUrl.protocol. Since the protocol survives normalization, it called proxyRequest(...).

  4. proxyRequest runs url.format(parsedUrl) on the mangled URL, getting http:/host:port/path. http-proxy parses that target, finds no host (url.parse('http:/...').host === null), and falls back to its default destination: localhost:80 (or localhost:443 for https).

  5. So in practice the SSRF lets you make Next open a WebSocket upgrade to the Next.js host's own localhost:80 / localhost:443 with an attacker-controlled path.

The fix (commit c4f69086) made the upgrade handler check finished && !statusCode before proxying. The 308-normalization case now fails the !statusCode check and the socket is closed instead.

Why a callback-based OOB verifier won't work for this CVE

The proxy never reaches an external host. If you set up an interactsh / Burp Collaborator / webhook canary and expect the Next process to phone home, it will not — the connection goes to localhost on the target machine. This verifier therefore uses an in-band signal read from the upgrade socket: a vulnerable server returns a recognizable error body, a patched server returns nothing.

Practical impact

The SSRF target is restricted but still meaningful in real deployments:

  • Sidecar containers / reverse proxies / admin panels co-located on the same host that bind 127.0.0.1:80 or :443 and trust localhost-originated requests.
  • Docker socket exposed over HTTP on 127.0.0.1:80 (uncommon but seen).
  • Path traversal into any localhost HTTP service with attacker-controlled URI path and WebSocket upgrade semantics.

AWS / GCP / Azure metadata endpoints (169.254.169.254) are not directly reachable because the bug pins the destination to localhost.

Detection model

For each target, the script opens a raw TCP (or TLS) socket, sends the crafted upgrade, reads the response, and produces two signals:

  • verdict — whether the bug is present.
  • impact_confirmed — whether the SSRF actually exfiltrated data (i.e. a co-located service on localhost:80/443 of the target answered and we got its response back).
ResponseVerdictimpact_confirmed
Contains Internal Server Errorvulnerablefalse — bug proven, but proxy hit nothing on localhost
Starts with HTTP/1.vulnerable_proxy_succeededtrue — real response data exfiltrated
Empty / clean closelikely_patchedfalse — also covers "not Next", "reverse proxy stripped Upgrade", "Vercel"
Identical to no-Upgrade controlfront_end_interceptsfalse — front-end proxy short-circuited both probes; SSRF never reached Next
Anything elseinconclusivefalse

When impact_confirmed is true the JSON output also includes upstream_status, upstream_server and upstream_content_type parsed from the leaked response (useful for triage / report-writing).

Front-end proxy false-positive guard

By default, every target also receives a control probe with the same absolute-URI request line but no Upgrade headers (Connection: close). If the front-end returns the same response to both probes (status line + size within tolerance), the host's own front-end is rejecting the absolute-URI request line itself — nginx 400, Apache 400, CDN edge — and the SSRF never reached Next. The verdict downgrades to front_end_intercepts and the JSON output includes front_end_status and front_end_server parsed from the proxy's response so the operator can identify what is intercepting.

This eliminates a real-world false positive observed when self-hosted Next sits behind nginx/Apache: those proxies reject the probe's GET http:///x HTTP/1.1 request line with a generic 400, which the detector previously misread as vulnerable_proxy_succeeded. Pass --no-control-probe to opt out and see the raw verdicts.

Requirements

  • Python 3.10+
  • No third-party dependencies (stdlib only)

Usage

# single target
python3 verify_ghsa_c4j6.py --target https://app.example.com

# multiple targets via flag repetition
python3 verify_ghsa_c4j6.py \
    --target https://app1.example.com \
    --target app2.example.com:3000 \
    --target 10.0.0.5:80

# from a file (one target per line; '#' for comments)
python3 verify_ghsa_c4j6.py --targets-file targets.txt

# from stdin
cat targets.txt | python3 verify_ghsa_c4j6.py

# JSON Lines output for downstream tooling
python3 verify_ghsa_c4j6.py --targets-file targets.txt --json

# Enumerate co-located services on the target's localhost:80/443 via the bug
python3 verify_ghsa_c4j6.py --target https://app.example.com --scan

# Same, with a custom path list
python3 verify_ghsa_c4j6.py --target ... --scan-paths-file my_paths.txt

Scan mode

--scan probes a built-in list of common paths (Apache/nginx status modules, health & metrics endpoints, Spring Boot Actuator, Go pprof, Docker daemon endpoints, common admin panels, leaky config files, Elasticsearch routes, etc.) through the SSRF gadget.

By default, scan mode runs one extra differential baseline probe with a random non-existent path per target. Subsequent probes are tagged DIFF only when their (status, body length) signature diverges from the baseline — uniform 404s from a "found nothing" upstream are marked noise and don't inflate the hit count. Pass --no-differential to report every probe that reached a service (legacy behavior).

Output is grouped per target:

Download Tool