信息收集
下载对应版本固件NR1800X_V9.1.0u.6279_B20210910.bin: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,
由于目标文件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 内部处理用户登录逻辑的核心代码
