Talos Vulnerability Report

TALOS-2026-2428

Totolink X2000R boa formWizard newPass OS command injection vulnerability

October 20, 2026

CVE Number

CVE-2026-9814

Summary

An OS command injection vulnerability exists in the boa formWizard newPass 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/formWizard page, which leads us into the formWizard() function:

0044e3dc    size_t formWizard(struct client* cli)
// [...] 
0044e474        char* opmode_wizard = get_from_postbody(cli, "opmode_wizard", &cgi_var_pref)  // [1]
0044e484        int32_t* $a1_2 = &var_12c
0044e480        int32_t MIB_DO_Mcli
0044e480        
0044e480        if (sx.d(*opmode_wizard) != 0) ...                                    // [2]
0044e480        
0044e5cc        int32_t $a2 = 1
0044e5cc        
0044e5c8        // useless, just need wispWanId post param
0044e5c8        if (handle_wispWanId(cli, $a1_2) s>= 0)                                // [3]
0044e5d4            int32_t $v0_3
0044e5d4            $v0_3, $a2 = get_ntp_servernames(cli, &var_12c, 1)                 // [4]
0044e5d4            
0044e5dc            if ($v0_3 s>= 0)
0044e5e4                set_timeZone()
0044e5f8                char* $v0_4 = get_from_postbody(cli, "newPass", &cgi_var_pref) // [5]
0044e60c                char* $a1_15 = &var_12c
0044e60c                
0044e608                if (sx.d(*$v0_4) != 0)
0044e614                    apmib_set(MIB_PASSWORD, $v0_4)
0044e628                    MIB_DO_Mcli = 0
0044e624                    apmib_get(MIB_DO_Mcli, out: &MIB_DO_Mcli)   // [6]
0044e638                    $a1_15 = &var_12c
0044e638                    
0044e634                    if (MIB_DO_Mcli == 1)
0044e654                        sprintf(dst: &var_190, fmt: "%s %d %s &", "Mcli", 5, $v0_4)  // [7]
0044e65c                        system(string: &var_190)                                     // [8]
0044e664                        $a1_15 = &var_12c

To reach our desired vulnerable code path we first have to make sure to have an opmode_wizard HTTP POST parameter [1] that is equal to 0x0, such that we skip the large branch at [2]. The handle_wispWanId() function at [3] can likewise be easily be passed by if we set our wispWanId HTTP POST parameter to 0x1. The get_ntp_servernames function [4] can also be ignored most simply if we set our timeZone parameter to something valid. We finally hit the read of our input payload at [5] inside of the newPass HTTP POST parameter, but before we can utilize it, we must note that actually hitting this vulnerability requires a specific router configuration to be set (the MIB_DO_Mcli [6] parameter, which determines if the router is in Mesh mode), but this can easily be done via the web portal. Assuming this has been done, we reach the sprintf call at [7], which builds a command that is run with system at [8], a command that includes our unsantized newPass input, resulting in arbitrary command injection.

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