Skip to content

Java: add experimental java/ldap-dn-injection-library-mode query (CWE-90)#149

Open
tonghuaroot wants to merge 1 commit into
GitHubSecurityLab:mainfrom
tonghuaroot:java-ldap-dn-injection-library-mode
Open

Java: add experimental java/ldap-dn-injection-library-mode query (CWE-90)#149
tonghuaroot wants to merge 1 commit into
GitHubSecurityLab:mainfrom
tonghuaroot:java-ldap-dn-injection-library-mode

Conversation

@tonghuaroot

Copy link
Copy Markdown

Summary

Adds a new experimental Java query, githubsecuritylab/java/ldap-dn-injection-library-mode, that detects LDAP distinguished-name injection (CWE-90, RFC 2253) into a bind DN inside an authentication library or framework (Apache Shiro, a custom Spring Security realm, a CAS / pac4j SPI, a Keycloak provider).

Why the supported query misses this

The supported java/ldap-injection query starts from ActiveThreatModelSource (remote flow sources). An authentication framework has none: the login principal arrives as a method parameter, not as a servlet parameter or request body. On a framework database the supported query therefore has zero sources, regardless of the sink model, and the bug survives despite heavy scanning. This query closes that source-boundary gap with library-boundary sources.

Sources (library-boundary)

  • Login-principal accessors: Shiro AuthenticationToken.getPrincipal / getUsername, Spring Security Authentication.getName / getPrincipal, and java.security.Principal.getName.
  • DN-builder method parameters: a String parameter of a method whose name looks like a DN builder (getUserDn, *UserDn, buildDn, resolveDn, getUsernameWithSuffix, get*Principal, ...).

Honest caveat — the DN-builder source model is name-heuristic. It keys partly off method names. This is a deliberate precision/recall trade for the library case, where there is no remote flow source to anchor on: a framework that builds the DN in a differently named helper is missed, and a benign method that matches the name pattern may produce a false positive. This is why the query is experimental and @precision medium, and the trade-off is documented in both the QLDoc and the qhelp. Triage a result by confirming the value reaches a real bind sink unescaped.

Sinks (bind DN)

javax.naming Context / DirContext bind / rebind / lookup / lookupLink / createSubcontext (the String name argument); the java.naming.security.principal environment value; and Shiro LdapContextFactory.getLdapContext (the principal argument). new LdapName(String) is deliberately excluded (it commonly parses an existing cert/principal DN, not a fresh one for a bind). Barriers are RFC 2253 DN escapers (Rdn.escapeValue, Spring LdapEncoder.nameEncode, ESAPI encodeForDN). The query defines its sinks inline rather than reusing a shared sink library, so it stands alone.

The DN escape set (RFC 2253) differs from the LDAP search-filter escape set (RFC 4515): a value escaped for a search filter is still unsafe in a DN, so only DN escapers are treated as sanitizers.

Origin

This was originally proposed as an experimental query in github/codeql#22003. @michaelnebel noted that the Java experimental queries have been migrated to the Community Packs repo and asked that the PR be opened here instead, so this PR ports it.

Adapted to Community Packs conventions: @id namespace githubsecuritylab/java/..., query under java/src/security/CWE-090/ with its qhelp + bad/good samples alongside, test under java/test/security/CWE-090/ (flat, matching the existing Java tests) with .qlref / .expected / options, and the Shiro stubs the test needs added under java/test/stubs/. The github/codeql-specific change-note and integration-test files were dropped.

Contents

  • java/src/security/CWE-090/LdapDnInjectionLibraryMode.ql — the query (@kind path-problem, @precision medium, experimental).
  • java/src/security/CWE-090/LdapDnInjectionLibraryMode.qhelp — query help.
  • java/src/security/CWE-090/LdapDnInjectionLibraryMode{Bad,Good}.java — qhelp samples.
  • java/test/security/CWE-090/LdapDnInjectionLibraryMode.{java,qlref,expected} + options — true-positive / true-negative test shaped like the Shiro realm (getUserDn concatenation vs Rdn.escapeValue).
  • java/test/stubs/org-apache-shiro-authc-2.0.1/.../UsernamePasswordToken.java and java/test/stubs/org-apache-shiro-realm-ldap-2.0.1/.../LdapContextFactory.java — stubs the test needs (the org-apache-shiro-authc-2.0.1 stub directory already exists; this adds UsernamePasswordToken to it and adds the realm-ldap stub).

Verification

Verified locally with CodeQL CLI 2.25.6:

  • codeql query compile --warnings=error java/src/security/CWE-090/LdapDnInjectionLibraryMode.ql — compiles clean, no warnings.
  • codeql query format — idempotent (autoformatted).
  • codeql test run java/test/security/CWE-090passes: 8 true-positive path results on the three unescaped bind sinks (SECURITY_PRINCIPAL env value, Shiro getLdapContext, Context.lookup), each traced both to the DN-builder parameter and to the auth-token principal accessor; 0 results on the Rdn.escapeValue-escaped GOOD methods.

Detects LDAP distinguished-name injection (CWE-90, RFC 2253) into a bind DN
inside an authentication library or framework (Apache Shiro, a custom Spring
Security realm, a CAS/pac4j SPI, a Keycloak provider), where the login
principal arrives as a method parameter at the library boundary rather than at
a remote flow source. The supported java/ldap-injection query starts from
ActiveThreatModelSource and reports nothing on such a framework; this variant
models the library-boundary sources (login-principal accessors and DN-builder
method parameters) that it misses.

Anchored on Apache Shiro CVE-2026-49268. Ships a qhelp with bad/good samples
and a true-positive / true-negative test.
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.

1 participant