# Getting a Shell on the Tapo C260 Camera (CVE-2026-0651, CVE-2026-0652, CVE-2026-0653)

Tapo C260 camera: URL-decoded path traversal leaks files, and unvalidated config writes plant shell payload in dev_name reaching popen for guest-level RCE.

- Payout: Research
- Severity: high
- Program: TP-Link Tapo C260
- Found by (vendor credit): spaceraccoon
- Write-up by: Eugene Lim
- Disclosed: 2026-03-06
- CVE: CVE-2026-0651, CVE-2026-0652, CVE-2026-0653
- Write-up quality: 8/10
- Original: https://spaceraccoon.dev/getting-shell-tapo-c260-webcam/
- Library page: https://bugbountylibrary.com/r/blog%3Aspaceraccoon-dev-getting-shell-tapo-c260-webcam
- Tags: area:devices domain:iot impact:data-leak impact:rce kind:research platform:research sev:high tech:rest technique:chained technique:source-review vuln:command-injection vuln:path-traversal

**Target:** Tapo C260 camera firmware /bin/main HTTP server and mobile cloud API
**Root cause:** GET handler URL-decodes path then concatenates onto /www with no traversal sanitisation; separately setLedStatus body keys map directly to arbitrary config JSON paths, and dev_name is passed unsanitised into popen via wlan_operate get_oemid.
**Steps:**
1. Extract firmware, open /bin/main in Ghidra, find accept() handler and http_get_handle logging string
2. Observe FUN_000acefc is a URL decoder with no sanitisation before stat/open
3. Request `GET /stok=<TOKEN>/%2e%2e%2fetc/passwd` to read arbitrary files (guest token works)
4. Use LFD to read /etc/dsd_convert.json and observe config files changing while using the app
5. Send setLedStatus with body `{"tp_manage":{"info":{"dev_name":";ping -c1 <COLLAB>;"}}}` to write payload into dev_name
6. Trigger set_region_code via `testUsrDefAudio` with `device_info.set_region_code.region=US` to invoke popen with dev_name
**How found:** Started from logging strings and accept() symbol to map HTTP handlers; noticed FUN_000acefc was a bare URL decoder before stat, hypothesised traversal; then searched popen callers, found dev_name unvalidated in get_oemid; used LFD to watch config files change while driving the mobile app to map param_3 to dev_name and discover arbitrary config-path writes.
**Exploitation:** Authenticated guest session: use path traversal to read live config, then abuse setLedStatus key-path mass assignment to plant `;cmd;` into tp_manage/info/dev_name, then trigger set_region_code which builds `wlan_operate get_oemid <region> <dev_name>` and popen executes the injected command, yielding shell as guest with config-write privilege escalation.
**Escalation:** Chained low-severity LFD (used for recon of live config) into config-write primitive into command injection, escalating guest to code execution.
**Insight:** When input validation blocks direct injection, hunt for sinks fed by internal config values, then look for a set-command API whose JSON body keys map directly onto config file paths (mass assignment) to write those values.
