Talos Vulnerability Report

TALOS-2026-2432

Totolink X2000R boa formPasswordSetup OS command injection vulnerability

October 20, 2026

CVE Number

CVE-2026-10131

Summary

An OS command injection vulnerability exists in the boa formPasswordSetup functionality of X2000R (version(s): TOTOLINK-X2000R-Gh-V1.0.0-B20250421.1327). A specially crafted HTTP request can lead to arbitrary code execution. An attacker can make an authenticated HTTP request 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.

X2000R (version(s): TOTOLINK-X2000R-Gh-V1.0.0-B20250421.1327)

Product URLs

X2000R - https://www.totolink.net/home/menu/newstpl/menu_newstpl/products/id/242.html

CVSSv3 Score

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

CWE

CWE-78 - Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)

Details

The Totolink X2000R is a popular budget gigabit home router primarly found in eastern markets. Supporting IPV6, WPA3, mesh capabilities among other features, the Totolink X2000R is a solid choice for the price point.

The Totolink X2000R mainly relies on the standalone boa binary for all web administration and subsequent configuration. While normally a security risk in itself due to the age, it appears that the version running on the X2000R has been patched. Regardless, for the current set of vulnerabilities we only pay attention to the Totolink-specific issues, so we start from the point where authentication has already occurred via the /boafrm/formLogin webpage, and then we can successfully interact with another /boafrm/form* page, each of which have a corresponding callback. For the current vulnerability we examine the /boafrm/formPasswordSetup page, which leads us into the formPasswordSetup() function:

0044fd5c       int32_t formPasswordSetup(struct client* arg1)
0044fd88        char var_108[0x64]
0044fd88        memset(str: &var_108, c: 0, n: 0x64)
0044fd9c        char* value = get_from_postbody(cli: arg1, "userName", &cgi_var_pref)                       // [1]
0044fdb4        char* value_1 = get_from_postbody(cli: arg1, "newPass", &cgi_var_pref)                      // [2]
0044fdf0        char* str1 = generate_digest_HA1(get_from_postbody(cli: arg1, "Cusername", &cgi_var_pref),  // [3]
0044fdf0            get_from_postbody(cli: arg1, "Cpassword", &cgi_var_pref))                               // [4]
0044fe00        char var_40[0x28]
0044fe00        apmib_get(MIB_PASSWORD, out: &var_40)
0044fe14        int32_t out
0044fe14        char* $a1_3
0044fe14        
0044fe14        if (strcmp(str1, str2: &var_40) == 0)   // [5]
0044fee4            free(ptr: str1)
0044fef4            int32_t $v0_4 = sx.d(*value_1)
0044fef4            
0044fef0            if (sx.d(*value) == 0)
0044fef8                if ($v0_4 == 0)
0044fef8                    goto label_44ff20
0044fef8                
0044ff08                $a1_3 = "ERROR: userName is empty,Password should be empty."
0044fef0            else if ($v0_4 != 0)
0044ff20            label_44ff20:
0044ff20                
0044ff28                if (apmib_set(MIB_USERNAME, value) != 0)                            
0044ff44                    if (apmib_set(MIB_PASSWORD, value: value_1) != 0)
0044ff58                        out = 0
0044ff5c                        // has to do with mesh stuff
0044ff5c                        apmib_get(MIB_DO_Mcli, &out)                         // [6]
0044ff5c                        
0044ff6c                        if (out == 1)
0044ff90                            snprintf(dst: &var_108, 0x64, format: "%s %d %s &", "Mcli", 5, value_1) // [7]
0044ff98                            system(string: &var_108)                         // [8]

As expected of a password setup function, we must first provide the desired new userName[1] and newPass[2], while also providing the current username Cusername [3] and current password Cpassword[4]. A hash is made of the currrent username and password and if they match the MIB_PASSWORD hash in flash memory then we enter our desired code flow. The new password and new user are set, but more importantly, if the system is in mesh mode (determined by the apmib_get call at [6] which we can easily set via another form), then we hit the snprintf call at [7] which takes our new password value and creates a command with it to be run by the system call at [8]. as there is no sanitization on our password field at all, this results in a classic OS command injection vulnerability.

Timeline

2026-06-16 - vulndev@cisco.com - Initial Vendor Notification
2026-06-23 - vulndiscovery@external.cisco.com - Follow-up sent
2026-06-25 - fae_hardware@zioncom.net - Vendor Acknowledged
2026-06-25 - vulndiscovery@external.cisco.com - Resent all notifications
2026-07-16 - vulndiscovery@external.cisco.com - Follow-up sent
2026-09-15 - vulndiscovery@external.cisco.com - Follow-up sent

Credit

Lilith Wyatt of Cisco Talos