Talos Vulnerability Report

TALOS-2026-2392

Totolink X2000R boa formParentControl url stack-based buffer overflow vulnerability

October 20, 2026

CVE Number

CVE-2026-41222

Summary

A stack-based buffer overflow vulnerability exists in the boa formParentControl url 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-121 - Stack-based Buffer Overflow

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/formParentControl page, which leads us into the formParentControl() function:

00419abc    size_t formParentControl(struct client* arg1)
00419aec        struct client* cli = arg1
00419afc        void _days_of_week
00419afc        memcpy(dest: &_days_of_week, src: &days_of_week, n: 0x1c)
00419b2c        uint32_t _addPCRule = get_from_postbody(cli, "addPCRule", &cgi_var_pref)
00419b44        char* _delPCRule = get_from_postbody(cli, "delPCRule", &cgi_var_pref)
00419b5c        char* _modPCRule = get_from_postbody(cli, "modPCRule", &cgi_var_pref) 
00419b64        char* _addPCUrl = get_from_postbody(cli, "addPCUrl", &cgi_var_pref)   
00419b8c        char* _delPCUrl = get_from_postbody(cli, "delPCUrl", &cgi_var_pref)
00419ba4        char* _urlnum = get_from_postbody(cli, "modRuleStatus", &cgi_var_pref)
00419ba0        char* _enabled = get_from_postbody(cli, "enabled", &cgi_var_pref)

We start from the top of formParentControl if only to display the main code flow style for this function. A large amount of HTTP POST parameters are read in and then we’re taken to downstream code chunks based on the HTTP POST parameters, quick and easy. Assuming we’re adding URLs to the parental filters, we eventually are asked to provide more important HTTP POST parameters, like the following:

00419f24        int32_t urlnum = atoi(str: get_from_postbody(cli, "urlNum", &cgi_var_pref))  // [1]
00419f3c        _urlnum = urlnum
00419f38        printf("url num is %d\n", urlnum)

The urlnum variable appropriately tells the program how many URLs are being added or modified. Continuing on this path:

0041a40c        while (true)
0041a40c            if (out_1 s<= 0)
0041a420                _addPCRule = 0
0041a424                int32_t $s4_2 = 1
0041a428                _addPCUrl = "ringEnabled"
0041a42c                _delPCRule = "url%d"
0041a430                _delPCUrl = "url str val ------- %s\n"
0041a430                
0041a438                while (true)
0041a438                    if (_addPCRule s>= _urlnum)   // [2]
0041a438                        goto label_419c20
0041a438                    
0041a448                    snprintf(dst: &var_f8, 0x14, format: "url%d", $s4_2, var_468)
0041a458                    char* url = get_from_postbody(cli, &var_f8, &cgi_var_pref) // [3]
0041a468                    printf("url str val ------- %s\n", url)
0041a468                    
0041a474                    if (sx.d(*url) != 0)
0041a494                        memset(str: &_delPCRule_1, c: 0, n: 0x100)
0041a4a0                        strcpy(dest: &_delPCRule_1, src: url)   // [4]
0041a4b8                        _addPCRule += 1
0041a4b8                        
0041a4b4                        if (apmib_set(_modPCRule, value: &_delPCRule_1) == 0)
0041a4c4                            err = "Set pc url add  error!"
0041a4c0                            break
0041a474                    else
0041a480                        printf(&_addPCUrl[0xffff89e4])
0041a480                    
0041a48c                    $s4_2 += 1
0041a48c                
0041a438                goto label_419c08
0041a438            

At this point the program loops from 0x0 to our urlNum [2] parameter provided before, searching for url1, url2, … urlx in our HTTP POST parameters as well [3]. If one of these is non-zero, we hit the branch immediately following and call strcpy on our url%d parameter, copying the value to the stack at [4]. Since this code flow has no length check or sanitization at all, we’re looking at a classic stack-based buffer overflow, resulting in arbitrary code execution.

Crash Information

***********************************************************************************
***********************************************************************************
v0[S]      : 0x7fa077e8                         | s3         : 0x00000000
v1         : 0x00000001                         | s4         : 0x00000001
a0[S]      : 0x7fa077e8                         | s5         : 0x00000001
a1         : 0x00000000                         | s6[X]      : 0x004689c4
a2         : 0x00000000                         | s7[X]      : 0x004689cc
a3[S]      : 0x7fa078e8                         | t8[X]      : 0x0048c228
t0         : 0x00000000                         | t9[L]      : 0x76f3e810
t1         : 0x00000000                         | k0         : 0x0000000a
t2         : 0x00000001                         | k1         : 0x00000000
t3         : 0x41414141                         | gp[L]      : 0x76f8e400
t4         : 0x00000080                         | sp[S]      : 0x7fa07698
t5[L]      : 0x76f8e400                         | ra[X]      : 0x0041a49c
t6         : 0x00000000                         | status     : 0x0000f413
t7[H]      : 0x00490000                         | badvaddr   : 0x76f89450
s0[H]      : 0x004a1c38                         | pc[X]      : 0x0041a4a0
s1         : 0x00480000                         | fcsr       : 0x00000000
s2         : 0x0001281d                         | restart    : 0x00000000
***********************************************************************************
   0x41a494:    jal     0x47ba90
   0x41a498:    addiu   a0,sp,336
   0x41a49c:    addiu   a0,sp,336
=> 0x41a4a0:    jal     0x47bf60
   0x41a4a4:    move    a1,s8
   0x41a4a8:    addiu   a1,sp,336
   0x41a4ac:    jal     0x47b8d0
   0x41a4b0:    move    a0,s2
***********************************************************************************
#0  0x0041a4a0 in (0x400000 0x47c000 0x7c000 0x0 r-xp /bin/boa)(+0x1a4a0)
***********************************************************************************

[<_<]> x/1s $s8
0x49ab98:       'A' <repeats 200 times>...

[x.x]> p/x $sp
$3 = 0x7fa07698

[^~^]> info reg $a0
a0: 0x7fa077e8

[~.~]> c
Continuing.
[Detaching after fork from child process 26773]
[Detaching after fork from child process 26781]

Program received signal SIGBUS, Bus error.
0x42424242 in ?? ()
[?.?] uhhhh...? Unhandled signal, gg. SIGBUS

[o.O]> !ls
***********************************************************************************
***********************************************************************************
v0         : 0x00000000                         | s3         : 0x41414141
v1         : 0x00000000                         | s4         : 0x41414141
a0         : 0x00000001                         | s5         : 0x41414141
a1         : 0x00000001                         | s6         : 0x41414141
a2[L]      : 0x76f862d0                         | s7         : 0x41414141
a3         : 0x00000001                         | t8[X]      : 0x0048c2ec
t0[H]      : 0x004994c0                         | t9[L]      : 0x774578bc
t1         : 0x263d656d                         | k0         : 0x00000000
t2         : 0x4163616d                         | k1         : 0x00000000
t3         : 0x3d726464                         | gp[L]      : 0x77473020
t4         : 0x63616d26                         | sp[S]      : 0x7fa07b10
t5         : 0x6e65263d                         | ra         : 0x42424242
t6         : 0x656c6261                         | status     : 0x0000f413
t7[H]      : 0x00490000                         | badvaddr   : 0x42424242
s0         : 0x41414141                         | pc         : 0x42424242
s1         : 0x41414141                         | fcsr       : 0x00000000
s2         : 0x41414141                         | restart    : 0x00000000
***********************************************************************************
Invalid $pc @ 0x42424242
#0  0x42424242 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
***********************************************************************************

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