Talos Vulnerability Report

TALOS-2026-2431

Totolink X2000R boa formTR069Config stack-based buffer overflow vulnerability

October 20, 2026

CVE Number

CVE-2026-9869

Summary

A stack-based buffer overflow vulnerability exists in the boa formTR069Config 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/formTR069Config page, which leads us into the formTR069Config() function:

0045bb44    size_t formTR069Config(struct client* arg1)
0045bbf4        apmib_get(MIB_TR069_URL, out: &var_5a4)
0045bc1c        char* fmt
0045bc1c        int32_t $a2_7
0045bc1c        char username[0x64]
0045bc1c        char* $a1_3
0045bc1c        
0045bc1c        if (apmib_set(MIB_TR069_URL, get_from_postbody(cli: arg1, "url", &cgi_var_pref)) != 0) // [1]
0045bc3c            char var_6a8[0x104]
0045bc3c            apmib_get(MIB_TR069_URL, out: &var_6a8)
0045bc5c            int32_t $s4_1 = 0 u< strcmp(str1: &var_5a4, str2: &var_6a8) ? 1 : 0
0045bc58            apmib_get(MIB_TR069_PASSWORD, out: &var_78)
0045bc6c            int32_t $s1_1 = $s4_1
0045bc68            apmib_get(MIB_TR069_USERNAME, out: &var_b8)
0045bc74            apmib_get(MIB_CON_REQ_NAME, out: &var_178)
0045bc80            apmib_get(MIB_CON_REQ_PW, out: &var_1b8)
0045bc94            char* tr069_username = get_from_postbody(cli: arg1, "username", &cgi_var_pref)

The first parameter we need to make sure we have is the url HTTP POST parameter [1], it doesn’t particularly matter what the url is, it just needs to be set. The function then pulls various configuration from flash via the calls to apmib_get which will be used later. Continuing to the parameter we most care about:

0045bd9c            char* password = get_from_postbody(cli: arg1, "password", &cgi_var_pref) // [2]
0045bda8            int32_t $v0_8 = check_for_spacews(password) // [3]
0045bdb0            int32_t ascicheck
0045bdb0            
0045bdb0            if ($v0_8 == 0)
0045bdc4                ascicheck = make_sure_lt_0x7f_ascii(password) // [4]
0045bdc4            
0045bdcc            if ($v0_8 != 0 || ascicheck == 0)
0045bdc0                $a1_9 = "Set Password error!"
0045bdbc                goto label_45bcbc
0045bdbc            
0045bde0            if (apmib_set(MIB_TR069_PASSWORD, password) == 0)
0045bdf0                $a1_3 = "Set Password error!"
0045bdec                goto label_45bc2c

At [2], the program reads in our password, and then at [3] and [4], checks to see if the input has any spaces or ASCII chars over hex value 0x7f. Assuming we pass both of these checks, we then set the MIB_TR069_PASSWORD flash value to our input. Soon after these lines of code we can quickly see an issue:

0045c3e8            apmib_get(MIB_TR069_PASSWORD, out: &var_f8) // [5]
0045c3f4            // max len 0x64
0045c3f4            apmib_get(MIB_TR069_USERNAME, out: &var_138)

For some reason, after writing our password HTTP POST value into flash, the program then decides to read it from flash onto the stack at [5]. While it’s worth noting that each specific MIB flash parameter has its own max length, the MIB_TR069_PASSWORD length is greater than 0xF8, which let’s us utilize this apmib_get to get a stack-based buffer overflow, resulting in code execution.

Crash Information

Breakpoint 3, 0x0045c3e8 in ?? ()

[x.x]> !ls
***********************************************************************************
***********************************************************************************
v0         : 0x00000000                         | s3[X]      : 0x004780c0
v1         : 0x00000000                         | s4         : 0x00000001
a0         : 0x00000001                         | s5         : 0x00000001
a1[S]      : 0x7fb1fa88                         | s6[H]      : 0x006fefbc
a2[L]      : 0x7792a2d0                         | s7         : 0x0000000f
a3         : 0x00000001                         | t8[X]      : 0x0048c2ec
t0[H]      : 0x004afce0                         | t9[L]      : 0x77dfb8bc
t1         : 0x41414141                         | k0         : 0x00000000
t2         : 0x41414141                         | k1         : 0x00000000
t3         : 0x41414141                         | gp[L]      : 0x77e17020
t4         : 0x41414141                         | sp[S]      : 0x7fb1f4c0
t5         : 0x41414141                         | ra[X]      : 0x0045c364
t6         : 0x41414141                         | status     : 0x0000f413
t7[H]      : 0x00490000                         | badvaddr[H] : 0x004980f0
s0[H]      : 0x006fe6e0                         | pc[X]      : 0x0045c3e8
s1         : 0x00000001                         | fcsr       : 0x00000000
s2         : 0x00480000                         | restart    : 0x00000000
***********************************************************************************
   0x45c3dc:    jal     0x47b8d0
   0x45c3e0:    sw      v0,804(sp)
   0x45c3e4:    addiu   a1,sp,1480
=> 0x45c3e8:    jal     0x47b7d0
   0x45c3ec:    li      a0,704
   0x45c3f0:    addiu   a1,sp,1416
   0x45c3f4:    jal     0x47b7d0
   0x45c3f8:    li      a0,703
***********************************************************************************
#0  0x0045c3e8 in (0x400000 0x47c000 0x7c000 0x0 r-xp /bin/boa)(+0x5c3e8)
***********************************************************************************

-----------------------------------------------------------------------------
[>_>]>
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()

[^_^] SIGSEGV

***********************************************************************************
***********************************************************************************
v0         : 0x00000046                         | s3         : 0x41414141
v1[H]      : 0x00490000                         | s4         : 0x41414141
a0[H]      : 0x004bdc79                         | s5         : 0x41414141
a1[S]      : 0x7fb1ece6                         | s6         : 0x41414141
a2         : 0x00000001                         | s7         : 0x0000000f
a3[S]      : 0x7fb1ece6                         | t8[X]      : 0x0048c074
t0         : 0x0000003e                         | t9[L]      : 0x778e26d0
t1         : 0x6c6d7468                         | k0         : 0x00085002
t2         : 0x00000008                         | k1         : 0x00000000
t3         : 0x00000400                         | gp[L]      : 0x77932400
t4         : 0x00000800                         | sp[S]      : 0x7fb1fb80
t5         : 0x00000807                         | ra         : 0x41414141
t6[S]      : 0x7fb1ea28                         | status     : 0x0000f413
t7[H]      : 0x00490000                         | badvaddr   : 0x41414140
s0         : 0x41414141                         | pc         : 0x41414141
s1         : 0x41414141                         | fcsr       : 0x00000000
s2         : 0x41414141                         | restart    : 0x00000000
***********************************************************************************
Invalid $pc @ 0x41414141
#0  0x41414141 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