README.md
Rendering markdown...
# CVE-2026-46716 Nuclei Detection Template Guide
---
## 1. Overview
This document describes the design and test results of the nuclei template for CVE-2026-46716
(Nezha Monitoring CronTrigger delivery authorization bypass → Cross-Tenant RCE).
**Key discovery**: both vulnerable and patched versions return HTTP 200 for `POST /api/v1/cron`
with `servers:[], cover:1` from a member account — the API acceptance behavior is identical.
The patch only adds ownership validation inside CronTrigger at execution time. Therefore,
version-based detection via `GET /api/v1/setting` is used as the distinguishing signal.
---
## 2. Detection Approach
| Item | Details |
|------|---------|
| Detection type | **Version-based** — compares installed version against vulnerable range |
| Required credentials | Admin (version field in `/api/v1/setting` is admin-only) |
| Vulnerable indicator | `"version"` matches 1.4.x – 1.14.14 |
| Patched indicator | `"version"` is >= 1.14.15 or absent (e.g., `"debug"` for source builds) |
| Why not behavior-based | Both versions return HTTP 200 for member cron creation; actual execution difference requires connected agents |
---
## 3. Template Architecture
```
Step 1 POST /api/v1/login (admin credentials)
→ Authenticate and extract JWT token
→ Internal matcher: body contains "data" and "token"
→ If match fails, step 2 is skipped (flow: http(1) && http(2))
Step 2 GET /api/v1/setting
→ Extract "version" field (only returned for admin-role users)
→ Regex match against vulnerable version range: 1.4.x – 1.14.14
→ Match → FINDING reported
→ No version match (>= 1.14.15 or "debug") → no finding
```
### Version Range Regex
```yaml
regex:
- '"version":"1\.[4-9]\.[0-9]+"' # 1.4.x – 1.9.x
- '"version":"1\.1[0-3]\.[0-9]+"' # 1.10.x – 1.13.x
- '"version":"1\.14\.([0-9]|1[0-4])"' # 1.14.0 – 1.14.14
```
---
## 4. Usage
```bash
# Scan vulnerable instance (admin credentials required)
nuclei -duc -u http://localhost:8008 \
-t nuclei/CVE-2026-46716.yaml \
-var username=admin -var password=admin
# Scan patched instance (should show 0 findings)
nuclei -duc -u http://localhost:8009 \
-t nuclei/CVE-2026-46716.yaml \
-var username=admin -var password=admin
```
---
## 5. Test Results
### Template Validation
```
nuclei -duc -validate -t nuclei/CVE-2026-46716.yaml
[INF] All templates validated successfully
```
### True-Positive (vulnerable v1.14.14)
```
nuclei -duc -u http://127.0.0.1:8008 \
-t nuclei/CVE-2026-46716.yaml \
-var username=admin -var password=admin
[CVE-2026-46716] [http] [critical] http://127.0.0.1:8008
[INF] Scan completed ... 1 matches found.
```
GET /api/v1/setting returns `"version":"1.14.14"` — matches the vulnerable range regex.
### False-Positive Validation (patched source build)
```
nuclei -duc -u http://127.0.0.1:8009 \
-t nuclei/CVE-2026-46716.yaml \
-var username=admin -var password=admin
[INF] Scan completed ... 0 matches found.
```
GET /api/v1/setting returns `"version":"debug"` for source builds — does not match the
version regex, so no finding is produced. Production patched deployments (>= v1.14.15)
also produce no finding.
---
## 6. Vulnerability Background
The vulnerability has two components:
**Component 1 — CheckPermission accepts empty server list without validation (present in both versions):**
`CheckPermission` iterates the provided server ID list to verify ownership. When `servers:[]`
is passed, the loop body never executes — no ownership check occurs and the function returns
`true`, allowing any authenticated user to create a cron with `cover:1` (all servers).
**Component 2 — CronTrigger delivery bypass (fixed in patch):**
Before patch: `CronTrigger` iterates all connected agents in `ServerShared` without
verifying that each agent belongs to the cron creator. With `cover:1`, every connected
agent receives the command — cross-tenant RCE.
After patch: `cronCanSendToServer()` checks `cr.UserID == server.UserID || userIsAdmin(cr.UserID)`
before each dispatch, restricting delivery to owned servers.
---
## 7. Important Notes
- Admin credentials are required for detection because `"version"` in `GET /api/v1/setting`
is only returned for RoleAdmin users.
- The template does not create any cron jobs — it is read-only (login + version check).
- Always obtain proper authorization before scanning production systems.
---