phpcms v9.6.1任意文件读取漏洞分析

前言

作者:血魂

声明

1.为了方便截图我改了一点点代码的缩进,并不影响代码功能
2.这个漏洞有很多代码和利用方式和上一个sql注入漏洞一样,我就偷了个懒,直接拿的上一篇漏洞分析复制,大致查了一下,没有什么冲突的地方
3.因为我个人习惯把关键代码贴到notepad++分析,为了方便阅读,提到的行数都是notepad++里的,所以和源码行数不匹配很正常
4.这篇文章是学习代码审计总结笔记,如果有错误和改正之处还望不吝赐教(这点必须放第一,强烈欢迎批评指正)
5.如转载(或其它),请标注作者,不胜感激

漏洞涉及版本

phpcms v9.6.1

漏洞涉及地址

swfupload_json函数:/phpcms/modules/attachment/attachments.php
set_cookie函数:/phpcms/libs/classes/param.class.php
sys_auth函数:/phpcms/libs/functions/global.func.php
safe_replace函数:/phpcms/libs/functions/global.func.php
wap模块接口:/phpcms/modules/wap/index.php
init函数:/phpcms/modules/content/down.php
Download函数:/phpcms/modules/content/down.php
File_down函数:/phpcms/libs/functions/global.func.php

整体思路

首先phpcms有一个file_down函数用于下载指定文件,而在download函数的最后一行调用了该函数,其中传入file_down的fileurl参数是由s变量和f变量拼接而成,而这两个变量是a_k变量代入parse_str函数解析出来的,而我们的目的是要控制s和f变量下载指定文件,而a_k变量在解析前在download函数里还进行了一次解密处理。所以我们现在需要一个地方传入s和f参数,并代入a_k变量,再进行加密处理并调用了download函数将a_k变量传递进去了,而init函数满足了要求,但是在init函数的开始时对于a_k变量进行了一个解密操作,所以我们需要传递进init函数的a_k变量是加密的,然后我们就可以利用上一个sql注入漏洞相同的套路,来得到加密的值,传递进init函数,再进入download函数,最后进入file_down函数下载我们想下载的文件,当然中间会对于一些过滤做绕过处理

漏洞分析

file_down.PNG
这是用于下载文件的函数,我们需要调用它,向它传递可控数据下载指定文件,先不管它,我们看download函数,代码如下:
download.PNG
download2.PNG
我们从下往上看,可以看到在download函数的最后一行(59行)是调用了file_down函数的,而传递进去的参数fileurl变量在第57行做了一次替换将<>替换为空,我们继续向上看,看到第32行,fileurl变量是由s和f变量拼接出来的,而s和f变量的由来我们继续向上看,看到第11行将a_k变量解析为多个变量,s和f变量就是这么来的,而在第6行对于a_k变量进行了解密处理,这里忘记上注释了,sys_auth是phpcms一个负责加解密的函数,这里需要注意的是,sys_auth函数的第三个参数是密钥,而这里用来解密的密钥是pc_auth_key变量,代码如下:
sys_auth.PNG
sys_auth2.PNG
对于这个函数我们并不需要过细的研究,知道它的功能就行了,然后,我们现在来总结一下我们得到的信息,我们需要给daownload函数传递一个加密的a_k参数,里面有我们可控的s和f变量,然后在download函数里面s和f变量拼接成了fileurl变量传递进了file_down下载文件。所以我们现在需要找一个将s和f代入到了a_k变量并用pc_auth_key密钥加密后传递进了download函数的地方,而init函数满足了条件,下面是init函数的代码:
init.PNG
init2.PNG
init3.PNG
在init函数代码的104行到113行可以看见是将s和f等加密后代入了a_k变量传递给了download函数,其中s=$s,f=$f。而我们再向上看再41行我们可以看到a_k变量又进行了解析得到的s和f,而在33行进行了解密操作,所以我们需要给init函数传递进加密并包含的s和f值的a_k,这里就利用到了上一个sql注入漏洞的方法,在swfupload_json函数,代码如下:
swfupload_json.PNG
我们主要看src参数,可以看到src参数被接收后,通过safe_replace函数进行了过滤,先不管过滤函数做了什么,在经过过滤后,被代入了arr数组,然后转换了json编码,代入了
json_str变量,最后进入了set_cookie函数(在代码11,16,30行)。然后现在我们在回过头去看看safe_replace函数做了什么。
safe_replace.PNG
safe_replace函数将一些敏感词进行了替换,但是由于只检测一次,所以很容易的就被绕过了,比如’%*27’传递进safe_replace函数后先经过对%27的过滤,因为有星号不会被检测出来,然后星号被替换为空,最后输出就成了%27。绕过safe_replace函数后我们继续往下跟。
set_cookie.PNG
到了set_cookie函数,直接看最后一行代码就好,可以看见通过sys_auth函数进行了加密
,ENCODE是加密,DECODE是解密,最后通过setcookie函数以cookie的形式输出。对于
sys_auth函数就并不需要太深入的了解了。而我们知道swfupload_json函数是attachments类的,我们来看看attachments类的构造函数来看看调用swfupload_json函数的条件。
attachments.PNG
先不管其他代码,先看最后两行代码,可以看到检查了$this->userid是否为空,为空时执行代码showmessage(L('please_login','','member')),用到了L函数和showmessage函数,主要看showmessage函数,代码如下:
showmessage.PNG
会跳转到登录界面,也就不能调用swfupload_json函数了,那么我们再回去看构造函数的
这一行代码param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'),结果一param类的get_cookie函数代码如下:
get_cookie.PNG
一眼看去,可以看到sys_auth函数,而结果二直接用sys_auth函数进行的解密,那么我们需要一个sys_auth函数能解密的cookie来绕过构造函数的限制,在刚才我们知道set_cookie函数是通过sys_auth函数进行加密的,所以反过来通过set_cookie函数设置的cookie同样能通过sys_auth函数进行解密,所以我们需要找一个通过set_cookie函数设置cookie,并将这个cookie输出了的地方,来得到我们要用的cookie。Wap模块有这么一个地方,这样的地方应该有很多。代码如下:
index.PNG
是index类的构造函数,看142,143行代码,将$this->siteid传递进了set_cookie函数,那么最后就会通过sys_auth函数加密出一个合格的cookie并以cookie的形式输出,参数名以siteid结尾,然后,我们现在就能生成用危险数据产生的合法加密输出了,然后我们现在利用的条件满足了。
再来看看过滤,来构造payload,首先在swfupload_json的第11行代码用到了safe_replace对我们传递进去的数据做了一次过滤,再来看init函数的第39行代码,又用到了safe_replace又做了一次过滤,最后在download函数的第57行代码对于<>做了替换为空的处理,当然中间还有一些对于后缀名的检查,所以我们需要绕过两侧safe_replace函数再经过一次替换,而再上面我们提到过safe_replace函数是有办法绕过的,可惜phpcms在9.6.1的补丁中并没有对此进行修复。这是第一种绕过方法。
我们还可以利用url编码不用去管safe_replace函数的过滤构造payload,比如我们要下载index.php文件payload就是index.p%253chp,因为%253c会绕过对于后缀的检查,而通过url解码%25会被解码成百分号最后<符号会在download函数被替换为空,这是第二种绕过。
至此漏洞分析完了。

Poc

poc.PNG

Poc执行结果

zxxg.PNG

添加新评论