Back to Blog
critical SEVERITY8 min read

How LDAP injection happens in C with OpenLDAP and how to fix it

A high-severity LDAP injection vulnerability was discovered in the OpenSIPS H350 module, where the `ldap_rfc4515_escape()` function failed to escape the NUL byte (`\0`) — one of the special characters defined in RFC 4515. This gap meant that crafted SIP URI values could bypass the escaping logic and manipulate LDAP filter queries. The fix adds explicit NUL byte escaping and replaces potentially unsafe `strncpy` calls with `memcpy` to ensure correct buffer handling.

O
By Orbis AppSec
Published June 14, 2026Reviewed June 14, 2026

Answer Summary

This is an LDAP injection vulnerability (CWE-90) in the OpenSIPS H350 module (`src/modules/h350/h350_exp_fn.c`), written in C. The root cause is that the `ldap_rfc4515_escape()` function in `src/modules/ldap/ldap_escape.c` did not escape the NUL byte (`\0`), which is a required special character per RFC 4515. An attacker could embed a NUL byte in a SIP URI header to truncate or manipulate the LDAP filter string. The fix adds a `case '\0':` branch to the escape switch statement, encoding it as `\00`, and replaces `strncpy` with `memcpy` for accurate length-bounded buffer copying.

Vulnerability at a Glance

cweCWE-90
fixAdded `case '\0':` to the escape switch in `ldap_escape.c` and replaced `strncpy` with `memcpy` in `h350_exp_fn.c`
riskAttacker-controlled SIP URI values manipulate LDAP filter queries, potentially bypassing authentication or exfiltrating directory data
languageC
root causeThe `ldap_rfc4515_escape()` function did not handle the NUL byte (`\0`), leaving a gap in RFC 4515 compliance
vulnerabilityLDAP Injection via incomplete RFC 4515 escaping

How LDAP Injection Happens in C with OpenLDAP and How to Fix It

Summary

A high-severity LDAP injection vulnerability was discovered in the OpenSIPS H350 module, where the ldap_rfc4515_escape() function failed to escape the NUL byte (\0) — one of the special characters defined in RFC 4515. This gap meant that crafted SIP URI values could bypass the escaping logic and manipulate LDAP filter queries. The fix adds explicit NUL byte escaping and replaces potentially unsafe strncpy calls with memcpy to ensure correct buffer handling.


Introduction

The src/modules/h350/h350_exp_fn.c file in OpenSIPS handles H.350 directory lookups — it takes SIP URI values from incoming SIP messages and uses them to query an LDAP directory. The lookup is constructed using the H350_SIPURI_LOOKUP_LDAP_FILTER macro, which embeds a SIP URI into an LDAP filter string.

A variable named sip_uri_escaped hints that escaping was intended. And indeed, ldap_rfc4515_escape() in src/modules/ldap/ldap_escape.c does escape several dangerous characters. But it missed one: the NUL byte (\0).

That single omission is enough to make the escaping incomplete — and LDAP injection possible through a crafted SIP header.


The Vulnerability Explained

What RFC 4515 Requires

RFC 4515 defines the string representation of LDAP search filters. It specifies that the following characters must be escaped when they appear in filter values:

Character Escaped Form
* \2a
( \28
) \29
\ \5c
NUL (\0) \00

Before this fix, the ldap_rfc4515_escape() function handled *, (, ), and \ — but not the NUL byte. Here's the relevant section of the vulnerable code:

while(src < (sin->s + sin->len)) {
    switch(*src) {
        case '*':
            *dst++ = '\\';
            *dst++ = '2';
            *dst = 'a';
            break;
        // ... other cases for (, ), \
        // ❌ No case for '\0'
    }
}

The absence of a case '\0': branch means that if a NUL byte appears in the input, it passes through unescaped.

Why NUL Bytes Are Dangerous in LDAP Filters

In C, strings are NUL-terminated. If an attacker can inject a NUL byte into a SIP URI value that gets embedded into an LDAP filter, the behavior depends on the LDAP library:

  • Some LDAP libraries treat the filter string as a C string and stop reading at the NUL, effectively truncating the filter. This can remove closing parentheses or additional constraints, producing a malformed or overly permissive filter.
  • Others may process the full binary buffer, where the NUL byte can act as a wildcard or separator depending on the LDAP server implementation.

Attack Scenario

Consider a SIP From: header crafted like this:

From: <sip:admin\x00*@example.com>

When this URI is passed through an incomplete escaping function and embedded into an LDAP filter such as:

(SIPIdentityUserName=admin\x00*)

A C-string-based LDAP library might truncate the filter at the NUL byte, resulting in:

(SIPIdentityUserName=admin

This is a malformed filter that could cause unexpected behavior — returning unintended results, bypassing authentication checks, or crashing the LDAP query processor. In a VoIP environment where H.350 is used to authenticate SIP users against an LDAP directory, this could allow an attacker to impersonate users or bypass credential verification entirely.


The Fix

The fix touches two files, each addressing a distinct problem.

1. src/modules/ldap/ldap_escape.c — Adding NUL Byte Escaping

The core fix adds a case '\0': branch to the existing switch statement in ldap_rfc4515_escape():

Before:

while(src < (sin->s + sin->len)) {
    switch(*src) {
        case '*':
            *dst++ = '\\';
            *dst++ = '2';
            *dst = 'a';
            break;
        // ... handles (, ), \ but NOT \0
    }
}

After:

while(src < (sin->s + sin->len)) {
    switch(*src) {
        case '\0':          // ✅ NEW: NUL byte now escaped
            *dst++ = '\\';
            *dst++ = '0';
            *dst = '0';
            break;
        case '*':
            *dst++ = '\\';
            *dst++ = '2';
            *dst = 'a';
            break;
        // ... rest of cases
    }
}

The NUL byte is now encoded as \00 per RFC 4515, preventing it from being passed raw into the LDAP filter string.

2. src/modules/h350/h350_exp_fn.c — Replacing strncpy with memcpy

The second change replaces two strncpy calls with memcpy in the h350_auth_lookup() function:

Before (line 138 and 155):

strncpy(username_avp_name_buf, username_avp_name.s.s,
        username_avp_name.s.len);
// ...
strncpy(password_avp_name_buf, password_avp_name.s.s,
        password_avp_name.s.len);

After:

memcpy(username_avp_name_buf, username_avp_name.s.s,
       username_avp_name.s.len);
// ...
memcpy(password_avp_name_buf, password_avp_name.s.s,
       password_avp_name.s.len);

This change matters because strncpy has subtle, often-misunderstood behavior: when the source string is shorter than n, it pads the remainder with NUL bytes. When the source is longer, it does not NUL-terminate the destination. Since the code already manually appends \0 at username_avp_name_buf[username_avp_name.s.len] = '\0', using memcpy with the exact length is both more explicit and more correct. It copies exactly s.len bytes without the ambiguous padding behavior of strncpy.

Together, these two changes close the injection vector and improve the correctness of buffer handling in the authentication lookup path.


Prevention & Best Practices

1. Implement Full RFC 4515 Compliance from the Start

When writing LDAP escaping functions in C, don't cherry-pick special characters. Implement the complete set defined in RFC 4515 from day one:

// All RFC 4515 special characters must be escaped
// *, (, ), \, NUL (and / in DNs per RFC 4514)

Write a test case for each one. A missing case in a switch statement is a classic C bug that's easy to introduce and hard to spot in code review.

2. Use memcpy for Length-Bounded Copies When You Control NUL Termination

The strncpy function is widely misunderstood. In OpenSIPS-style code where string lengths are tracked explicitly (via str structs with .s and .len), memcpy + manual NUL termination is cleaner and less error-prone:

memcpy(buf, src.s, src.len);
buf[src.len] = '\0';

This pattern is explicit about what it does and avoids strncpy's padding behavior.

3. Validate at the Boundary, Escape at the Sink

SIP URI values from incoming messages should be treated as untrusted input. Apply escaping immediately before they are used in LDAP filters — not earlier, not later. This keeps the escaping logic close to the dangerous operation.

4. Reference Standards Explicitly in Code Comments

/* Escape per RFC 4515 Section 3: *, (, ), \, NUL */

A comment like this makes it immediately obvious to the next developer that all six characters must be handled, reducing the chance of a future regression.

5. Use Static Analysis

Tools like Semgrep can be configured to trace tainted data from SIP message parsing functions to LDAP filter construction calls, flagging cases where the escaping function is missing or incomplete.

Relevant standards:
- OWASP LDAP Injection Prevention Cheat Sheet
- CWE-90: Improper Neutralization of Special Elements used in an LDAP Query


Key Takeaways

  • Incomplete escaping is still vulnerable escaping. The ldap_rfc4515_escape() function handled most RFC 4515 special characters but missed NUL — one missing case in a switch statement was enough to leave an injection vector open.
  • Always implement the full RFC 4515 character set. LDAP filter escaping must cover all six defined special characters: *, (, ), \, NUL, and /. Partial compliance creates a false sense of security.
  • NUL bytes are a real attack vector in C LDAP code. Because C strings are NUL-terminated, an unescaped NUL byte in a filter value can truncate or corrupt the filter in ways that are hard to predict and easy to exploit.
  • Prefer memcpy over strncpy when you control NUL termination. In h350_auth_lookup(), the code already manually NUL-terminates the buffer — strncpy's implicit behavior was redundant and potentially misleading.
  • SIP URI values are attacker-controlled input. Any code path that takes data from SIP headers and uses it in a system query (LDAP, SQL, shell) must treat that data as untrusted and apply appropriate escaping.

How Orbis AppSec Detected This

  • Source: SIP URI values extracted from incoming SIP message headers (e.g., From:, To:) in h350_exp_fn.c
  • Sink: LDAP filter string construction using the H350_SIPURI_LOOKUP_LDAP_FILTER macro in src/modules/h350/h350_exp_fn.c:38, passing through ldap_rfc4515_escape() in src/modules/ldap/ldap_escape.c
  • Missing control: The ldap_rfc4515_escape() function did not include a case '\0': branch, leaving the NUL byte unescaped despite RFC 4515 requiring it
  • CWE: CWE-90: Improper Neutralization of Special Elements used in an LDAP Query
  • Fix: Added case '\0': to the escaping switch in ldap_escape.c to encode NUL as \00, and replaced strncpy with memcpy in h350_exp_fn.c for correct length-bounded buffer copying

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

This vulnerability is a textbook example of how partial compliance with a security standard can be just as dangerous as no compliance at all. The ldap_rfc4515_escape() function was doing the right thing — but it was doing it incompletely. A single missing case '\0': in a switch statement left a gap that an attacker could exploit by embedding a NUL byte in a SIP URI to manipulate LDAP filter logic in the H.350 authentication path.

The fix is small — just a few lines — but the impact is significant. It brings the escaping function into full RFC 4515 compliance and removes a subtle but real injection vector from a security-critical code path. The accompanying strncpymemcpy change is a good example of defensive cleanup: removing ambiguous behavior even when it isn't directly exploitable.

For developers working on SIP, LDAP, or any code that bridges user-controlled input into directory queries: always implement the full escaping specification, test each special character explicitly, and treat every field from a network message as untrusted input.


References

Frequently Asked Questions

What is LDAP injection?

LDAP injection is an attack where unsanitized user input is embedded into an LDAP query, allowing an attacker to manipulate the query's logic — similar to SQL injection but targeting directory services.

How do you prevent LDAP injection in C?

Escape all LDAP special characters defined in RFC 4515 (*, (, ), \, NUL) before embedding user input into filter strings. Use a well-tested escaping function that covers all cases, including the NUL byte.

What CWE is LDAP injection?

LDAP injection is classified as CWE-90: Improper Neutralization of Special Elements used in an LDAP Query.

Is escaping most special characters enough to prevent LDAP injection?

No. RFC 4515 defines six special characters that must all be escaped: `*`, `(`, `)`, `\`, NUL, and `/`. Missing even one — like NUL — leaves a bypass vector open.

Can static analysis detect LDAP injection?

Yes. Static analysis tools like Semgrep can trace tainted data from SIP headers to LDAP filter construction calls, flagging cases where escaping functions are incomplete or missing.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4750

Related Articles

critical

How LDAP injection happens in Python Apache Airflow FAB security manager and how to fix it

A critical LDAP injection vulnerability was discovered in Apache Airflow's FAB (Flask-AppBuilder) security manager, specifically in the `_search_ldap()` method of `override.py`. The `AUTH_LDAP_SEARCH_FILTER` configuration value was interpolated directly into LDAP filter strings without validation, enabling attackers who could influence that configuration value to craft malicious filters that bypass authentication or exfiltrate directory data. The fix adds structural validation of the filter stri

critical

LDAP Injection in Apache Airflow: How a Missing Escape Nearly Opened the Gates

A critical LDAP injection vulnerability in Apache Airflow's Flask-AppBuilder security manager allowed attackers to bypass authentication and gain unauthorized access by crafting malicious usernames. The flaw stemmed from unsanitized user input being directly interpolated into LDAP filter strings — a classic but devastating mistake. This post breaks down how the attack works, what was fixed, and how you can prevent similar issues in your own code.

critical

How buffer overflow in URL parsing happens in C++ HTTP client and how to fix it

A critical buffer overflow vulnerability in the HTTP client's URL parsing function allowed attackers to overflow a stack-allocated host buffer through specially crafted URLs with excessively long hostnames. The vulnerability enabled arbitrary code execution by overwriting the return address. The fix adds proper bounds validation before the memcpy() operation to ensure the hostname length never exceeds the destination buffer size.

critical

How heap buffer overflow happens in C WiFi frame capture and how to fix it

A critical buffer overflow vulnerability in the ESP32 WiFi frame capture feature (feat_capture_hs.c) allowed attackers within WiFi range to craft oversized 802.11 frames that would overflow heap buffers and achieve remote code execution. The fix adds explicit length validation before memcpy operations and rejects oversized frames rather than silently truncating them.

critical

How integer overflow in _wopendir() happens in C Windows dirent and how to fix it

A critical integer overflow vulnerability in `include/compat/dirent_msvc.h` allowed an attacker-controlled directory path length to wrap the `sizeof(wchar_t) * n + 16` allocation calculation, resulting in a dangerously undersized heap buffer. Subsequent writes to that buffer caused a heap overflow, enabling potential memory corruption or code execution on Windows systems. The fix adds a pre-allocation bounds check and proper errno signaling to safely reject overflow-inducing inputs.

critical

How buffer overflow in SCSI command handling happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in libretro-common's CDROM handling code where the `cdrom_send_command_win32()` function copied an arbitrary number of bytes into a fixed 16-byte SCSI Command Descriptor Block (CDB) buffer without validation. This vulnerability could allow an attacker using a malicious CDROM image or USB device to corrupt memory and potentially execute arbitrary code. The fix adds a simple bounds check before the memcpy operation to ensure cmd_len never exc