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
CVE-2026-94545-nextjs-og-poc — Isolated Docker lab and non-destructive Python scanner reproducing CVE-2026-94545, the Next.js next/og ImageResponse SVG injection, with vulnerable vs patched controls and pixel-level PNG evidence. | Kitploit
Tools/GitHubGitHub/hassham1/cve-2026-94545-nextjs-og-poc
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityLabs & Practice
GitHubhassham1/cve-2026-94545-nextjs-og-poc

CVE-2026-94545-nextjs-og-poc

Isolated Docker lab and non-destructive Python scanner reproducing CVE-2026-94545, the Next.js next/og ImageResponse SVG injection, with vulnerable vs patched controls and pixel-level PNG evidence.

View Repository
1 day 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

CVE-2026-94545 - Next.js next/og ImageResponse SVG Injection (GHSA-vcvr-r3jv-pc5j)

Security-research material for reproducing and validating CVE-2026-94545, the Next.js ImageResponse SVG-serialization injection (upstream: Satori GHSA-wx4j-mvgx-mqwp), in an isolated, loopback-only lab.

  • Affected: Next.js >=16.2.0 <16.3.6 (Node.js ImageResponse only)
  • Fixed: 16.3.6 (Satori hardening, commit 26a52aff); 15.5.26 ships related hardening, 15.x is not affected by the RCE issue
  • Severity: Critical (advisory); Satori improper escaping -> attacker controls the embedded SVG document; RCE per the advisory depends on the downstream SVG parser
  • Upstream: vercel/satori GHSA-wx4j-mvgx-mqwp

Use only on systems you own or are explicitly authorized to test. The scanner is non-destructive: it renders an image and counts pixels. Nothing is written, stored, or executed on the target.

Verdict

Status: proven in the supplied lab.

Against the advisory's exact route pattern (attacker value into <svg><title>{value}</title></svg>):

root@kitploit:~
next 16.3.5 (vulnerable): injected 500x150 red rect rendered into the PNG
                          (75,000 #FF0000 pixels measured in the response)
next 16.3.6 (patched)   : value escaped, 0 red pixels
satori 0.25.0 (vuln)    : payload present raw in the serialized SVG string
satori 0.33.5 (fixed)   : payload escaped to &lt;/title&gt;

This proves the injection primitive end-to-end (serializer -> embedded SVG -> rasterized output). It does not demonstrate remote code execution: the advisory ties RCE to the downstream SVG parser, whose vulnerable details are not public at the time of writing. No file read, SSRF, or code execution is claimed.

Root cause (verified against the fix commit)

Satori serializes <svg> nodes with translateSVGNodeToSVGString() (src/handler/preprocess.ts), which interpolated into the output XML without escaping:

  1. Text nodes returned via String(node) - unescaped (now escapeXMLText).
  2. Attribute values interpolated raw inside quotes - " breaks out.
  3. Attribute names interpolated raw - space/quote injects new attributes.
  4. style values interpolated raw.
  5. The embedded-SVG data-URL encoder did not encode &, so inner XML entities survived the outer SVG parse.
  6. expand.ts passed internal style properties (prefix _) into serialized styles - now rejected.

The fix (satori@26a52aff) routes everything through a hardening buildXMLString() that escapes values and validates XML names. This lab verifies all of it behaviorally; the exact request and pixel evidence are in poc/.

Quick start

Requirements: Docker with Compose v2, Python 3.10+ on the host (for the scanner), network access to npm on first run.

root@kitploit:~
./lab verify

Builds the vulnerable app (vendored next/og from [email protected]), requires the marker rect to render in the HTTP response, then the patched app ([email protected]) and requires its absence.

Expected evidence

Vulnerable control:

root@kitploit:~
[*] #FF0000 pixels: 75000 (threshold 10000)
[!!!] INJECTION CONFIRMED: the attacker value escaped <title> and
      became a rendered SVG element in the generated image.
RESULT: VULNERABLE CONTROL CONFIRMED

Patched control:

root@kitploit:~
[*] #FF0000 pixels: 0 (threshold 10000)
[=] no injection observed
RESULT: PATCHED CONTROL CONFIRMED

Manual lab lifecycle

root@kitploit:~
./lab start vulnerable   # 16.3.5 app on http://127.0.0.1:9481/og
./lab test               # assert injection renders
./lab start patched      # 16.3.6 app
./lab test               # assert absence
./lab status
./lab reset

Run the scanner by hand (loopback default; authorized external targets need the explicit override):

root@kitploit:~
python3 poc/scan.py http://127.0.0.1:9481/og
python3 poc/scan.py https://host.example/og --param value \
  --allow-authorized-non-loopback [--insecure]

How the detection works (and why it is safe)

The payload is balanced XML carrying a 500x150 pure-red <rect>. On a vulnerable endpoint the value escapes <title> and the rect becomes a real element in the embedded SVG document; the scanner counts exact #FF0000 pixels in the returned PNG (75,000 expected; threshold 10,000). On a patched endpoint the value is escaped text and no red renders. The probe changes nothing on the target: one GET request, one image render.

Note the carrier is deliberately a <rect>, not text: text inside the embedded SVG has no loadable font in the renderer and would be invisible even when injected.

Why there is no nuclei template

The proof of this vulnerability is the pixel content of a rendered raster, and nuclei matchers cannot decode PNGs. A size-differential template was built and rejected: on a patched app the escaped payload still renders (as literal inert text), so benign-vs-payload sizes differ on patched systems too, and the separation between vulnerable and patched deltas is app-specific compression noise. Shipping a matcher that fires on patched systems is worse than shipping none. Use poc/scan.py; it evaluates the response image exactly.

Affected and tested versions

VersionAssessment
Next.js 16.3.5 (vendored @vercel/og)Injection reproduced end-to-end over HTTP
Next.js 16.3.6 (vendored @vercel/og)Patched negative control reproduced
satori 0.25.0 (npm)Raw injection in serialized SVG (source level)
satori 0.33.5 (npm, 22 Sep 2026)Escaped (source level)
Next.js 16.2.0-16.3.4Affected per advisory; not individually tested
Next.js 15.xNot affected by the RCE issue per vendor blog

Repository layout

root@kitploit:~
.
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── app/
│   └── server.mjs        # advisory route pattern over loopback HTTP
└── poc/
    └── scan.py           # non-destructive scanner (Python stdlib: urllib + zlib)

Not committed: assets/ (npm-packed next tarballs + extracted bundles), node_modules/, .lab-state.

Validation boundaries

ClaimStatus
SVG-serialization injection in satori < fixProven (source + behavior)
End-to-end rendering of injected markup via next/og 16.3.5Proven
Patched negative control (16.3.6 / satori 0.33.5)Proven
Attribute/style carriers (same root cause)Root cause confirmed in fix diff; text carrier proven
SSRF / file read via injected resource referencesNot tested
Remote code executionNot claimed (depends on undisclosed downstream parser details)

References

  • GHSA-vcvr-r3jv-pc5j (Next.js)
  • GHSA-wx4j-mvgx-mqwp (Satori)
  • Next.js security update, 22 Sep 2026
  • Satori fix commit 26a52aff
  • Netlify customer advisory (CVE-2026-94545)

Safety boundary

Run this repository only on systems you own or are explicitly authorized to test. The lab driver refuses variants other than vulnerable/patched, the scanner refuses non-loopback targets unless --allow-authorized-non-loopback is supplied, and the Compose port is bound to 127.0.0.1.

See SECURITY.md for disclosure handling. This repository does not include production targets, customer data, access tokens, or evidence from real systems.

License

MIT - see LICENSE.

Download Tool