CVE-2025-32010
A stack-based buffer overflow vulnerability exists in the Cloud API functionality of Tenda AC6 V5.0 V02.03.01.110. A specially crafted HTTP response can lead to arbitrary code execution. An attacker can send an HTTP response to trigger this vulnerability.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Tenda AC6 V5.0 V02.03.01.110
AC6 V5.0 - https://www.tendacn.com/product/ac6v5.html
8.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-121 - Stack-based Buffer Overflow
The Tenda AC1200 AC6 is an IPv6 smart wifi router that supports multiple configuration types for home connectivity options. Extremely popular and affordable in online sellers, the Tenda AC1200 AC6 sees large usage in the home-networking space.
Whenever any device is connected to the Tenda AC6 AC1200, the device will attempt to send an HTTP request to api.cloud.tenda.com.cn
that looks as such:
POST /route/mac/v1 HTTP/1.0\r\n
Connection: Keep-Alive\r\n
Accept: */*\r\n
User-Agent: Mozilla/5.0 (compatible; MSIE 5.01; Windows NT 5.0)\r\n
Content-Length: 31\r\n
Host: api.cloud.tenda.com.cn\r\n\r\n
{"mac":["aa-bb-cc-dd-ee-ff"]}\r\n'
Where the mac address in the POST data JSON will match the mac address of the newly connected device. The router continually makes these requests until a response is gotten from api.cloud.tenda.com.cn
that looks approximately like such:
\r\n\r\n
{"result": ["assortedvalue"] }
It is unknown what the “assortedvalue” string is supposed to be or what purpose this request and response actually serve, as api.cloud.tenda.com.cn
never actually responds to these requests by default (it is theorized that a response would be sent if the mac address is registered to a phone with the Tenda app that’s configured for cloud management). Regardless, even laptops connecting to the router will also cause these requests to be repeatedly sent to api.cloud.tenda.com.cn
, so the vulnerability is always present as long as there’s a device connected to the router. Looking within the code that actually sends this request and parses the response, we see the following approximate disassembled code:
80021c74 char* $v0_4 = cj_get(calloc_1)
80021cac sprintf(dst: &http_msg, fmt: "POST %s HTTP/1.0\r\nConnection: …", "/route/mac/v1", strlen($v0_4) + 2, "api.cloud.tenda.com.cn", $v0_4, var_1450, var_144c) // [1]
80021cb4 char* $a0_8
// [...]
80021d90 int32_t $s4_1 = 1 << (sockfd & 0x1f)
80021d94 void* $s3_4 = &read_buf[sockfd u>> 5 << 2]
80021db4 *($s3_4 + 0x1000) |= $s4_1
80021dbc var_44 = 0x4e20
80021dc4 int32_t $v0_7 = sock_select(sockfd + 1, nullptr, &mac_addr_string, nullptr, &var_48) // [2]
80021dcc char* rbuf
80021dcc
80021dcc if ($v0_7 != 0xffffffff)
80021e04 if ($v0_7 != 0)
80021e50 if (($s4_1 & *($s3_4 + 0x1000)) != 0)
80021e74 if (send(sockfd, &http_msg, strlen(&http_msg), 0) != 0xffffffff) // [3]
80021ee0 rbuf = &read_buf
The request is generated at [1], the connected socket is checked to see if it’s writable at [2], and then the request is actually sent at [3]. Further down in the function:
80021eec if (read_input_by_cloud(sockfd, dst: rbuf, len: 0x800) s> 0) // [4]
// [...]
80021f1c
80021f28 mac_addr_string[0].d = 0
80021f30 mac_addr_string[4].w = 0
80021f34 char* POST_DATA = strstr(&read_buf, "\r\n\r\n") // [5]
80021f34
80021f3c if (POST_DATA != 0)
80021f44 struct parsed_json* inp = json_parsing(POST_DATA) // [6]
80021f44
80021f4c if (inp != 0)
80021f5c void** json_result = get_json_variable(inp, "result") // [7]
80021f5c
80021f64 if (json_result != 0)
80021f6c int32_t result_len = json_get_list_len_or_dict_len(jsonmaybe: json_result)
80021f6c
80021f84 for (int32_t i_1 = 0; i_1 s< result_len; i_1 += 1)
80021f8c struct json_item_0xc* cur_json_item = get_item_by_listindex(json_result, i_1)
80021f8c
80021f98 if (cur_json_item->var_value != 0)
80021fa0 struct json_item_0xc* jsonitem = malloc(0xc) // [8]
80021fa0
80021fa8 if (jsonitem == 0)
80021fa8 goto label_80022058
80021fa8
80021fb4 jsonitem->mac = 0
80021fbc jsonitem->str_malloc = nullptr
80021fc4 jsonitem->list_ptr = nullptr
80021fc0 mac_to_bytes(mac: out_, output: &mac_addr_string)
80021fd4 jsonitem->mac.b = mac_addr_string[0]
80021fdc jsonitem->mac:1.b = mac_addr_string[1]
80021fe4 jsonitem->mac:2.b = mac_addr_string[2]
80021fe8 char* var_value = cur_json_item->var_value
80021ff4 void* item_value_copy
80021ff4 char* inp_1
80021ff4
80021ff4 if (strcmp(var_value, "Null") != 0)
8002201c item_value_copy = malloc(strlen(var_value) + 1) // [9]
80022024 inp_1 = var_value
80022028 jsonitem->str_malloc = item_value_copy
80021ff4 else
80021ffc item_value_copy = malloc(6)
80022008 jsonitem->str_malloc = item_value_copy
80022010 inp_1 = "other"
80022010
8002202c strcpy(dst: item_value_copy, inp: inp_1) // [10]
80022038 char** cloud_list_1 = cloud_list
8002203c cloud_list = jsonitem
80022040 jsonitem->list_ptr = cloud_list_1
80022040
80022044 out_ = *(out_ + 0x14)
80022044
80022050 parsed_json_dtor(inp)
The response from the cloud is read in as a 0x800 len buffer at [4], and then immediately we search for the POST data of the response at [6]. This POST data is parsed as a json, and assuming that succeeds, the "result"
value is parsed as a list. For each value in that list, a parsed json struct is allocated at [8] to hold the data which looks as such:
struct json_item {
char *mac;
char * str_malloc;
struct json_item * next;
}
The mac address of the connected device is thrown into mac
, and the value of the response from the server is thrown into the appropriately sized buffer allocated at [9] with a strcpy
at [10]. Nothing unusual or improper about this code in particular, but it’s important to understand how the data is parsed and stored. Obviously this data must be used somewhere else in the code, and so we now take a look at the function that utilizes this parsed data:
800215c8 int32_t look_through_cloud_list(void* our_mac, char* outstr)
800215dc char macbytes[0x8]
800215dc macbytes[0].d = 0
800215e4 macbytes[4].w = 0
800215ec struct json_item_0xc* cloud_list_1 = cloud_list
800215ec
800215f0 if (our_mac == 0 || outstr == 0)
800216d0 return 0
800216d0
800215f8 mac_to_bytes(mac: our_mac, output: &macbytes)
80021604 uint32_t macbyte0 = zx.d(macbytes[0])
80021608 int32_t x = 0
8002160c void** const maclist = &maclist
80021610 uint32_t macbyte1 = zx.d(macbytes[1])
80021614 uint32_t macbyte2 = zx.d(macbytes[2])
80021624 char* cloudval
80021624
80021624 while (true) // [11]
80021624 if (zx.d(*maclist) != macbyte0)
80021628 x += 1
80021624 else if (zx.d(*(maclist + 1)) != macbyte1)
80021634 x += 1
80021630 else
8002163c if (zx.d(*(maclist + 2)) == macbyte2)
80021668 cloudval = *(((sx.d(maclist[x]:3) - 1) << 2) - 0x7fcc3bc8)
80021664 break
80021664
80021640 x += 1
80021640
80021670 maclist = &maclist[1]
80021670
8002166c if (x == 1988) // [12]
80021674 while (true)
80021674 if (cloud_list_1 == 0)
800216b8 strcpy(dst: outstr, inp: "other")
800216c0 return 0
800216c0
80021680 if (zx.d(cloud_list_1->mac.b) != macbyte0)
80021684 cloud_list_1 = cloud_list_1->list_ptr
80021680 else if (zx.d(cloud_list_1->mac:1.b) != macbyte1)
80021690 cloud_list_1 = cloud_list_1->list_ptr
8002168c else
80021698 if (zx.d(cloud_list_1->mac:2.b) == macbyte2)
80021698 break
80021698
8002169c cloud_list_1 = cloud_list_1->list_ptr
8002169c
800216a0 cloudval = cloud_list_1->str_malloc
800216a0 break
800216a0
800216a4 strcpy(dst: outstr, inp: cloudval) // [13]
800216b0 return 1
In the loop at [11], the device first checks a pre-known list of mac prefixes, and assuming that none of the prefixes match we hit the conditional at [12] in which the device will iterate over our linked-list of allocated json_items
from the previous function. If any of the mac addresses match there, then the response from our cloud server is actually copied into the destination at [13]. Looking at any of the calling functions reveals that the destination of this copy all end up being to fixed-size stack buffers in the calling function, all of which seem to be sized for much smaller data than the 0x800 size buffer we can maximally store in the json_item
. As such, assuming that a malicious attacker can intercept the HTTP traffic of this network traffic, most likely through DNS poisoning, then this attacker can easily overflow the stack of any of these calling functions, resulting in arbitrary code execution.
--------- [cloud_manufacturer] get exception !! ---------
ptr:0x80570160 base 0x8056db0c size:10240
limit:0x8057030c
--------- map symbol only ---------
updated stack ptr from R29 :0x80570288
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<00000000>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>]
Exception --------------------------------------------------------------
Type: TLB miss (Load or IFetch)
Data Regs:
R0 00000000 R8 81F3A118 R16 41414141 R24 00000000
R1 80000000 R9 00000032 R17 41414141 R25 00000040
R2 00000000 R10 00000037 R18 41414141 R26 00000000
R3 00000000 R11 0000000F R19 41414141 R27 00000000
R4 00000000 R12 FFFFFFDF R20 41414141 R28 8053FFE0
R5 81F2E1D8 R13 FFFFFFFF R21 41414141 R29 80570288
R6 81F3A118 R14 AC1AA580 R22 11110016 R30 805702D0
R7 00000001 R15 00472201 R23 11110017 R31 41414141
HI 0000000F LO 0FFFFFFF SR 10000403 PC 41414141
CAUSE 00000000 PRID 00019385 BADVR 41414140
------------------------------------------------------------------------
zero at v0 v1
$00 : 00000000 80000000 00000000 00000000
a0 a1 a2 a3
$04 : 00000000 81f2e1d8 81f3a118 00000001
t0 t1 t2 t3
$08 : 81f3a118 00000032 00000037 0000000f
t4 t5 t6 t7
$12 : ffffffdf ffffffff ac1aa580 00472201
s0 s1 s2 s3
$16 : 41414141 41414141 41414141 41414141
s4 s5 s6 s7
$20 : 41414141 41414141 11110016 11110017
t8 t9 k0 k1
$24 : 00000000 00000040
gp sp fp ra
$28 : 8053ffe0 80570288 805702d0 41414141
Hi : 0000000f
Lo : 0fffffff
epc : 41414141
ra : 41414141
Status: 10000403 KERNEL EXL IE
Cause : 50000008
BadVA : 41414140
PrId : 00019385
Stack : 41414141 41414141 41414141 41414141 00000000 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
41414141 41414141 41414141 41414141 41414141 41414141 41414141 41414141
Call stack:
--------- [cloud_manufacturer] get exception !! ---------
ptr:0x80570160 base 0x8056db0c size:10240
limit:0x8057030c
--------- map symbol only ---------
updated stack ptr from R29 :0x80570018
[<8000b47c>] [<00001001>] [<80570138>] [<41414141>] [<00000000>] [<0000000d>] [<802c75fc>] [<8000b6cc>]
[<80220064>] [<00000031>] [<80570138>] [<00000000>] [<00000001>] [<80320000>] [<802c75fc>] [<802c0000>]
[<11110016>] [<11110017>] [<805702d0>] [<8000bb64>] [<11110016>] [<00019385>] [<00000030>] [<00000000>]
[<80570138>] [<00000008>] [<80570158>] [<802c59c4>] [<00000008>] [<41414141>] [<11110016>] [<8000c370>]
[<00000000>] [<0000000a>] [<00000030>] [<00000000>] [<41414141>] [<00000017>] [<11110017>] [<0000001f>]
[<41414141>] [<40240000>] [<80570138>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<80220358>]
[<81f0dde9>] [<802d1384>] [<000003fc>] [<81f2e1d8>] [<81f2e298>] [<80220544>] [<802d0000>] [<8021ee30>]
[<802d3b40>] [<80226f6c>] [<00000000>] [<80215944>] [<802ccc3a>] [<8021ee84>] [<80328ac8>] [<81f2e205>]
[<00000007>] [<80000940>] [<802d1384>] [<80042628>] [<00000000>] [<00000000>] [<00000000>] [<00000001>]
[<000003fc>] [<81f2e1d8>] [<00000000>] [<80000000>] [<00000000>] [<00000000>] [<00000000>] [<81f2e1d8>]
[<81f3a118>] [<00000001>] [<81f3a118>] [<00000032>] [<00000037>] [<0000000f>] [<ffffffdf>] [<ffffffff>]
[<ac1aa580>] [<00472201>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<11110016>] [<11110017>] [<00000000>] [<00000040>] [<00000000>] [<00000000>] [<8053ffe0>] [<80570288>]
[<805702d0>] [<41414141>] [<0000000f>] [<0fffffff>] [<00000008>] [<10000403>] [<41414141>] [<00000000>]
[<50000008>] [<41414140>] [<00019385>] [<0313ce20>] [<00000000>] [<00000006>] [<80570230>] [<802227e4>]
[<00000000>] [<800ea004>] [<80570000>] [<00000000>] [<00000000>] [<00000000>] [<81f2e1d8>] [<00000000>]
[<81f2e1d8>] [<81f2e29c>] [<00000000>] [<00000001>] [<80570298>] [<00000001>] [<11110016>] [<80222834>]
[<e327b69e>] [<00003218>] [<11110016>] [<81f2e1d8>] [<81f2e1dc>] [<8002214c>] [<000001f4>] [<8021ee84>]
[<802c4cec>] [<802c0000>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<00000000>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
Exception --------------------------------------------------------------
Type: TLB miss (Load or IFetch)
Data Regs:
R0 00000000 R8 0000001B R16 FFFFFFFF R24 00000001
R1 80000000 R9 00006300 R17 41414141 R25 000023BD
R2 00000000 R10 0000F000 R18 00000000 R26 80000500
R3 00000000 R11 00000004 R19 00000000 R27 00000001
R4 00000001 R12 00000002 R20 80000948 R28 8053FFE0
R5 FFFFF800 R13 00006200 R21 00000000 R29 80570018
R6 00000001 R14 00001800 R22 00000000 R30 41414141
R7 FFFFFF00 R15 000027BD R23 80570288 R31 00001001
HI 00002000 LO 00000000 SR 10000402 PC 8000B47C
CAUSE 00000000 PRID 00019385 BADVR 41414140
------------------------------------------------------------------------
zero at v0 v1
$00 : 00000000 80000000 00000000 00000000
a0 a1 a2 a3
$04 : 00000001 fffff800 00000001 ffffff00
t0 t1 t2 t3
$08 : 0000001b 00006300 0000f000 00000004
t4 t5 t6 t7
$12 : 00000002 00006200 00001800 000027bd
s0 s1 s2 s3
$16 : ffffffff 41414141 00000000 00000000
s4 s5 s6 s7
$20 : 80000948 00000000 00000000 80570288
t8 t9 k0 k1
$24 : 00000001 000023bd
gp sp fp ra
$28 : 8053ffe0 80570018 41414141 00001001
Hi : 00002000
Lo : 00000000
epc : 8000b47c
ra : 00001001
Status: 10000402 KERNEL EXL
Cause : c0000408
BadVA : 41414140
PrId : 00019385
Stack : 80570138 41414141 00000000 0000000d 802c75fc 8000b6cc 80220064 00000031
80570138 00000000 00000001 80320000 802c75fc 802c0000 11110016 11110017
805702d0 8000bb64 11110016 00019385 00000030 00000000 80570138 00000008
80570158 802c59c4 00000008 41414141 11110016 8000c370 00000000 0000000a
00000030 00000000 41414141 00000017 11110017 0000001f 41414141 40240000
Call stack:
SP: 0x80570018, RA Offset: 68, Ret Address: 0x8000b47c, Func Address: 0x8000b348
SP: 0x80570060, RA Offset: 44, Ret Address: 0x8000bb64, Func Address: 0x8000b860
SP: 0x80570090, RA Offset: 60, Ret Address: 0x8000c370, Func Address: 0x8000c220
SP: 0x805700d0, RA Offset: 20, Ret Address: 0x80220358, Func Address: 0x80220344
SP: 0x805700e8, RA Offset: 20, Ret Address: 0x80220544, Func Address: 0x80220524
SP: 0x80570100, RA Offset: 20, Ret Address: 0x80215944, Func Address: 0x8021592c
Code: 2631fffc 08002d14 00109023 <57000019> 9638ffff 3c04802c 03c02821 0c087a08 24845378
--------- [cloud_manufacturer] get exception !! ---------
ptr:0x80570160 base 0x8056db0c size:10240
limit:0x8057030c
--------- map symbol only ---------
[<00000037>] [<0000000f>] [<ffffffdf>] [<ffffffff>] [<ac1aa580>] [<00472201>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<11110016>] [<11110017>] [<00000000>] [<00000040>]
[<00000000>] [<00000000>] [<8053ffe0>] [<80570288>] [<805702d0>] [<41414141>] [<0000000f>] [<0fffffff>]
[<00000008>] [<10000403>] [<41414141>] [<00000000>] [<50000008>] [<41414140>] [<00019385>] [<0313ce20>]
[<00000000>] [<00000006>] [<80570230>] [<802227e4>] [<00000000>] [<800ea004>] [<80570000>] [<00000000>]
[<00000000>] [<00000000>] [<81f2e1d8>] [<00000000>] [<81f2e1d8>] [<81f2e29c>] [<00000000>] [<00000001>]
[<80570298>] [<00000001>] [<11110016>] [<80222834>] [<e327b69e>] [<00003218>] [<11110016>] [<81f2e1d8>]
[<81f2e1dc>] [<8002214c>] [<000001f4>] [<8021ee84>] [<802c4cec>] [<802c0000>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<00000000>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>] [<41414141>]
[<41414141>] [<41414141>] [<41414141>]
Thread cloud_manufacturer (tid: 11), CurPri: 9, SetPri: 9
Stack Base: 0x8056db0c, Size: 0x2800, Used: 0x1bac
2025-04-29 - Initial Vendor Contact
2025-04-30 - Vendor Disclosure
2025-05-05 - Vendor Feedback Request
2025-05-08 - Vendor Feedback Request
2025-05-12 - Vendor Feedback Request
2025-06-11 - Vendor Feedback Request
2025-07-07 - Feedback Request / Announcement Of Upcoming Release Date
2025-07-23 - Feedback Request / Announcement Of Upcoming Release Date
2025-08-19 - Announcement Of Upcoming Release Date
2025-08-20 - Public Release
Discovered by Lilith >_> of Cisco Talos.