Skip to content

Commit 3e86dd1

Browse files
committed
Query to detect LDAP injections in Java
Apache LDAP API sink
1 parent c01aa3d commit 3e86dd1

2 files changed

Lines changed: 76 additions & 37 deletions

File tree

java/ql/src/Security/CWE/CWE-90/LdapInjection.ql

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,5 @@ import DataFlow::PathGraph
1818
from
1919
DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf
2020
where conf.hasFlowPath(source, sink)
21-
// select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(),
22-
// "this user input",
23-
select source, sink, sink.getNode().getEnclosingCallable().getName(), sink.getNode().getLocation().getStartLine()
21+
select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(),
22+
"this user input"

java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,19 @@ import java
22
import semmle.code.java.dataflow.FlowSources
33
import DataFlow
44

5+
/** The interface `javax.naming.directory.DirContext`. */
6+
class TypeDirContext extends Interface {
7+
TypeDirContext() { this.hasQualifiedName("javax.naming.directory", "DirContext") }
8+
}
9+
10+
/** The interface `javax.naming.ldap.LdapContext`. */
11+
class TypeLdapContext extends Interface {
12+
TypeLdapContext() { this.hasQualifiedName("javax.naming.ldap", "LdapContext") }
13+
}
14+
515
/** The class `com.unboundid.ldap.sdk.SearchRequest`. */
6-
class TypeSearchRequest extends Class {
7-
TypeSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") }
16+
class TypeUnboundIdSearchRequest extends Class {
17+
TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") }
818
}
919

1020
/** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */
@@ -53,6 +63,20 @@ class TypeSpringLdapFilter extends Interface {
5363
TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") }
5464
}
5565

66+
/** The interface `org.apache.directory.ldap.client.api.LdapConnection`. */
67+
class TypeLdapConnection extends Interface {
68+
TypeLdapConnection() {
69+
this.hasQualifiedName("org.apache.directory.ldap.client.api", "LdapConnection")
70+
}
71+
}
72+
73+
/** The interface `org.apache.directory.api.ldap.model.message.SearchRequest`. */
74+
class TypeApacheSearchRequest extends Interface {
75+
TypeApacheSearchRequest() {
76+
this.hasQualifiedName("org.apache.directory.api.ldap.model.message", "SearchRequest")
77+
}
78+
}
79+
5680
/** The class `org.springframework.ldap.support.LdapEncoder`. */
5781
class TypeLdapEncoder extends Class {
5882
TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") }
@@ -87,9 +111,10 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration {
87111

88112
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
89113
filterStep(node1, node2) or
90-
searchRequestStep(node1, node2) or
114+
unboundIdSearchRequestStep(node1, node2) or
91115
ldapQueryStep(node1, node2) or
92-
hardcodedFilterStep(node1, node2)
116+
hardcodedFilterStep(node1, node2) or
117+
apacheSearchRequestStep(node1, node2)
93118
}
94119
}
95120

@@ -103,30 +128,6 @@ class LocalSource extends LdapInjectionSource {
103128
LocalSource() { this instanceof LocalUserInput }
104129
}
105130

106-
abstract class Context extends RefType { }
107-
108-
/**
109-
* The interface `javax.naming.directory.DirContext` or
110-
* the class `javax.naming.directory.InitialDirContext`.
111-
*/
112-
class DirContext extends Context {
113-
DirContext() {
114-
this.hasQualifiedName("javax.naming.directory", "DirContext") or
115-
this.hasQualifiedName("javax.naming.directory", "InitialDirContext")
116-
}
117-
}
118-
119-
/**
120-
* The interface `javax.naming.ldap.LdapContext` or
121-
* the class `javax.naming.ldap.InitialLdapContext`.
122-
*/
123-
class LdapContext extends Context {
124-
LdapContext() {
125-
this.hasQualifiedName("javax.naming.ldap", "LdapContext") or
126-
this.hasQualifiedName("javax.naming.ldap", "InitialLdapContext")
127-
}
128-
}
129-
130131
/**
131132
* JNDI sink for LDAP injection vulnerabilities, i.e. 2nd argument to search method from
132133
* DirContext, InitialDirContext, LdapContext or InitialLdapContext.
@@ -137,7 +138,12 @@ class JndiLdapInjectionSink extends LdapInjectionSink {
137138
ma.getMethod() = m and
138139
ma.getArgument(index) = this.getExpr()
139140
|
140-
m.getDeclaringType() instanceof Context and m.hasName("search") and index = 1
141+
(
142+
m.getDeclaringType().getAnAncestor() instanceof TypeDirContext or
143+
m.getDeclaringType().getAnAncestor() instanceof TypeLdapContext
144+
) and
145+
m.hasName("search") and
146+
index = 1
141147
)
142148
}
143149
}
@@ -161,7 +167,7 @@ class UnboundIdLdapInjectionSink extends LdapInjectionSink {
161167
(
162168
// Parameter type is SearchRequest or ReadOnlySearchRequest
163169
param.getType() instanceof TypeReadOnlySearchRequest or
164-
param.getType() instanceof TypeSearchRequest or
170+
param.getType() instanceof TypeUnboundIdSearchRequest or
165171
// Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is)
166172
index = any(int i | i = [2..3] or i = [5..7])
167173
)
@@ -201,6 +207,27 @@ class SpringLdapInjectionSink extends LdapInjectionSink {
201207
}
202208
}
203209

210+
/** Apache LDAP API sink for LDAP injection vulnerabilities, i.e. LdapConnection.search method. */
211+
class ApacheLdapInjectionSink extends LdapInjectionSink {
212+
ApacheLdapInjectionSink() {
213+
exists(MethodAccess ma, Method m, int index, RefType paramType |
214+
ma.getMethod() = m and
215+
ma.getArgument(index) = this.getExpr() and
216+
m.getParameterType(index) = paramType
217+
|
218+
// LdapConnection.search method
219+
m.getDeclaringType().getAnAncestor() instanceof TypeLdapConnection and
220+
m.hasName("search") and
221+
(
222+
// Parameter type is SearchRequest
223+
paramType instanceof TypeApacheSearchRequest or
224+
// Or parameter index is 1 (this is where filter parameter is)
225+
index = 1
226+
)
227+
)
228+
}
229+
}
230+
204231
/** An expression node with a primitive type. */
205232
class PrimitiveTypeSanitizer extends LdapInjectionSanitizer {
206233
PrimitiveTypeSanitizer() { this.getType() instanceof PrimitiveType }
@@ -240,7 +267,6 @@ class UnboundIdSanitizer extends LdapInjectionSanitizer {
240267
*/
241268
predicate filterStep(ExprNode n1, ExprNode n2) {
242269
exists(MethodAccess ma, Method m |
243-
n1.asExpr() = ma.getQualifier() or
244270
n1.asExpr() = ma.getAnArgument()
245271
|
246272
n2.asExpr() = ma and
@@ -255,9 +281,9 @@ predicate filterStep(ExprNode n1, ExprNode n2) {
255281
* `SearchRequest`, i.e. `new SearchRequest([...], tainted, [...])`, where `tainted` is
256282
* parameter number 3, 4, 7, 8 or 9, but is not varargs.
257283
*/
258-
predicate searchRequestStep(ExprNode n1, ExprNode n2) {
284+
predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) {
259285
exists(ConstructorCall cc, Constructor c, int index |
260-
cc.getConstructedType() instanceof TypeSearchRequest
286+
cc.getConstructedType() instanceof TypeUnboundIdSearchRequest
261287
|
262288
n1.asExpr() = cc.getArgument(index) and
263289
n2.asExpr() = cc and
@@ -274,7 +300,6 @@ predicate searchRequestStep(ExprNode n1, ExprNode n2) {
274300
*/
275301
predicate ldapQueryStep(ExprNode n1, ExprNode n2) {
276302
exists(MethodAccess ma, Method m, int index |
277-
n1.asExpr() = ma.getQualifier() or
278303
n1.asExpr() = ma.getArgument(index)
279304
|
280305
n2.asExpr() = ma and
@@ -294,4 +319,19 @@ predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) {
294319
n1.asExpr() = cc.getAnArgument() and
295320
n2.asExpr() = cc
296321
)
322+
}
323+
324+
/**
325+
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API
326+
* `SearchRequest`, i.e. `SearchRequest s = new SearchRequestImpl(); s.setFilter(tainted");`.
327+
*/
328+
predicate apacheSearchRequestStep(ExprNode n1, ExprNode n2) {
329+
exists(MethodAccess ma, Method m |
330+
n1.asExpr() = ma.getAnArgument()
331+
|
332+
n2.asExpr() = ma.getQualifier() and
333+
ma.getMethod() = m and
334+
m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and
335+
m.hasName("setFilter")
336+
)
297337
}

0 commit comments

Comments
 (0)