Talos Vulnerability Report

TALOS-2025-2168

Tenda AC6 V5.0 Cloud API stack-based buffer overflow vulnerability

August 20, 2025
CVE Number

CVE-2025-32010

SUMMARY

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.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

AC6 V5.0 - https://www.tendacn.com/product/ac6v5.html

CVSSv3 SCORE

8.1 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-121 - Stack-based Buffer Overflow

DETAILS

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.

Crash Information

--------- [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
TIMELINE

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

Credit

Discovered by Lilith >_> of Cisco Talos.