Skip to content

Commit 3b81c3b

Browse files
authored
Merge pull request #2651 from ggolawski/java-ldap-injection
Java LDAP Injection (CWE-90)
2 parents c24651c + 3fd8d9e commit 3b81c3b

File tree

57 files changed

+1841
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1841
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>If an LDAP query is built using string concatenation, and the
7+
components of the concatenation include user input, a user
8+
is likely to be able to run malicious LDAP queries.</p>
9+
</overview>
10+
11+
<recommendation>
12+
<p>If user input must be included in an LDAP query, it should be escaped to
13+
avoid a malicious user providing special characters that change the meaning
14+
of the query. If possible build the LDAP query using framework helper methods, for example
15+
from Spring's <code>LdapQueryBuilder</code> and <code>LdapNameBuilder</code>,
16+
instead of string concatenation. Alternatively, escape user input using an appropriate
17+
LDAP encoding method, for example: <code>encodeForLDAP</code> or <code>encodeForDN</code>
18+
from OWASP ESAPI, <code>LdapEncoder.filterEncode</code> or <code>LdapEncoder.nameEncode</code>
19+
from Spring LDAP, or <code>Filter.encodeValue</code> from UnboundID library.</p>
20+
</recommendation>
21+
22+
<example>
23+
<p>In the following examples, the code accepts an "organization name" and a "username"
24+
from the user, which it uses to query LDAP.</p>
25+
26+
<p>The first example concatenates the unvalidated and unencoded user input directly
27+
into both the DN (Distinguished Name) and the search filter used for the LDAP query.
28+
A malicious user could provide special characters to change the meaning of these
29+
queries, and search for a completely different set of values. The LDAP query is executed
30+
using Java JNDI API.
31+
</p>
32+
33+
<p>The second example uses the OWASP ESAPI library to encode the user values
34+
before they are included in the DN and search filters. This ensures the meaning of
35+
the query cannot be changed by a malicious user.</p>
36+
37+
<sample src="LdapInjectionJndi.java" />
38+
39+
<p>The third example uses Spring <code>LdapQueryBuilder</code> to build an LDAP query. In addition to
40+
simplifying the building of complex search parameters, it also provides proper escaping of any
41+
unsafe characters in search filters. The DN is built using <code>LdapNameBuilder</code>, which also provides
42+
proper escaping.</p>
43+
44+
<sample src="LdapInjectionSpring.java" />
45+
46+
<p>The fourth example uses <code>UnboundID</code> classes, <code>Filter</code> and <code>DN</code>, to construct a safe filter and
47+
base DN.</p>
48+
49+
<sample src="LdapInjectionUnboundId.java" />
50+
51+
<p>The fifth example shows how to build a safe filter and DN using the Apache LDAP API.</p>
52+
53+
<sample src="LdapInjectionApache.java" />
54+
</example>
55+
56+
<references>
57+
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html">LDAP Injection Prevention Cheat Sheet</a>.</li>
58+
<li>OWASP ESAPI: <a href="https://owasp.org/www-project-enterprise-security-api/">OWASP ESAPI</a>.</li>
59+
<li>Spring LdapQueryBuilder doc: <a href="https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/query/LdapQueryBuilder.html">LdapQueryBuilder</a>.</li>
60+
<li>Spring LdapNameBuilder doc: <a href="https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/support/LdapNameBuilder.html">LdapNameBuilder</a>.</li>
61+
<li>UnboundID: <a href="https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/">Understanding and Defending Against LDAP Injection Attacks</a>.</li>
62+
</references>
63+
</qhelp>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name LDAP query built from user-controlled sources
3+
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
4+
* malicious LDAP code by the user.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id java/ldap-injection
9+
* @tags security
10+
* external/cwe/cwe-090
11+
*/
12+
13+
import java
14+
import semmle.code.java.dataflow.FlowSources
15+
import LdapInjectionLib
16+
import DataFlow::PathGraph
17+
18+
from DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf
19+
where conf.hasFlowPath(source, sink)
20+
select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(),
21+
"this user input"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import org.apache.directory.ldap.client.api.LdapConnection;
2+
import org.apache.directory.api.ldap.model.name.Dn;
3+
import org.apache.directory.api.ldap.model.name.Rdn;
4+
import org.apache.directory.api.ldap.model.message.SearchRequest;
5+
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
6+
import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
7+
8+
public void ldapQueryGood(HttpServletRequest request, LdapConnection c) {
9+
String organizationName = request.getParameter("organization_name");
10+
String username = request.getParameter("username");
11+
12+
// GOOD: Organization name is encoded before being used in DN
13+
Dn safeDn = new Dn(new Rdn("OU", "People"), new Rdn("O", organizationName));
14+
15+
// GOOD: User input is encoded before being used in search filter
16+
String safeFilter = equal("username", username);
17+
18+
SearchRequest searchRequest = new SearchRequestImpl();
19+
searchRequest.setBase(safeDn);
20+
searchRequest.setFilter(safeFilter);
21+
c.search(searchRequest);
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import javax.naming.directory.DirContext;
2+
import org.owasp.esapi.Encoder;
3+
import org.owasp.esapi.reference.DefaultEncoder;
4+
5+
public void ldapQueryBad(HttpServletRequest request, DirContext ctx) throws NamingException {
6+
String organizationName = request.getParameter("organization_name");
7+
String username = request.getParameter("username");
8+
9+
// BAD: User input used in DN (Distinguished Name) without encoding
10+
String dn = "OU=People,O=" + organizationName;
11+
12+
// BAD: User input used in search filter without encoding
13+
String filter = "username=" + userName;
14+
15+
ctx.search(dn, filter, new SearchControls());
16+
}
17+
18+
public void ldapQueryGood(HttpServletRequest request, DirContext ctx) throws NamingException {
19+
String organizationName = request.getParameter("organization_name");
20+
String username = request.getParameter("username");
21+
22+
// ESAPI encoder
23+
Encoder encoder = DefaultEncoder.getInstance();
24+
25+
// GOOD: Organization name is encoded before being used in DN
26+
String safeOrganizationName = encoder.encodeForDN(organizationName);
27+
String safeDn = "OU=People,O=" + safeOrganizationName;
28+
29+
// GOOD: User input is encoded before being used in search filter
30+
String safeUsername = encoder.encodeForLDAP(username);
31+
String safeFilter = "username=" + safeUsername;
32+
33+
ctx.search(safeDn, safeFilter, new SearchControls());
34+
}

0 commit comments

Comments
 (0)