# !exploitable Episode One - Breaking IoT

Stack buffer overflow in Tenda AC15 httpd saveParentControlInfo via the time parameter's sscanf %[^-] into a 32-byte stack buffer, exploited via ROP to add a root backdoor user.

- Payout: Research
- Severity: high
- Program: Tenda AC15
- Credited: Doyensec
- Disclosed: 2025-02-10
- CVE: CVE-2020-13393
- Write-up quality: 8/10
- Original: https://blog.doyensec.com/2025/02/11/exploitable-iot.html
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-doyensec-com-2025-02-11-exploitable-iot-html
- Tags: domain:iot impact:privesc impact:rce kind:research platform:research sev:high tech:linux technique:chained technique:reverse-engineering technique:source-review vuln:memory-corruption vuln:rce

**Target:** httpd web server /goform/saveParentControlInfo endpoint on Tenda AC15 router firmware
**Root cause:** The time parameter is split with sscanf(time_param, "%[^-]-%s", time_from, time_to) where time_from/time_to are 32-byte stack buffers; %[^-] has no width limit and no whitespace restriction, so a long pre-hyphen string overflows time_from.
**Steps:**
1. Emulate firmware with EMUX (Tenda AC15, control panel at 127.0.0.1:20080, password ringzer0).
2. Authenticate (or use CVE-2021-44971 auth bypass via `/goform/saveParentControlInfo?img/main-logo.png`).
3. Send POST to `/goform/saveParentControlInfo` with `time=<payload>-1` and other required fields (deviceId, deviceName, enable, url_enable, urls, day, limit_type).
4. Payload: `A`*880 + p32(0xbefff510)*17 (valid stack string ptr to time_to="1" to avoid early strcpy crash) + p32(pop_r0_pc=0x4023fb80) + p32(cmd_str_addr=0xbefff8e0) + p32(system=0x4025c270) + cmd string.
5. Command: `echo 'backdoor:$1$xyz$ufCh61iwD3FifSl2zK3EI0:0:0:injected:/:/bin/sh' >> /etc/passwd`.
6. Telnet to port 23 and log in as backdoor:hunter2 for root shell.
**How found:** Advisory claimed stack overflow in urls but decompilation showed urls copied between heap buffers (heap overflow), so pivoted. Noticed sscanf "%[^-]-%s" into 32-byte stack buffers time_from/time_to; %[^-] lacks width limit and whitespace restriction, making time_from the ideal overflow target. GDB crash showed strcpy segfault from corrupted parameter pointers, solved by filling with valid stack pointer to time_to.
**Exploitation:** ASLR disabled and binary No PIE/No canary/No RELRO with NX only. Overflow time_from to overwrite saved return address; fill intervening char* parameter pointers with a valid stack pointer (time_to at 0xbefff510) so subsequent strcpy calls don't crash before return. ROP: pop {r0, pc} gadget in libc loads command string address into r0, then jumps to __libc_system, appending a root user to /etc/passwd; log in via telnetd for root shell.
**Escalation:** Chained with CVE-2021-44971 auth bypass to reach the endpoint pre-auth; used existing telnetd to convert command execution into interactive root shell.
**Insight:** When an advisory's stated buffer/parameter contradicts the decompiled code, trust the decompilation and hunt nearby parsing code; format specifiers like %[^-] (scanset) lack width limits and whitespace stops, making them better overflow primitives than %s. Fill corrupted intermediate pointers with a valid stack address (e.g. a sibling buffer holding "1") to survive pre-return strcpy calls.
