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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4750

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.