CVE-2018-5767环境复现 欢迎来看我的博客原文εεε(~▽)~CVE-2018-5767环境复现 | T0_D9本文主要是根据《IoT从入门到入土》(3)--BooFuzz的简单使用以CVE-2018-5767为例 | Cyberangels Blog该篇文章进行复现具体环境大都使用的为相同版本因为此前没有尝试复现过IoT的漏洞在学习复现时遇到了很多思路问题所以在原文基础上稍微梳理了一下顺序并且加上了一些基础性的相关知识希望能帮助到和我一样刚开始学习IoT逆向的师傅本篇主要是研究如何在没有真实设备的情况下让一个固件跑起来后续的漏洞利用还在研究ing漏洞简介CVE-2018-5767是针对TENDA AC15路由器的远程代码执行漏洞。这个漏洞存在于路由器的httpd服务中由于服务在处理cookie中的password时未对用户输入进行合理限制直接使用sscanf函数拷贝到栈上导致栈溢出。攻击者可以利用这个漏洞在路由器上执行任意代码环境固件下载固件下载Tenda AC15 15.03.1.16_multibinwalk安装#这里安装的是2.3.4版本不确定不同版本是否会对最终复现结果造成影响 #因为该版本的binwalk使用了python的imp模块所以高于3.12版本的python环境中使用会报错建议先开启虚拟环境后再安装 git clone --depth 1 -b v2.3.4 https://github.com/ReFirmLabs/binwalk cd binwalk sudo ./deps.sh sudo python3 ./setup.py installdocker镜像拉取原文作者基于IoT-vulhub修改的项目对该路由器进行了仿真可以在没有实物的情况下复现真实环境docker pull ccr.ccs.tencentyun.com/cyberangel-public/iot-emu:tenda_ac15_cve-2018-5767boofuzz安装conda create -n boofuzz python3.10 -y //我个人习惯选用的conda虚拟环境 conda activate boofuzz pip install boofuzz0.4.1 PySocks requests其余工具qemuidapwntoolspwndbg分析固件前期信息搜集第一步自然是对bin文件使用binwalk解压binwalk -e US_AC15V1.0BR_V15.03.1.16_multi_TD01.bin该路由器文件系统位于squashfs-root中而该漏洞的利用点则在bin/httpd中什么是httpd我们的目标显然是通过攻击httpd、从而获取到设备的root权限所以第一步我们先要尝试让服务跑起来不过在此之前还要对该文件的架构等信息进行搜集确认后续调用模式file httdp readelf -h httpd checksec httpd可以确认为32位arm小端序架构且开启了NX保护不能简单的利用栈执行shellcode模拟环境使用qemu尝试启动cp $(which qemu-arm-static) ./ sudo chroot . ./qemu-arm-static ./bin/httpd发现启动的时候没有后续服务运行卡在此处那就使用ida分析该处首先直接使用字符串查找定位并跳转到此处先看到下面的while循环可能是该check函数始终不符合条件导致进程一直睡眠在该处至于其具体做了什么我们完全不用管我们现在的目标就是先让他跑起来所以我们可以直接把相关的跳转patch掉下面还有一个ConnectCfm函数需要让他进入if分支也不用管直接patch即可第一处此处是将R3寄存器与0做比较只有当R3大于0时才能通过BGT跳转到if内部可以先把mov R3,R0patch为mov R3,1第二处BNE跳转检查的是Z标志位这里同上面修改即可将patch应用后再次尝试启动服务这次可以跑起来了但是输出的ip完全不对从ida查找该条字符串的引用发现问题主要出现在这个inet_ntoa函数上但是直接进入函数内部分析函数是不可取的因为这个函数极大概率只是对传入数据进行运算这大概率不是错误ip的来源我们应该先分析的是这个传入的变量s.sa_data[2]向上查找到而a1又是该函数传入的第一个参数所以可以确定v8即ip的来源是v8 inet_ntoa( inet_addr(a1) )因此接下来可以再继续向上找1A36C函数的调用者交叉引用的话会找到3个函数不方便查找真正的调用者所以使用gdb调试来寻找调用者sudo chroot . ./qemu-arm-static -g 1234 ./bin/httpd #让gdb监听1234端口 gdb-multiarch #在新终端运行gdb set architecture arm #指定arm架构 b *0x1A36C target remote :1234 #链接之前指定的端口因为程序已经在qemu里面跑起来了所以用c而非r运行到断点处然后bt查看调用栈不难发现是283E0调用了该函数跳转过去发现该函数又在28338函数内部而在此处我们找到第一个参数又是来自一个全局变量g_lan_ip那我们就要找是谁向g_lan_ip写入的数据查找交叉引用的话发现有许多条引用但发现想要watch这个变量寻找写入者时报错了所以只能翻看交叉引用后定位到该处使用gdb查看上面if判断的条件发现pwndbg已经帮我们找到了检查的参数内的数据是br0而在根据这个函数名可以猜测这个函数就是在向br0这个网桥请求ip地址什么是网桥而如果我们再步过到下一条命令可以看见他将要跳转到0x2d174这个地址这个地址也正是进入if内部的地址因为我们并不是在真实的设备上运行的所以环境缺少br0网桥因此返回了小于0的值所以如果想要让它正常运行我们可以搭建一个虚拟网桥sudo ip link add name br0 type bridge sudo ip addr add 192.168.2.3/24 dev br0 sudo ip link set br0 up ip addr show br0检查br0有ip输出后再次尝试启动httpd此时虽然还有报错但是ip已经正常显示成我们设置好的网桥ip了接下来尝试访问无法访问但是可以正常ping通这是非常关键的一步因为我们ping过去是正常的说明服务是在正常运行的且httpd有响应只是返回了404错误码说明服务器在找文件只不过文件不存在我们只需要找到httpd所需要的文档根目录然后修复即可strings -a bin/httpd | grep -i -E webroot|www|html不难看出其中最特殊的就是/webroot%s该条没有特殊前后缀后面紧跟着%s占位符方便拼接路径所以大概率该条即是文档的根目录但是webroot不能直接访问发现其实是一个指向var/webroot的符号链接但是其也只是一个空目录而其下面的webroot_ro却反而像是真正的文档根目录包含了众多文件查看/etc_ro/init.d/rcS得知设备在运行初始化时有做一个复制的动作我们补上之后再尝试访问此时我们可以算是成功完成了环境的搭建漏洞分析CVE-2018-5767的漏洞存在httpd内部厂商基于goahead框架中的websSecurityHandler魔改的R7WebsSecurityHandler函数中这里被ida识别成int类型的a1通过与goahead源码的对比可以推出其应该是webs_t类型的结构体可以在ida中导入结构体并修复怎么在ida导入结构体typedef struct { unsigned char *buf; unsigned char *servp; unsigned char *endp; unsigned char *endbuf; int buflen; int maxsize; int increment; } ringq_t; typedef struct websRec { ringq_t header; int since; int cgiVars; int cgiQuery; int timestamp; int timeout; char ipaddr[32]; char type[64]; char *dir; char *path; char *url; char *host; char *lpath; char *query; char *decodedQuery; char *authType; char *password; char *userName; char *cookie; char *userAgent; char *protocol; char *protoVersion; int sid; int listenSid; int port; int state; int flags; int code; int clen; int wid; char *cgiStdin; int docfd; int numbytes; int written; void (*writeSocket)(struct websRec *wp); } websRec; typedef websRec *webs_t;修复后漏洞就很清晰了if ( a1-cookie ) //检查是否存在cookie { v43 strstr(a1-cookie, password); //strstr在字符串中查找指定的子字符串password并返回首字母地址 if ( v43 ) sscanf(v43, %*[^]%[^;];*, v36); //sscanf按照指定格式提取字符串 //%*[^]读取并忽略*表示忽略所有直到遇到 的字符也就是跳过 password 这几个字母 //:匹配等号 //%[^;]读取直到遇到分号 ; 的内容并将其存入变量 v36 中即提取出的密码 //;*匹配分号忽略后面的所有内容 //简单来说就是把 password 后面直到 ; 之前的值提取出来赋值给 v36 else sscanf(a1-cookie, %*[^]%[^;];*, v36); }最大的致命缺陷就在于它没有指定最大的读取长度而v36又只有128字节如果构造一个恶意的cookie大于128就能造成栈溢出通过构造ret2libc即可获取root权限一些知识点什么是httpdhttpdHTTP daemonHTTP 守护进程即“在后台常驻运行、专门处理 HTTP 请求的服务器程序”。这个名字既是一类程序的通称也是几个具体软件的可执行文件名而又因为其必然暴露——要远程配置就必须开 80/8080/8000/443权限极高——很多设备的 CGI 直接以 root 运行一个命令注入就等于整机沦陷编码质量差——system()/popen()里拼接用户输入永不修复——厂商不发固件或发了用户也不升一个 CVE 能吃十年所以常为最大攻击面之一什么是rcSrc run commands S Startup也就是一个设备启动时执行的初始化脚本因为其暴露了这种众多信息比如调用方式、调试通道等所以时分析固件时最应该读的一个文件怎么在ida导入结构体然后右键Add type即可然后回到原处对需要修改的参数按y再输入webs_t即可p然后右键Add type即可然后回到原处对需要修改的参数按y再输入webs_t即可