CVE-2019-16920
复现D-link漏洞研究,原文链接
登录操作通过/apply_sec.cgi的URI执行,快速浏览代码可以发现apply_sec.cgi的代码位于/www/cgi/ssi二进制文件中的函数do_ssc(0x40a210)。
current_user和current_username的值取自nvram:
然后,该函数会将current_user的值和变量acStack160的值进行比较。
该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"> |
这也就提供了action的值,也就是值保存到了ENV当中acStack288变量。如果成功,那么函数返回值0。
当iVar等于0,我们就会进入if条件,它将URI的值与”/apply_sec.cgi”进行比较。如果比较成功,那么ppcVar3就会指向SSC_SEC_OBJS数组。否则它会指向SSC_OBJS数组。
现在,ppcVar3指向了SSC_SEC_OBJS数组,该数组包含了一系列的action值。如果我们输入一个不包含在内的值,那么程序就会返回LAB_0040a4b8,也就会输出错误:”No OBJS for action: <action input=””>”
可以在之前返回error.asp的那段代码中中看到发生错误的身份验证检查的位置。即使我们未经身份验证,代码流仍会执行,这意味着我们可以在SSC_SEC_OBJS数组“ /apply_sec.cgi”路径下执行任何操作。
SSC_SEC_OBJS操作数组在哪里?它在函数init_plugin()的寄存器中:
当我们转到地址**0x0051f294并将其变量转换为单字(word)类型时,我们可以看到以下数组:
在IDA中变为DoubleWord格式(不会这里的快捷键,手动实现的),IDA帮助我们识别出来了功能。
注意这里的ping_test功能,进入函数sub_41a010:
这个函数从参数ping_ipaddr中获取其值。它通过inet_aton(),inet_ntoa()函数将值进行转换,然后执行ping操作。
如果我们尝试输入任何特殊字符,例如双引号,双引号,分号等,则ping操作将失败。但是如果我们传递换行符,例如:8.8.8.8%0als,我们可以执行命令注入攻击。
测试exp:
1 | POST /apply_sec.cgi HTTP/1.1 |
我们通过使用POST方法请求“apply_sec.cgi”来操控ping_test 。然后,我们在ping_ipaddr中执行命令注入。即使返回登录页面,仍然会执行ping_test操作,ping_ipaddr的值将在路由器服务器中执行“echo 1234”命令,然后将结果发送回我们的服务器。
测试图片(空)
此时,攻击者可以检索管理员密码,或将自己的后门安装到服务器上。
另一篇文章的二进制部分分析如下:
通过对 ssi 应用程序 启动流程的分析,ssi应用程序会对 url为 /apply_sec.cgi 请求进行单独处理。
对于url为 /apply_sec.cgi 请求,根据功能的不同,为其注册功能相对应的响应函数,并在结构体struct_api_map中记录,并形成action_register_buffer 注册函数结构体。
根据分析结果,定义 struct_api_map 结构体为:
action_register_buffer全局数组如下:(这个是怎么用Ghidra搞出来的呢)
在ssi 应用程序的 main函数中调用 init_plugin函数,设置action_register_buffer 的引用
当 /apply_sec.cgi 提交请求时,程序会根据表单中的参数 action 的值,从注册函数结构体数组中查找对应的响应函数,并调用处理。
此过程在 do_ssc 函数 中处理:
在 do_ssc 函数中对 url 为 /apply_sec.cgi的请求进行处理,具体流程如下:
- current_user的值 与 user_username经过 base64_encode 处理之后的值进行比较。
查看nvram的默认配置文件 nvram.default 如下:
1 | str_current_user = (char *)nvram_get("current_user"); |
- 获取表单 action的值,验证是否为 /apply_sec.cgi 用户登录操作
- 根据action的值,从数组中查看是否有对应的注册函数,如果有并调用处理
到目前为止可以知道,/apply_sec.cgi 对应的表单中, action的值决定了ssi调用不同功能的处理函数,因此 可以通过伪造action的值发送不同的请求 ,来达到我们想要的目的。
对action=”ping_test” 响应函数 FUN_0041a060_ping_test 进行分析(含有命令注入漏洞)
i. 在action_register_buffer结构体数组中,有这么一项结构体:
ii. FUN_0041a060_ping_test 命令执行流程如下:
程序对于 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 |
查看 ./www.wizard_detect_wan.asp可知是我们想要的表单
1 | <form id="form2" name="form2" method="post" action="apply_sec.cgi"> |
执行远程注入exp:
通过远程ping 自己开启的8889端口,并执行命令: echo EversecLab,输出EversecLab,ping_test请求利用:
1 | POST /apply_sec.cgi HTTP/1.1 |
输出效果:
上文提到的通过默认用户名和密码登录设备
登录前
点击login, 登录后效果: