# Looting UniFi Controllers: Detecting and Weaponizing CVE-2026-22557

Unauthenticated path traversal in UniFi Network Application's guest portal page_error parameter reads arbitrary files, exposing backups with cleartext device SSH credentials and network secrets.

- Payout: Research
- Severity: critical
- Program: UniFi Network Application
- Write-up by: n00r3
- Disclosed: 2026-05-29
- CVE: CVE-2026-22557
- Write-up quality: 9/10
- Original: https://bishopfox.com/blog/looting-unifi-controllers-detecting-and-weaponizing-cve-2026-22557
- Library page: https://bugbountylibrary.com/r/blog%3Abishopfox-com-blog-looting-unifi-controllers-detecting-and-weaponizing-cve-2026-22557
- Tags: area:web domain:web impact:data-leak impact:privesc kind:research platform:research sev:critical tech:java technique:incomplete-fix technique:source-review vuln:info-disclosure vuln:path-traversal vuln:secrets-leak

**Target:** Guest captive portal servlet (/guest/*) on UniFi Network Application
**Root cause:** The failure-page renderer passes the attacker-controlled page_error request parameter verbatim as a relative path to the portal resource loader, which does new File(baseDir + "/" + path) with no canonicalization or containment check, allowing ../ traversal out of the portal directory.
**Steps:**
1. Trigger the error path with a GET to the WeChat sign-in handler, which requires POST: `GET /guest/s/default/wechat/sign?page_error=../system.properties`
2. The thrown RuntimeException populates the context error attribute, causing the failure-page renderer to use page_error as the resource path
3. Calibrate traversal depth (1-8) using system.properties, which answers in both classpath and filesystem branches
4. At the calibrated depth, read a disk-only file such as firmware.json to confirm the filesystem branch is active
5. Read autobackup_meta.json to obtain the auto-backup filename, then read the named .unf backup file
6. Decrypt the .unf with the static AES-CBC key/IV compiled into the application to obtain cleartext credentials
**How found:** Diffed the 10.1.85 and 10.1.89 Debian packages with a normalizing diff to mask re-randomized obfuscated identifiers; the portal request dispatcher and portal resource loader were the two classes that gained new validation logic, pointing at the bug.
**Exploitation:** Two unauthenticated requests pull a current database dump: one reads the fixed-name autobackup_meta.json index, the next reads the backup file it names. The .unf decrypts under a static key shipped in the product, yielding cleartext device SSH credentials (root/admin on all managed APs, switches, gateways), per-device inform keys, RADIUS shared secrets, VPN PSKs/WireGuard keys, WPA passphrases, SSL-inspection CA private key, and payment-gateway keys. Alternatively the live WiredTiger MongoDB files (_mdb_catalog.wt plus collection-*.wt) are readable directly, and the PKCS12 TLS keystore (default password) enables MITM of the inform channel to impersonate the controller and push config.
**Escalation:** File read escalates to full managed-hardware takeover via cleartext device SSH credentials and inform keys; VPN PSKs extend blast radius to interconnected sites; keystore enables controller impersonation and config push to devices; super_admin x_shadow sha512crypt hash allows UI login if cracked.
**Insight:** When diffing obfuscated Java builds, normalize renamed identifiers to surface only structural/string changes; the classes gaining validation logic are the patched vulnerability. Also, calibrate traversal depth with a file that answers in both classpath and filesystem branches before probing a disk-only file, or you'll mis-flag non-customized portals as exploitable.

## Proof of concept (copied exactly from the original report)

**Single GET request path traversal PoC**

```
GET /guest/s/default/wechat/sign?page_error=../system.properties
```

**curl traversal reads .version on admin and guest ports**

```
$ curl -k "https://localhost:8443/guest/s/default/wechat/sign?page_error=../../../../.version" 
HTTP/1.1 200 OK 
10.1.85.0-g4284b71a4
```

**Detection tool run showing VULNERABLE verdict**

```
$ python3 cve_2026_22557_check.py https://target:8443 
 Software version: 10.1.85 [UNPATCHED, vulnerable build] 
>> Checking whether the guest portal is reachable 
 [ OK ] Guest portal is reachable for site 'default' 
>> Testing whether the vulnerable code path responds 
 [WARN] The vulnerable code path responded (at traversal depth 6) 
>> Confirming whether disk content is readable 
 [FAIL] Internal files are readable without credentials (fetched firmware.json) 

 RESULT: VULNERABLE
```

