Skip to content

Commit 95723b0

Browse files
committed
Query to detect LDAP injections in Java
Add help
1 parent 8cec463 commit 95723b0

File tree

5 files changed

+152
-0
lines changed

5 files changed

+152
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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 (or search filter / DN) using your
15+
framework helper methods to avoid string concatenation, or escape user input
16+
using the right LDAP encoding method, for example encodeForLDAP from OWASP ESAPI,
17+
LdapEncoder from Spring LDAP or Filter.encodeValue from UnboundID library.</p>
18+
</recommendation>
19+
20+
<example>
21+
<p>In the following examples, the code accepts an "organization name" and a "username"
22+
from the user, which it uses to query LDAP.</p>
23+
24+
<p>The first example concatenates the unvalidated and unencoded user input directly
25+
into both the DN (Distinguished Name) and the search filter used for the LDAP query.
26+
A malicious user could provide special characters to change the meaning of these
27+
queries, and search for a completely different set of values. The LDAP query is executed
28+
using Java JNDI API.
29+
</p>
30+
31+
<p>The second example uses the OWASP ESAPI library to encode the user values
32+
before they are included in the DN and search filters. This ensures the meaning of
33+
the query cannot be changed by a malicious user.</p>
34+
35+
<sample src="LdapInjectionJndi.java" />
36+
37+
<p>The third example uses Spring LdapQueryBuilder to build LDAP query. In addition to
38+
simplifying building of complex search parameters, it also provides proper escaping of any
39+
unsafe characters in search filters. DN is built using LdapNameBuilder, which also provides
40+
proper escaping.</p>
41+
42+
<sample src="LdapInjectionSpring.java" />
43+
44+
<p>The fourth example uses UnboundID Filter and DN classes to construct safe filter and
45+
base DN.</p>
46+
47+
<sample src="LdapInjectionUnboundId.java" />
48+
49+
<p>The fifth example shows how to build safe filter and DN using Apache LDAP API.</p>
50+
51+
<sample src="LdapInjectionApache.java" />
52+
</example>
53+
54+
<references>
55+
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html">LDAP Injection Prevention Cheat Sheet</a>.</li>
56+
<li>OWASP: <a href="https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java">Preventing LDAP Injection in Java</a>.</li>
57+
<li>OWASP ESAPI: <a href="https://owasp.org/www-project-enterprise-security-api/">OWASP ESAPI</a>.</li>
58+
<li>Spring LdapQueryBuilder doc: <a href="https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/query/LdapQueryBuilder.html">LdapQueryBuilder</a>.</li>
59+
<li>Spring LdapNameBuilder doc: <a href="https://docs.spring.io/spring-ldap/docs/current/apidocs/org/springframework/ldap/support/LdapNameBuilder.html">LdapNameBuilder</a>.</li>
60+
<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>
61+
</references>
62+
</qhelp>
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+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import static org.springframework.ldap.query.LdapQueryBuilder.query;
2+
import org.springframework.ldap.support.LdapNameBuilder;
3+
4+
public void ldapQueryGood(@RequestParam String organizationName, @RequestParam String username) {
5+
// GOOD: Organization name is encoded before being used in DN
6+
String safeDn = LdapNameBuilder.newInstance()
7+
.add("O", organizationName)
8+
.add("OU=People")
9+
.build().toString();
10+
11+
// GOOD: User input is encoded before being used in search filter
12+
LdapQuery query = query()
13+
.base(safeDn)
14+
.where("username").is(username);
15+
16+
ldapTemplate.search(query, new AttributeCheckAttributesMapper());
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import com.unboundid.ldap.sdk.LDAPConnection;
2+
import com.unboundid.ldap.sdk.DN;
3+
import com.unboundid.ldap.sdk.RDN;
4+
import com.unboundid.ldap.sdk.Filter;
5+
6+
public void ldapQueryGood(HttpServletRequest request, LDAPConnection c) {
7+
String organizationName = request.getParameter("organization_name");
8+
String username = request.getParameter("username");
9+
10+
// GOOD: Organization name is encoded before being used in DN
11+
DN safeDn = new DN(new RDN("OU", "People"), new RDN("O", organizationName));
12+
13+
// GOOD: User input is encoded before being used in search filter
14+
Filter safeFilter = Filter.createEqualityFilter("username", username);
15+
16+
c.search(safeDn.toString(), SearchScope.ONE, safeFilter);
17+
}

0 commit comments

Comments
 (0)