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 API key exposure in configuration files happens in TOML config and how to fix it

A critical security vulnerability in `commands/webperf.toml` allowed API keys to be hardcoded directly in configuration files, creating a credential exposure risk. The documentation on line 11 suggested developers could provide `CRUX_API_KEY` or `GOOGLE_API_KEY` directly in the config, which could lead to these sensitive credentials being committed to version control or exposed in logs. The fix updated the documentation to explicitly require environment variables and warn against hardcoding cred

critical

How integer overflow in buffer size calculation happens in C and how to fix it

A critical integer overflow vulnerability was discovered in the `nsh_setvar()` function in `nshlib/nsh_vars.c`, where the buffer size calculation `newsize = pstate->varsz + varlen` could wrap around, causing a heap buffer overflow. The fix adds overflow checking before the addition, preventing attackers with shell access from corrupting memory by setting variables with crafted names and values.

critical

How weak cryptographic randomness happens in C CSPRNG fallback paths and how to fix it

A critical vulnerability in `lib/sp_crypto.c` allowed the CSPRNG function to fall back to predictable randomness based on `time(NULL)` XORed with a counter when `/dev/urandom` was unavailable. An attacker who knew the approximate generation time could brute-force the output. The fix removes the unsafe fallback entirely, failing fast instead of silently degrading to weak randomness.

critical

How integer overflow happens in C reliable.c and how to fix it

A critical integer overflow vulnerability was discovered in `reliable.c` at line 1299, where the `packet_buffer_size` calculation used signed `int` arithmetic that could wrap to a negative or undersized value when large `fragment_size` values were involved. By casting each operand to `size_t` before multiplication, the fix eliminates the overflow risk entirely and ensures the allocated buffer is always large enough to hold the reassembled packet data.