Skip to content

Java: model LDAP bind-DN sinks for java/ldap-injection#22002

Open
tonghuaroot wants to merge 1 commit into
github:mainfrom
tonghuaroot:ldap-bind-dn-sinks
Open

Java: model LDAP bind-DN sinks for java/ldap-injection#22002
tonghuaroot wants to merge 1 commit into
github:mainfrom
tonghuaroot:ldap-bind-dn-sinks

Conversation

@tonghuaroot

Copy link
Copy Markdown
Contributor

What

java/ldap-injection models LDAP search sinks (the filter and base of a directory
search) but not the bind-DN path, where LDAP distinguished-name injection
(CWE-90, RFC 2253) actually lands. This PR adds the bind-DN sinks:

  • Models-as-Data sink rows (ldap-injection):
    • javax.naming.Context bind / rebind / createSubcontext / lookup /
      lookupLink — the String name argument (javax.naming.model.yml).
    • javax.naming.directory.DirContext bind / rebind / createSubcontext — the
      String name argument (javax.naming.directory.model.yml).
    • org.apache.shiro.realm.ldap.LdapContextFactory.getLdapContext — the principal
      argument (new org.apache.shiro.realm.ldap.model.yml). This is the exact sink in
      Apache Shiro CVE-2026-49268.
  • A hand-written LdapInjectionSink for the java.naming.security.principal JNDI
    environment value (env.put(Context.SECURITY_PRINCIPAL, dn) and the literal-key
    form). This cannot be a MaD sink because it keys off the put key argument, not
    the method signature.

When given a String, all of these positions interpret the argument as a
(distinguished) name; an unescaped, attacker-controlled value lets the caller
manipulate the DN structure used to authenticate, which can bypass authentication or
impersonate another principal.

Deliberately not modeled

new javax.naming.ldap.LdapName(String) is not added as a sink. It commonly
parses an existing certificate or principal DN to read its RDNs (for example
new LdapName(cert.getSubjectX500Principal().getName()).getRdns()), which is not
injection. Modeling it as a sink would over-fire on that benign idiom. This is noted
in a code comment and in the change note. (The DN-string positions where a DN is used
to bind/look up/authenticate are the real sinks.)

DN escaping vs filter escaping

DN escaping (RFC 2253) uses a different escape set from search-filter escaping
(RFC 4515). Rdn.escapeValue neutralizes DN metacharacters (, + " \ < > ; =,
leading #, leading/trailing space) but not filter metacharacters (* ( )). The
existing model already treats Rdn.escapeValue as flow-stopping (neutral model in
ext/generated/modelgenerator/javax.naming.ldap.model.yml), so the canonical Shiro
2.2.1 fix is correctly recognized as safe; the added test confirms this.

Evidence (Apache Shiro CVE-2026-49268)

CVE-2026-49268 (fixed 2.2.1 / 3.0.0-alpha-2). DefaultLdapRealm.getUserDn and
ActiveDirectoryRealm.getUsernameWithSuffix concatenated the login username into the
bind DN with no escaping. Running a 3-way comparison on a database built from
apache/shiro@shiro-root-2.2.0:

Query Hits on Shiro 2.2.0
stock java/ldap-injection (before this PR) 0
java/ldap-injection with these bind-DN sinks 0 (library has no remote flow source — see the companion experimental PR)

The zero here is expected and is exactly the point: Shiro is an authentication
library, so there is no remote flow source for the app-mode query to start from. This
PR makes the query catch the same bind-DN construction in downstream applications
where the username crosses an HTTP boundary; catching the framework itself is the job
of the companion experimental query (java/ldap-dn-injection-library-mode).

Tests

The CWE-090 query test gains bind-DN true positives for each new sink
(Context.SECURITY_PRINCIPAL via the constant and the literal key, DirContext.bind,
Context.lookup, Shiro getLdapContext) and Rdn.escapeValue true negatives.
codeql test run is green, and the query compiles with --warnings=error.

The java/ldap-injection query modelled LDAP search sinks (the filter and
base of a directory search) but not the bind-DN path, where DN injection
(CWE-90, RFC 2253) actually lands. Add bind-DN sinks:

- javax.naming.Context / javax.naming.directory.DirContext bind, rebind,
  lookup, lookupLink and createSubcontext (the String name argument);
- the java.naming.security.principal JNDI environment value, modelled as a
  hand-written sink because it depends on the put key, not the signature;
- Apache Shiro LdapContextFactory.getLdapContext (the principal argument).

new javax.naming.ldap.LdapName(String) is deliberately left unmodelled: it
commonly parses an existing certificate or principal DN rather than building
one for a bind, so modelling it as a sink would over-fire.

Extend the CWE-090 test with bind-DN true positives and Rdn.escapeValue
true negatives.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant