0x00 前期
拿到测试范围清单后,首先用脚本获取了下各个子站的标题,基本都是XXX管理系统。浏览器查看后,各个子站也大同小异,纯登录系统,带验证码。这些系统基本都是jsp写的,逮到了两个没有验证码的小宝贝,一番暴力破解,小宝贝对我说:别爱我,没结果。又看到了两个站返回的Set-Cookie头:rememberMe=deleteMe; 很明显,这是Apache Shiro。打了一波exp,没有反应,放弃。搞了三天啥都没搞出来,和女朋友出去玩散散心,还是很高兴。
0x01 初显进展
测试范围内有六个门户网站,其中四个静态站(也不知道是不是伪静态,没拿下来),还有两个动态站。一个是Java写的,一个是PHP写的。出于对Java的敬畏之心,我对PHP下了手。是个伪静态站,URL是这样的:http://www.xxx.cn/index.php/lists/68.html,可是暴露的很明显。尝试还原为参数值,失败,显示下图页面:
很典型的MVC架构站,尝试指纹识别、浏览robots.txt,一无所获。看了下前台功能,除了浏览新闻就是下载文件,下载的文件名经过了MD5 hash,放弃任意文件下载。没有什么其他的功能点了,啥都不用考虑了,扫Web目录。
扫到了composer.json、composer.lock、.htaccess以及readme.md。前两个是composer包管理器的文件,.htaccess是Apache的配置文件,能够直接下载有点奇怪。重头戏就在readme.md了,查看得知此CMS名为PCCMS,基于ThinkPHP 5.1开发。百度了一下,并没有发现PCCMS的源代码,也没有相关漏洞信息,太小众了吧。以前也见过很多识别不出来的CMS,可能有的人就是喜欢用小众CMS?百度啥也没找到,谷歌也不用说了,中国人自己都用的不多,哪有外国人去关注这小CMS。
又去仔仔细细地浏览了下载下来的文件,composer.json里都是些开源组件的信息,又臭又长,又去看了下readme.md。里面给了后台管理页面截图的地址,我进不去后台,看看后台长什么样子不过分吧哈哈。复制截图地址在浏览器打开,UI挺好看的,仔细再看看,看到了YZNCMS几个字。百度了一下,找到了gitee的项目地址。好家伙,readme里提到是基于ThinkPHP 5.1开发的,其实是基于YZNCMS三次开发的。
直接去找TP 5.1的RCE payload,没有什么反应,估计是被修复了。看了下YZNCMS的目录结构,和TP没有什么不一样,通过阅读源代码确认RCE被修复了。但是我发现了一个有趣的文件——yzncms.sql,这是MySQL的备份文件。试着在网站上访问yzncms.sql,404。改名为pccms.sql,弹出了下载框,28M,有戏。
0x02 登录旁站
本来以为这单纯是建站时用于创建表结构用的,打开后发现,居然还有文章信息。看起来不再简单了,直接搜索password,在pccms_admin表找到了两个账户密码。密码都有盐,admin的密码解出来是admin,登录不上后台,意料之中。根据账户的最后登录时间戳来看,这CMS最晚的备份时间是去年7月,一个站用admin做密码,很难保证一年不被日。另一个账户的密码没解出来,思路这时候又断掉了。去看了测试范围外的一个子站,也是相同cms,ping一下IP,是个旁站。直捣黄龙,下载pccms.sql,发现两个站管理员账号密码居然一样?尝试去登录,依旧失败。去对比了两个sql文件的sha1哈希值,一模一样,操……又看了一下用户名,另一个用户名和子域名有相同之处,这个备份文件无疑是从旁站复制过来的了,可能是去年7月迁移了服务器。
admin没搞定,只能打剩下的那个账户的主意了。虽然cmd5解不开,但是我有hashcat呀。把哈希值和盐都复制出来,用hashcat配上我的辣鸡1050ti显卡,几秒钟不到居然跑出了密码,123456操……
登录网站,失败;登录旁站,成功。操,旁站在测试清单之外。去向老师傅请示了一下,老师傅说先别测。我也累了,休息了。
第二天,老师傅说,旁站也在测试范围之内。
0x03 自助提权
此时已经是项目第六天,也就是昨天。登进了旁站的后台,当前用户是编辑权限。编辑也好, 比之前的普通会员权限大多了,还能编辑文章。
我的目标只有一个,拿shell。一波操作猛如虎,附件管理、图片上传、广告位上传,都试了一遍,一共两个上传接口,CMS自带的和Ueditor的,文件后缀限制的死死的,文件名md5哈希过,看样子是没办法绕过去了。
这时,一处不应出现的逻辑漏洞出现了。在管理员管理功能点,我发现了这个
操作之后,系统提示修改成功
绝了,此功能堪称自助提权,逻辑漏洞中的黑洞。
0x04 getshell
刷新页面,加载超管的功能模块,主要增加了会员管理、角色管理、模型管理模块。三个模块没有一处上传点,这时候我在“其他”模块里找到了插件管理功能,显眼的本地安装吸引了我的注意力。
结合我对应用开发的认知,以及之前日Empire CMS、WordPress的经验,Web应用的功能基本都是由脚本实现。那么,这个插件也会是php写的,想拿到shell,需要构造一个恶意插件安装包。在gitee项目页面搜罗了一番,找到了一个手册,手册里什么都有,唯独插件开发那栏是空的。那么只剩一条路了,代码审计。看了下addons.php的代码,船头调用API到船尾,我没造过船,看不懂,放弃。其实还有一条路,黑盒测试嘛,凭感觉。
按照其他插件的目录结构,我创建了一个config.php,其内容是:
<?php
return null;
?>
又创建了一个info.php,调用phpinfo()函数,为了防止触发WAF规则,我习惯先上一段正常代码。将两个文件压缩后,直接上传,系统弹出这样的提示:
看来也就是个普通压缩包,根据gitee项目的目录结构,我确定插件解压在addons目录下,路径格式为: /addons/插件安装包名/文件名。访问了下插件目录,因为没有默认索引页,服务端响应403,表示目录存在。又访问了info.php,还是403,这就不正常了。根据之前的TP环境搭建经验,可能是.htaccess中的url重写功能影响了对shell的直接访问。那么,可以在目录下重写一个.htaccess,覆盖apache的配置。于是我写了这样一段.htaccess:
<FilesMatch "info">
SetHandler application/x-httpd-php
</Filesmatch>
这个配置文件的作用是,把文件名包含info的文件,都当作php脚本解析。重新访问info.php后,我发现配置并没有生效。因为…这个Web容器根本不是Apache!!!看了下响应头,是openresty。我对这玩意非常陌生,一直认为只有CDN服务器才会用它。
换个思路,php不能解析,是不是整个addons目录都被配置为不可访问。我又传了txt纯文本,发现可以访问到文本内容。此目录有访问权限,只是不能访问PHP。我试着将php1-php7所有的后缀都打包成一个插件安装包,然而还是403。试着转换大小写,访问pHP,还是失败。可能使无视大小写,而且php1-php7都在黑名单里吧。最后,第七次上传安装包时,我用了php12345的后缀,通过访问pHP12345,成功看到蓝色的phpinfo,解析了,shell稳了。前前后后七次,终于要拿到shell了,我又有出息了=_=。直接上冰蝎马:
给冰蝎设置好测试用的代理,连接shell。然而,冰蝎一直在转圈圈,加载信息。看了看burp的http记录,发现只发了两个包,获取了两次密钥。两次都没被拦截,冰蝎也没有发第三个包,问题可能不在服务端。排查了一下,恍然大悟。我用畸形后缀解析了shell,冰蝎不能根据扩展名识别脚本类型,手动设置为php后,成功连接。
整个过程梳理起来,发现并不是很难。不轻易放弃,关注细节,总是能挖到洞的。