Talos Vulnerability Report

TALOS-2025-2165

Tenda AC6 V5.0 HTTP authentication bypass vulnerability

August 20, 2025
CVE Number

CVE-2025-27129

SUMMARY

An authentication bypass vulnerability exists in the HTTP authentication functionality of Tenda AC6 V5.0 V02.03.01.110. A specially crafted HTTP request can lead to arbitrary code execution. An attacker can send packets 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

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

CWE

CWE-288 - Authentication Bypass Using an Alternate Path or Channel

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.

As common to most home routers, the main management interface for the Tenda AC6 AC1200 is via the web portal on TCP port 80. In the setup httpd_mainloop function, a series of callbacks is registered to a set of URLs, and these callbacks are accessed in series if they match on the URL of a given HTTP request:

int32_t httpd_mainloop()
// [...] 
800b2724        setup_web_server(port: 80, retries: 5)
800b2744        websUrlHandlerDefine(urlPrefix: &NULLSTR, webDir: nullptr, arg: 0, handler: http_auth_control_function, flags: 1)  // [1]
800b2764        websUrlHandlerDefine(urlPrefix: "/cgi-bin", webDir: nullptr, arg: 0, handler: cgi_bin_http_handler, flags: 0)      // [2]
800b2780        int32_t $s1 = 0
800b277c        do_log3(fmt: "-----------%s------------%d-----…", "initWebs", 0x10e)
800b279c        websUrlHandlerDefine(urlPrefix: "/goform", webDir: nullptr, arg: 0, handler: websFormHandler, flags: 0)            // [3]
800b27bc        websUrlHandlerDefine(urlPrefix: &NULLSTR, webDir: nullptr, arg: 0, handler: websHandler, flags: 2)
800b27c4        setup_goform_callbacks()           // [4]
800b27cc        setup_ate_cb()
800b27ec        websUrlHandlerDefine(urlPrefix: &slash, webDir: nullptr, arg: 0, handler: minimal_resp_cb, flags: 0)

To start, the authentication function is registered at [1], which verifies that either a correct password POST parameter is given, or that the ecos_pw cookie matches to a logged in admin. Since this registration occurs with a NULL urlPrefix, it is hit on every HTTP request. Next up at [2], we can see a callback registered for the /cgi-bin directory which will only hit if our HTTP request’s URL starts with /cgi-bin. For the current vulnerability, we only particularly care about the authentication callback at [1], the /goform callback at [3], and then the particular functions that handle each particular goform which are registered within [4]. Let us first look briefly at the http_auth_control_function:

800c1814    int32_t http_auth_control_function(webs_t wp, char* uri)
// [...]
800c1968        if (strnstr(s1: uri, needle: "/public", len: 7) != 0 && 
                    wp->host != 0 &&          // [5]
                    strnstr(s1: uri, needle: "/favicon.ico", len: 0xc) != 0 && 
                    strnstr(s1: uri, needle: "/goform/getproductInfo", len: 0x16) != 0) { 
                }
800c25ac        return 0

Our requested URL is pointed to by the uri parameter, and within the line at [5], we clearly see a curious conditional. Assuming that the passed in webs_t wp structure contains a NULL host, we end up completely bypassing the authentication process.
It’s unknown why this conditional exists, but we need to understand the webs_t structure and how its fields are populated in order to actually utilize this check. Without dumping the lengthy webs_t struct itself, it suffices to say that every HTTP request is allocated a struct to keep track of all of the fields - the URL, the POST params, the HTTP headers, etc. By default, some of these fields are set by the router itself, such as the IP address of the request, and assuming that we do not include a “Host:” HTTP header field in our request, wp->host is normally populated with the IP address that the HTTP request originates from. However, due to how the parsing of the HTTP headers works, if we manually include an empty HTTP Host header like so: “Host: \r\n” , our resultant webs_t structure will have its wp->host field overwritten with a NULL entry. Thus, we can hit the conditional at [5] and bypass authentication for all URLs and have full unauthenticated admin access, allowing us to do anything a normal admin could, including flashing unsigned firmware, resulting in arbitrary code execution.

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.