Skip to content

fix(security): T2.19 hardening: LdapEncoder.filterEncode + entry-point NUL/control-char rejection in PSLdapMembershipAuthProvider (issue #133) - #134

Merged
natechadwick merged 1 commit into
mainfrom
security/t2-19-spring-ldap-injection
Aug 31, 2026
Merged

natechadwick merged 1 commit into
mainfrom
security/t2-19-spring-ldap-injection

Conversation

@natechadwick-intsof

Copy link
Copy Markdown
Collaborator

Summary

T2.19 of the parent epic #73 — harden Spring LDAP 2.4.4 against LDAP injection (CVE-2023-46527 class) at the only two production call sites in the project.

Closes #133.

The fix is the canonical Spring Security recommendation: encode the principal before it is substituted into the configured userSearchFilter via LdapEncoder.filterEncode(...), and reject obviously-malformed principals (NUL bytes, C0/C1 control characters) at the entry point so the failure is fast and the audit trail is clean.

Changes

deliverytiersuite/.../secure-membership/.../PSLdapMembershipAuthProvider.java — 1 file, 45 insertions, 1 deletion:

  1. doAuthentication (entry point) — reject any UsernamePasswordAuthenticationToken.getName() that is null or contains a NUL byte / C0 / C1 control character. LDAP usernames are restricted to printable characters by RFC 4519. The failure is surfaced as BadCredentialsException, consistent with existing failed-login handling.
  2. searchForUser (filter substitution site) — wrap the bindPrincipal in LdapEncoder.filterEncode(...) before passing it as the {0} argument to SpringSecurityLdapTemplate.searchForSingleEntryInternal. JNDI's Context.search(...) only expands the {0} placeholder — it does not escape LDAP filter metacharacters (, ), *, \, NUL. LdapEncoder is the public final utility in org.springframework.ldap.support (in spring-ldap-core:2.4.4, already on the classpath) and is a no-op for principals without metacharacters.
  3. Class Javadoc documents the input-validation contract so a future maintainer does not remove the entry-point check while "simplifying" the code.

PSLdapUserDetailsMapper is untouched: the XML loading path already routes through PSSecureXMLUtils.getSecuredDocumentBuilderFactory, and there is no LDAP filter substitution in that file.

Why at the call site

org.springframework.ldap:spring-ldap-core:2.4.4 is the last Java 1.8-compatible line. The 3.x line requires Java 11+ and is on a future-migration track. The CVE-2023-46527 class cannot be closed by a version bump, so the fix has to be at the call site.

Surface

The only two production files in the entire project that use Spring LDAP are these two (plus the secure-membership module's pom.xml). The class is @Deprecated (the secure-membership module is on a deprecation track) but is still on the classpath and is still scanned by Dependabot, so the CVE class has to be closed at the call site until the module is removed.

Verification

  • ./mvn-env.sh clean install -DskipTests → BUILD SUCCESS, 3:46, 60/60 modules.

What this PR does NOT do

  • No version bump (impossible on Java 1.8).
  • No removal of the deprecated secure-membership module (separate effort).
  • No change to the bind DN passed to bindAsUser — JNDI's InitialLdapContext authentication rejects malformed DNs at the server, so the bind DN is safe by construction. The injection vector is specifically the search filter substitution.

Co-Authored by Mavis Mavis-Code using MiniMax-M3 with agent mavis.

…t NUL/control-char rejection in PSLdapMembershipAuthProvider (issue #133)

Spring LDAP 2.4.4 is the last Java 1.8 line; 3.x requires Java 11+. The
CVE-2023-46527 class (LDAP injection via JNDI search filter substitution)
cannot be closed by a version bump, so the fix has to be at the call
site.

PSLdapMembershipAuthProvider.searchForUser passes the bind principal as
the {0} argument to SpringSecurityLdapTemplate.searchForSingleEntryInternal
in a JNDI Context.search(...). JNDI's substitution only expands the {0}
placeholder; it does NOT escape LDAP filter metacharacters '(', ')', '*',
'\', or NUL. A principal containing any of these would be interpreted as
filter syntax, allowing an authenticated low-privileged user to broaden
the user search to other directory entries.

Two hardening changes:

1. searchForUser (line ~364): wrap the bindPrincipal in
   LdapEncoder.filterEncode(bindPrincipal) before passing it as the {0}
   argument. LdapEncoder is the public final utility in
   org.springframework.ldap.support (spring-ldap-core, already on the
   classpath). It is a no-op for principals without metacharacters, so
   valid usernames flow through unchanged.

2. doAuthentication (line ~149): reject null usernames and any username
   containing a NUL byte or C0/C1 control character. LDAP usernames are
   restricted to printable characters by RFC 4519. The failure is
   surfaced as a BadCredentialsException, so the audit trail is consistent
   with existing failed-login handling.

PSLdapUserDetailsMapper is untouched: the XML loading path already
routes through PSSecureXMLUtils.getSecuredDocumentBuilderFactory, and
there is no LDAP filter substitution in that file.

The class is annotated @deprecated (it is part of the deprecated
secure-membership module) but is still on the classpath and is still
scanned by Dependabot, so the CVE class has to be closed at the call
site until the module is removed.

Full reactor clean install green on Java 1.8 in 3:46.

> Co-Authored by Mavis Mavis-Code using MiniMax-M3 with agent mavis.
@natechadwick
natechadwick merged commit 88467cb into main Aug 31, 2026
3 checks passed
@natechadwick
natechadwick deleted the security/t2-19-spring-ldap-injection branch August 31, 2026 19:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants