【破壳之路】破壳平台多粒度漏洞查询实践

一、前言

“破壳之路”是破壳平台推出的一个系列文章,旨在通过复现多种漏洞挖掘过程,深入讲解破壳平台的使用。作为该系列的开篇之作,本文将通过两个具体漏洞的复现,展示在面对不同规模目标时,如何巧妙运用破壳平台进行高效的漏洞挖掘。

二、细粒度查询:TP-Link SR20命令执行

2.1 漏洞介绍

该漏洞出现在TP-Link SR20在处理TDDP协议内容时,TDDP(TP-Link设备发现协议,TP-Link Device Discovery Protocol)是一种专有协议,用于在网络中发现和管理TP-Link设备。TDDP协议允许网络管理员通过网络自动发现并配置TP-Link的路由器、交换机和其他设备。其具备以下功能:

  • 发现设备:TDDP可以自动扫描网络,找到所有连接的TP-Link设备。这对于大规模网络管理非常有用,因为管理员不需要手动查找每个设备的IP地址。
  • 配置设备:通过TDDP协议,管理员可以远程配置设备的参数。这可以包括网络设置、安全设置、固件更新等。
  • 管理设备:TDDP还允许管理员监控设备的状态,如在线状态、设备负载等,以便及时进行维护和故障排除。

TDDP协议有两个版本:v1v2。其中v1不支持身份验证和对数据包载荷的加密,而v2要求身份验证和加密。所以若TP-Link SR20 运行了 V1 版本的 TDDP 协议,在无需认证的情况下,往 SR20 设备的 UDP 1040 端口发送特定数据就可以造成命令注入漏洞。其原理如下:

图一
图二
图三

结合图一可知,recvfrom函数的第一个参数param_1接受信息,然后传递到FUN_00015E74(图二),再次传递到FUN_00009944(图三)。在FUN_00009944中,通过sscanf(local_c,"%[^;];%s",local_68,local_a8) 语句对其内容以 ; 进行分割,字符串中 ; 前面的部分放入 local_68 中,; 后面的部分放入 local_a8 中,随后进入 FUN_000091dc 函数处理流程(图四)。

图四

查看变量定义的位置, local_130 是一个指针数组,execve 函数的第二个参数就是指针数组(是 execve 执行程序的命令行参数),这里 execve("/bin/sh",(char **)local_130,(char **)0x0); 这个 local_130 也就是变量 local_68 ,由 vsprintf 函数拼接而成,原始的数据为 FUN_000091dc 函数中调用 sscanf(local_c,"%[^;];%s",local_68,local_a8) 进行传递。

所以当控制 TDDP协议的版本号为 v1 ,然后输入任意命令以 ; 结尾。这里虽然无法使用命令分隔符 ; ,但是 && 和 || 并没有进行过滤,依然可以造成命令注入。

2.2 查询思路与流程

该漏洞的漏洞模式较为清晰,而且分析对象较为简单,可以直接在变量层追踪变量的数据流,这种查询方式较为底层,误报较少,所以在本文中,将这种方式称呼为细粒度查询。即其是一个典型的污点类型可检测的漏洞,只需要检测类似recvfrom这样的可以被控制的数据引入点,有没有数据流传递到类似于execve这样的危险函数点即可。在静态漏洞挖掘中,这种检测方法被称作污点分析。

简单来说,污点分析要做的就是查询可控的数据源source点和危险函数sink点之间是否存在数据流关系。为了用户可以快速进行污点分析,破壳平台提供了VQL.taintPropagation函数给用户使用,其有以下的参数:

List<Long>source

List<Long>sink

Integer flag

List<Long>should

List<Long>shouldNot

source为source点ID集合;sink为sink点ID集合;should为期望经过的点ID集合;shouldNot为排除点ID的集合;flag为污点传播的标志位,默认为空,为从source和sink中数量较少的一方开始传递 ,flag=1时为从source到sink进行有向传播,flag=2时为从sink到source进行有向传播,flag=3时进行无向污点分析。

该漏洞的漏洞模式可以用以下的语句表示:

MATCH (n) 
WHERE n.callee="recvfrom" and n.index=0 
WITH collect(id(n)) as source
MATCH (m) 
WHERE m.callee="execve" and m.index=1 
WITH source,collect(id(m)) as sink
CALL VQL.taintPropagation(source,sink) yield taintPropagationPath 
RETURN taintPropagationPath

第一步:查询recvfrom的第一个参数作为source点集合

MATCH (n) where n.callee="recvfrom" and n.index=0 
WITH collect(id(n)) as source;

第二步:查询execve的第二个参数作为sink点集合

MATCH (m) where m.callee="execve" and m.index=1 
WITH source,collect(id(m)) as sink;

第三步:使用污点查询原语查询可达路径

CALL VQL.taintPropagation(source,sink) yield taintPropagationPath 
RETURN taintPropagationPath;

最后产生的结果如图五所示,即通过污点分析最后定位了漏洞所在位置:

图五

三、粗粒度查询:MikroTik CVE-2018-14847

3.1 漏洞介绍

MikroTik作为网络基础设施供应商,RouterOS 是 MikroTik 的旗舰软件产品,在线运行RouterOS 系统的设备至少有 300 万台。

本次复现的漏洞是CVE-2018-14847的一个目录遍历漏洞,漏洞原理网络上,参考文献比较多,本文的重点需要理解这种漏洞模式,然后如何编写脚本。该漏洞的流程如图六所示:

图六
  1. 首先nv::message::get_string函数的第一个参数v73是一个可控的变量,而sub_804FCFA函数是一个赋值作用的函数,其将v73的值传递给了p_path
  2. 追寻p_path的来源,可见p_path的值来源于(string *)(v5+3)
  3. 由于v5[3]v5+3指向同一块地址,而v5[3]的值又传递给了v11
  4. v11的值最后传到了open的第一个参数,导致目录遍历漏洞形成。

3.2 攻击面分析

使用ps -a 查看routeros中运行的进程,发现许多进程都是/nova/bin目录下的二进制文件所启动的。通过逆向分析可知,用户发送的数据通过80端口或8291端口进行处理后,会通过进程间通信的方式转发给对应的nova/bin目录下的二进制文件进行处理。因此,我们可以将nova/bin目录下的二进制文件一起打包上传到破壳平台上进行分析。

   80 0         0:00 /nova/bin/sstore
   83 0         0:03 /nova/bin/loader
   85 0         0:00 /nova/bin/mproxy
   86 0         0:00 /nova/bin/log
   87 0         0:00 /nova/bin/moduler
   88 0         0:02 /nova/bin/discover
   89 0         0:00 /nova/bin/mactel
   90 0         0:00 /nova/bin/bridge2
   91 0         0:00 /nova/bin/macping
   93 0         0:12 /nova/bin/net
   94 0         0:00 /nova/bin/sys2
   95 0         0:00 /nova/bin/btest
   97 0         0:01 /pckg/ipv6/nova/bin/radvd
   98 0         0:00 /pckg/dhcp/nova/bin/dhcpclient
   99 0         0:00 {route_MAIN} /nova/bin/route
  100 0         0:00 /pckg/ups/nova/bin/ups
  102 0         0:00 /nova/bin/sermgr
  103 0         0:00 /nova/bin/diskd
  104 0         0:00 /pckg/wireless/nova/bin/wireless
  106 0         0:00 /nova/bin/quickset
  108 0         0:00 /nova/bin/watchdog
  110 0         0:01 /nova/bin/resolver
  111 0         0:00 /nova/bin/www
  112 0         0:00 /nova/bin/modprobed
  113 0         0:00 /nova/bin/keyman
  114 0         0:00 route_NETLINK
  115 0         0:00 route_POLICY
  116 0         0:00 route_BGP
  117 0         0:00 route_RIP
  118 0         0:00 route_OSPF
  119 0         0:00 route_PIMSM
  120 0         0:00 route_LDP
  121 0         0:00 route_FANTASY
  122 0         0:00 route_STATIC
  183 0         0:00 /nova/bin/user
  185 0         0:00 /nova/bin/mesh
  190 0         0:00 /pckg/security/nova/bin/sshd
  191 0         0:00 /nova/bin/cerm-worker
  201 0         0:00 /nova/bin/graphing
  203 0         0:00 /nova/bin/romon
  215 0         0:00 [kworker/u4:2-ev]
  217 0         0:00 /bin/login -z admin
  218 0         0:01 /nova/bin/console
  232 0         0:00 /rw/disk/busybox telnetd -l /rw/disk/ash -p 1270
  250 0         0:00 /rw/disk/ash
  251 0         0:00 {ps} /rw/disk/ash

3.3初步分析

在该漏洞案例中,因为分析的对象比较多,如果直接对所有分析对象使用污点传播,速度会非常慢,所以我们这里使用函数调用图(call-graph)来疑似点进行过滤。与上文介绍的细粒度查询不同的是函数的抽象程度更高,是一种更上层的查询,所以在本文中称作粗粒度查询。

函数调用图是一种图形表示方式,用于描述程序中函数或方法之间的调用关系。在函数调用图中,每个函数或方法被表示为一个节点,函数或方法的调用关系被表示为边。

既然是图,那么一定有节点和边,该图还是一张有向图,那么就存在方向。在call-graph中,节点的类型应该全是函数或方法,但是通过调用和被调用关系分为caller和callee,Caller指的是调用其他函数的函数。‌在函数调用过程中,‌caller负责执行函数调用操作,‌即将控制权转移到被调用的函数(‌callee)‌中;Callee则是指被调用的函数,‌即接收来自caller传递的参数并执行相应操作的函数。根据上面的关系caller有一条边函数调用边指向callee,代表在caller中调用了callee,在破壳中记作(caller)-[:cg]→(callee)。

如果大家希望通过自己变成来生成二进制文件的call-graph,可以借助ghidra和其提供的API,具体实现代码可参考https://github.com/HackOvert/GhidraSnippets

基础知识介绍完毕,就可以开始编写语句进行分析。

首先,需要明确该漏洞是一个目录遍历漏洞,那么其对应的危险参数是open,所以基本思想就是寻找哪些函数具有open函数的调用链。可以获得下面的语句:

MATCH p=((m:function)-[:cg*1..]->(n:function{name:"open"})),(k:file)-[:own]->(m)
WHERE not ()-[:cg*]->(m)
WITH  p as path_set,k as own_file
RETURN [p IN nodes(path_set)|p.name],own_file.name limit 1000

这条语句是获得的一条较好的cg查询实践,下面将对其分解说明:

  1. 首先我们查询call-graph,p=((m:function)-[:cg*1..]->(n:function{name:”open”}))的含义是查询哪些函数调用了函数名为open的函数,记作m;同时通过(k:file)-[:own]->(m)查询函数m归属于哪个文件,记作k;
  2. 因为函数调用图cg会查询出来很多子路径,所以通过WHERE not ()-[:cg*]->(m)限制输出的路径path,使得查询出来的函数调用链是最长的,因为m是没有前继节点的;
  3. 第三步是是将查询出来的路径p重命名为path_set,k重命名为own_file;
  4. 由于查询出来的结果全是Path和Node这种特殊结构,不方方便查看,所以通过[p IN nodes(path_set)|p.name],提取函数调用路径path_set中所有节点集合,并提取出其中所有节点的名称,即函数调用名称;最后提取own_file的name来展示其位于哪个二进制。

通过上述语句查询出来的结果有80多条,通过人工审计还有一些难度。

3.4 细化分析

通过初步分析查询出80多条结果,可以把重点放在认证前的逻辑上,routeros通过下面的set_policy函数对进程间通信的接口进行鉴权,其中第三个参数如果是0的话就说明这个接口是无需认证的,代码中的表示如图七所示:

图七

使用破壳平台对所有set_policy的第三个参数为0的调用处进行检测:

MATCH (f:file)-[:own]->(:function)<-[:use]-(:operator)-[:ast]->(n:const) 
WHERE n.callee contains "set_policy" and n.index = 2 and n.name="0" 
RETURN distinct(f.name)

在这句话中(f:file)-[:own]->(:function)<-[:use]-(:operator)-[:ast]->(n:const)是指在文件f中找到归属于这个文件的常量n,这个常量n必须是set_policy函数的第三个参数,且值为0。

如下图八所示,发现在众多进程中只有结果文件中拥有未鉴权接口:

图八

此时再针对这几个进程进行open函数的调用栈查询,并且保证open函数的参数为一个变量。查询命令如下:

MATCH (n:identifier) 
WHERE n.callee = "open" and n.index = 0 and n.function <> "open" 
WITH collect(DISTINCT n.function) as func_set
MATCH p=((m:function)-[:cg*0..]->(n:function)),(k:file)-[:own]->(n) 
WHERE n.name in func_set and not ()-[:cg]->(m) and k.name in ["console","mepty", "mproxy", "user"] 
WITH  p as path_set,k as own_file
RETURN [p IN nodes(path_set)|p.name]   

1. 首先第一行是查询open的第一个参数是变量,同时避免进入plt表,所以这个变量所在的函数不是open,并将调用open的函数记作func_set;

2. 后面两行和上述的代码初步分析的流程和思想一样,只不过,其中加入了限制,需要在[“console”,”mepty”, “mproxy”, “user”]这些文件里面。

这时函数调用栈的结构如图九所示,只剩22条,此时可以根据这个结果进行人工排除。

图九

经过人工确认,最后认证前目录穿越如下,此处open的路径没有经过检测。也即是查询到的[“FUN_0805016a”]这条函数调用栈,而这个地方就是cve-2018-14847的漏洞位置。

如果还要进行细化分析,那就需要借助污点分析的能力了,将nv::message::get_string的第一个函数作为sourceSet,将open的第一个参数作为sinkSet,查看之间是否存在数据流可达。

MATCH (n:identifier)
WHERE n.callee IN ['nv::message::get_string'] AND n.index = 0
WITH collect(id(n)) AS sourceSet
MATCH (n:identifier)
WHERE n.callee IN ['open'] AND n.index=0
WITH sourceSet, collect(id(n)) AS sinkSet
CALL VQL.taintPropagation(sourceSet, sinkSet,1) YIELD taintPropagationPath 
RETURN taintPropagationPath

运行完还剩下5条结果,如图十所示,除了已知漏洞,目标均在console里面,经过人工识别,其余四条均认证后,且后续有拼接后缀,利用困难。

图十

第一处

第二处

第三处

第四处:本文所述漏洞位置

第五处

五、总结

在实际漏洞挖掘过程中,面对不同的分析对象的时候,若需考虑时间成本,往往通过不同粒度的查询方式是一个不错的选择。

本文主要介绍了面对不同的目标时,在破壳平台上针对不同粒度的程序元素编写不同的语句,来快速地进行漏洞筛查。利用函数调用图进行粗粒度查询的优势就是在分析关系复杂的对象时能快速定位漏洞,相比于直接使用污点分析进行细粒度查询可以更换快速搜索获得结果,但劣势就显而易见,就是相较于污点分析,其精度较差,会有较多误报,需要更高的人工审计成本。

本文中提及的两个漏洞项目已在破壳平台示例项目中上线(https://poc.qianxin.com/example-project),感兴趣的同学可以注册破壳平台,进入示例项目,查看分析结果或者复制为私有项目进行破壳分析。