-
Notifications
You must be signed in to change notification settings - Fork 24
Java: add experimental java/ldap-dn-injection-library-mode query (CWE-90) #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tonghuaroot
wants to merge
1
commit into
GitHubSecurityLab:main
Choose a base branch
from
tonghuaroot:java-ldap-dn-injection-library-mode
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
79 changes: 79 additions & 0 deletions
79
java/src/security/CWE-090/LdapDnInjectionLibraryMode.qhelp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| <!DOCTYPE qhelp PUBLIC | ||
| "-//Semmle//qhelp//EN" | ||
| "qhelp.dtd"> | ||
| <qhelp> | ||
|
|
||
| <overview> | ||
| <p> | ||
| An LDAP distinguished name (DN) identifies an entry in a directory, for example | ||
| <code>uid=alice,ou=people,dc=example,dc=com</code>. When an authentication framework | ||
| builds the bind DN by concatenating the login principal into a DN template without | ||
| escaping it for RFC 2253, an attacker can supply DN metacharacters | ||
| (<code>, + " \ < > ; =</code>, a leading <code>#</code>, or leading/trailing | ||
| whitespace) to change the structure of the DN that is used to authenticate. Depending | ||
| on the directory, this can bypass authentication or impersonate another principal. | ||
| </p> | ||
| <p> | ||
| This query targets the defect inside an authentication <em>library or framework</em> | ||
| (Apache Shiro, a custom Spring Security realm, a CAS or pac4j SPI, a Keycloak provider), | ||
| where the login principal does not arrive at a remote flow source such as a servlet | ||
| parameter, but as a method parameter at the library boundary. The supported | ||
| <code>java/ldap-injection</code> query, which starts from remote flow sources, does not | ||
| report on such a framework because there is no remote flow source to start from. | ||
| </p> | ||
| <p> | ||
| The DN escape set (RFC 2253) differs from the LDAP search-filter escape set (RFC 4515). | ||
| A value escaped for a search filter (for example with <code>LdapEncoder.filterEncode</code>) | ||
| is still unsafe in a DN, and vice versa. This query treats only DN escapers as | ||
| sanitizers. | ||
| </p> | ||
| <p> | ||
| The library-mode source model is name-heuristic: it treats the login-principal | ||
| accessors of common authentication frameworks, and the string parameters of | ||
| DN-builder-shaped methods (for example <code>getUserDn</code> or | ||
| <code>getUsernameWithSuffix</code>), as sources. 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 uses medium precision. Triage a result by confirming the | ||
| value reaches a real bind sink unescaped. | ||
| </p> | ||
| </overview> | ||
|
|
||
| <recommendation> | ||
| <p> | ||
| Escape the login principal for RFC 2253 before placing it in a DN, for example with | ||
| <code>javax.naming.ldap.Rdn.escapeValue</code>, Spring LDAP | ||
| <code>LdapEncoder.nameEncode</code>, or OWASP ESAPI <code>encodeForDN</code>. Prefer | ||
| building the DN from structured components (an <code>LdapName</code> and | ||
| <code>Rdn</code> objects) rather than string concatenation. | ||
| </p> | ||
| </recommendation> | ||
|
|
||
| <example> | ||
| <p> | ||
| The following example concatenates the login principal into the bind DN with no | ||
| escaping. An attacker who logs in as <code>*</code> or | ||
| <code>admin,ou=admins,dc=example,dc=com+uid=anything</code> can manipulate the DN. | ||
| </p> | ||
| <sample src="LdapDnInjectionLibraryModeBad.java" /> | ||
| <p> | ||
| The following example escapes the principal with <code>Rdn.escapeValue</code> before | ||
| building the DN, so DN metacharacters are neutralised. | ||
| </p> | ||
| <sample src="LdapDnInjectionLibraryModeGood.java" /> | ||
| </example> | ||
|
|
||
| <references> | ||
| <li> | ||
| OWASP: <a href="https://owasp.org/www-community/attacks/LDAP_Injection">LDAP Injection</a>. | ||
| </li> | ||
| <li> | ||
| RFC 2253: <a href="https://datatracker.ietf.org/doc/html/rfc2253">UTF-8 String Representation of Distinguished Names</a>. | ||
| </li> | ||
| <li> | ||
| Java SE API: <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.naming/javax/naming/ldap/Rdn.html#escapeValue(java.lang.Object)">Rdn.escapeValue</a>. | ||
| </li> | ||
| </references> | ||
|
|
||
| </qhelp> | ||
180 changes: 180 additions & 0 deletions
180
java/src/security/CWE-090/LdapDnInjectionLibraryMode.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,180 @@ | ||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * @name LDAP distinguished name injection in authentication framework code (library-mode sources) | ||||||||||||||||||||||||||||||||||||||||||
| * @description Building an LDAP bind distinguished name (DN) from an unescaped login | ||||||||||||||||||||||||||||||||||||||||||
| * principal lets an attacker manipulate the DN structure used to | ||||||||||||||||||||||||||||||||||||||||||
| * authenticate, potentially bypassing authentication or impersonating | ||||||||||||||||||||||||||||||||||||||||||
| * another principal. This variant uses library-boundary sources to find | ||||||||||||||||||||||||||||||||||||||||||
| * the defect inside an authentication framework, where the principal | ||||||||||||||||||||||||||||||||||||||||||
| * arrives as a method parameter rather than at a remote flow source. | ||||||||||||||||||||||||||||||||||||||||||
| * @kind path-problem | ||||||||||||||||||||||||||||||||||||||||||
| * @problem.severity error | ||||||||||||||||||||||||||||||||||||||||||
| * @security-severity 8.1 | ||||||||||||||||||||||||||||||||||||||||||
| * @precision medium | ||||||||||||||||||||||||||||||||||||||||||
| * @id githubsecuritylab/java/ldap-dn-injection-library-mode | ||||||||||||||||||||||||||||||||||||||||||
| * @tags security | ||||||||||||||||||||||||||||||||||||||||||
| * experimental | ||||||||||||||||||||||||||||||||||||||||||
| * external/cwe/cwe-090 | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import java | ||||||||||||||||||||||||||||||||||||||||||
| import semmle.code.java.dataflow.TaintTracking | ||||||||||||||||||||||||||||||||||||||||||
| import LdapDnLibraryModeFlow::PathGraph | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * The `String name` argument of `javax.naming.Context` / `DirContext` | ||||||||||||||||||||||||||||||||||||||||||
| * `bind` / `rebind` / `lookup` / `lookupLink` / `createSubcontext` -- interpreted as | ||||||||||||||||||||||||||||||||||||||||||
| * a (composite or distinguished) name when given a `String`. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * `new javax.naming.ldap.LdapName(String)` is deliberately not used as a sink: it | ||||||||||||||||||||||||||||||||||||||||||
| * commonly parses an existing certificate or principal DN to read its RDNs (e.g. | ||||||||||||||||||||||||||||||||||||||||||
| * `new LdapName(cert.getSubjectX500Principal().getName()).getRdns()`), which is not | ||||||||||||||||||||||||||||||||||||||||||
| * injection. The injection sinks are the positions where a DN string is used to bind, | ||||||||||||||||||||||||||||||||||||||||||
| * look up, or authenticate. | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| class JndiNameLookupMethod extends Method { | ||||||||||||||||||||||||||||||||||||||||||
| JndiNameLookupMethod() { | ||||||||||||||||||||||||||||||||||||||||||
| this.hasName(["bind", "rebind", "lookup", "lookupLink", "createSubcontext"]) and | ||||||||||||||||||||||||||||||||||||||||||
| this.getDeclaringType() | ||||||||||||||||||||||||||||||||||||||||||
| .getAnAncestor*() | ||||||||||||||||||||||||||||||||||||||||||
| .hasQualifiedName("javax.naming", ["Context", "directory.DirContext"]) and | ||||||||||||||||||||||||||||||||||||||||||
| this.getParameterType(0) instanceof TypeString | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * A call to `Map.put` / `Hashtable.put` whose key is the | ||||||||||||||||||||||||||||||||||||||||||
| * `javax.naming.Context.SECURITY_PRINCIPAL` constant or the literal string | ||||||||||||||||||||||||||||||||||||||||||
| * `"java.naming.security.principal"`. The value argument is the bind DN. | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| class SecurityPrincipalPut extends MethodCall { | ||||||||||||||||||||||||||||||||||||||||||
| SecurityPrincipalPut() { | ||||||||||||||||||||||||||||||||||||||||||
| this.getMethod().hasName("put") and | ||||||||||||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||||||||||||
| this.getArgument(0).(FieldRead).getField().hasName("SECURITY_PRINCIPAL") | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| this.getArgument(0).(CompileTimeConstantExpr).getStringValue() = | ||||||||||||||||||||||||||||||||||||||||||
| "java.naming.security.principal" | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * The `principal` argument of Apache Shiro | ||||||||||||||||||||||||||||||||||||||||||
| * `LdapContextFactory.getLdapContext(principal, credentials)` -- the bind DN. This is | ||||||||||||||||||||||||||||||||||||||||||
| * the sink in Apache Shiro CVE-2026-49268. | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| class ShiroGetLdapContextMethod extends Method { | ||||||||||||||||||||||||||||||||||||||||||
| ShiroGetLdapContextMethod() { | ||||||||||||||||||||||||||||||||||||||||||
| this.hasName("getLdapContext") and | ||||||||||||||||||||||||||||||||||||||||||
| this.getDeclaringType() | ||||||||||||||||||||||||||||||||||||||||||
| .getAnAncestor*() | ||||||||||||||||||||||||||||||||||||||||||
| .hasQualifiedName("org.apache.shiro.realm.ldap", "LdapContextFactory") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+73
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * A login-principal accessor: Apache Shiro `AuthenticationToken.getPrincipal` / | ||||||||||||||||||||||||||||||||||||||||||
| * `getUsername`, Spring Security `Authentication.getName` / `getPrincipal`, or | ||||||||||||||||||||||||||||||||||||||||||
| * `java.security.Principal.getName`. These return the untrusted login identity inside | ||||||||||||||||||||||||||||||||||||||||||
| * an authentication framework. | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| class AuthPrincipalAccessor extends MethodCall { | ||||||||||||||||||||||||||||||||||||||||||
| AuthPrincipalAccessor() { | ||||||||||||||||||||||||||||||||||||||||||
| exists(Method m | m = this.getMethod() | | ||||||||||||||||||||||||||||||||||||||||||
| m.hasName(["getPrincipal", "getUsername"]) and | ||||||||||||||||||||||||||||||||||||||||||
| m.getDeclaringType() | ||||||||||||||||||||||||||||||||||||||||||
| .getAnAncestor*() | ||||||||||||||||||||||||||||||||||||||||||
| .hasQualifiedName("org.apache.shiro.authc", | ||||||||||||||||||||||||||||||||||||||||||
| ["AuthenticationToken", "UsernamePasswordToken", "HostAuthenticationToken"]) | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| m.hasName(["getName", "getPrincipal"]) and | ||||||||||||||||||||||||||||||||||||||||||
| m.getDeclaringType() | ||||||||||||||||||||||||||||||||||||||||||
| .getAnAncestor*() | ||||||||||||||||||||||||||||||||||||||||||
| .hasQualifiedName("org.springframework.security.core", "Authentication") | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| m.hasName("getName") and | ||||||||||||||||||||||||||||||||||||||||||
| m.getDeclaringType().getAnAncestor*().hasQualifiedName("java.security", "Principal") | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * A `String` parameter of a DN-builder-shaped method, e.g. `getUserDn`, | ||||||||||||||||||||||||||||||||||||||||||
| * `getUsernameWithSuffix`, `buildDn`, `resolveDn`. An authentication framework | ||||||||||||||||||||||||||||||||||||||||||
| * receives the untrusted principal here and concatenates it into the bind DN. | ||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||
| * This source model is name-heuristic: it keys partly off method names. It 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; a benign method that matches the name pattern may produce a false | ||||||||||||||||||||||||||||||||||||||||||
| * positive. Triage a result by confirming the value reaches a real bind sink | ||||||||||||||||||||||||||||||||||||||||||
| * unescaped. | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| class DnBuilderParam extends Parameter { | ||||||||||||||||||||||||||||||||||||||||||
| DnBuilderParam() { | ||||||||||||||||||||||||||||||||||||||||||
| this.getType() instanceof TypeString and | ||||||||||||||||||||||||||||||||||||||||||
| exists(string name | name = this.getCallable().getName().toLowerCase() | | ||||||||||||||||||||||||||||||||||||||||||
| name.matches([ | ||||||||||||||||||||||||||||||||||||||||||
| "get%userdn", "%userdn", "build%dn", "make%dn", "resolve%dn", "create%dn", "to%dn", | ||||||||||||||||||||||||||||||||||||||||||
| "compute%dn" | ||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| name.matches("%usernamewithsuffix%") | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| name.matches(["get%principal", "build%principal"]) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** A call to a recognised RFC 2253 DN escaper, e.g. `javax.naming.ldap.Rdn.escapeValue`. */ | ||||||||||||||||||||||||||||||||||||||||||
| class DnEscaperCall extends MethodCall { | ||||||||||||||||||||||||||||||||||||||||||
| DnEscaperCall() { | ||||||||||||||||||||||||||||||||||||||||||
| exists(Method m | m = this.getMethod() | | ||||||||||||||||||||||||||||||||||||||||||
| m.hasName("escapeValue") and | ||||||||||||||||||||||||||||||||||||||||||
| m.getDeclaringType().hasQualifiedName("javax.naming.ldap", "Rdn") | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| m.hasName("nameEncode") and | ||||||||||||||||||||||||||||||||||||||||||
| m.getDeclaringType().hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| m.hasName("encodeForDN") | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| m.getName() | ||||||||||||||||||||||||||||||||||||||||||
| .toLowerCase() | ||||||||||||||||||||||||||||||||||||||||||
| .matches(["%escapedn%", "%escapeldapdn%", "%encodefordn%", "%escapedistinguished%"]) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * A taint-tracking configuration for an unescaped login principal flowing into an | ||||||||||||||||||||||||||||||||||||||||||
| * LDAP bind DN inside an authentication framework. | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
| module LdapDnLibraryModeConfig implements DataFlow::ConfigSig { | ||||||||||||||||||||||||||||||||||||||||||
| predicate isSource(DataFlow::Node source) { | ||||||||||||||||||||||||||||||||||||||||||
| source.asExpr() instanceof AuthPrincipalAccessor | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| source.asParameter() instanceof DnBuilderParam | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| predicate isSink(DataFlow::Node sink) { | ||||||||||||||||||||||||||||||||||||||||||
| exists(MethodCall ma | ma.getMethod() instanceof JndiNameLookupMethod | | ||||||||||||||||||||||||||||||||||||||||||
| sink.asExpr() = ma.getArgument(0) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| sink.asExpr() = any(SecurityPrincipalPut p).getArgument(1) | ||||||||||||||||||||||||||||||||||||||||||
| or | ||||||||||||||||||||||||||||||||||||||||||
| exists(MethodCall ma | ma.getMethod() instanceof ShiroGetLdapContextMethod | | ||||||||||||||||||||||||||||||||||||||||||
| sink.asExpr() = ma.getArgument(0) | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| predicate isBarrier(DataFlow::Node node) { node.asExpr() instanceof DnEscaperCall } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| module LdapDnLibraryModeFlow = TaintTracking::Global<LdapDnLibraryModeConfig>; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from LdapDnLibraryModeFlow::PathNode source, LdapDnLibraryModeFlow::PathNode sink | ||||||||||||||||||||||||||||||||||||||||||
| where LdapDnLibraryModeFlow::flowPath(source, sink) | ||||||||||||||||||||||||||||||||||||||||||
| select sink.getNode(), source, sink, | ||||||||||||||||||||||||||||||||||||||||||
| "LDAP bind DN depends on a $@ without RFC 2253 distinguished-name escaping.", source.getNode(), | ||||||||||||||||||||||||||||||||||||||||||
| "library-boundary login principal" | ||||||||||||||||||||||||||||||||||||||||||
21 changes: 21 additions & 0 deletions
21
java/src/security/CWE-090/LdapDnInjectionLibraryModeBad.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import org.apache.shiro.authc.AuthenticationToken; | ||
| import org.apache.shiro.realm.ldap.LdapContextFactory; | ||
|
|
||
| public class LdapDnInjectionLibraryModeBad { | ||
| private final LdapContextFactory ldapContextFactory; | ||
|
|
||
| public LdapDnInjectionLibraryModeBad(LdapContextFactory ldapContextFactory) { | ||
| this.ldapContextFactory = ldapContextFactory; | ||
| } | ||
|
|
||
| // BAD: the login principal is concatenated into the bind DN with no escaping, so an | ||
| // attacker can supply DN metacharacters to manipulate the DN used to authenticate. | ||
| protected String getUserDn(String principal) { | ||
| return "uid=" + principal + ",ou=people,dc=example,dc=com"; | ||
| } | ||
|
|
||
| public Object bind(AuthenticationToken token) throws Exception { | ||
| String dn = getUserDn((String) token.getPrincipal()); | ||
| return ldapContextFactory.getLdapContext(dn, token.getCredentials()); | ||
| } | ||
| } |
22 changes: 22 additions & 0 deletions
22
java/src/security/CWE-090/LdapDnInjectionLibraryModeGood.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import javax.naming.ldap.Rdn; | ||
| import org.apache.shiro.authc.AuthenticationToken; | ||
| import org.apache.shiro.realm.ldap.LdapContextFactory; | ||
|
|
||
| public class LdapDnInjectionLibraryModeGood { | ||
| private final LdapContextFactory ldapContextFactory; | ||
|
|
||
| public LdapDnInjectionLibraryModeGood(LdapContextFactory ldapContextFactory) { | ||
| this.ldapContextFactory = ldapContextFactory; | ||
| } | ||
|
|
||
| // GOOD: the login principal is escaped for RFC 2253 with Rdn.escapeValue before it | ||
| // is placed in the DN, so DN metacharacters are neutralised. | ||
| protected String getUserDn(String principal) { | ||
| return "uid=" + Rdn.escapeValue(principal) + ",ou=people,dc=example,dc=com"; | ||
| } | ||
|
|
||
| public Object bind(AuthenticationToken token) throws Exception { | ||
| String dn = getUserDn((String) token.getPrincipal()); | ||
| return ldapContextFactory.getLdapContext(dn, token.getCredentials()); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a developer were to use a structured component such as
Rdn(String, Object)would this query show an alert still since they are not considered sanitizers?