Skip to content

Commit c01aa3d

Browse files
committed
Query to detect LDAP injections in Java
Spring LDAP sink
1 parent 7570fa9 commit c01aa3d

1 file changed

Lines changed: 105 additions & 46 deletions

File tree

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

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,63 @@ class TypeSearchRequest extends Class {
77
TypeSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") }
88
}
99

10-
/** The class `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */
10+
/** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */
1111
class TypeReadOnlySearchRequest extends Interface {
1212
TypeReadOnlySearchRequest() {
1313
this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest")
1414
}
1515
}
1616

1717
/** The class `com.unboundid.ldap.sdk.Filter`. */
18-
class TypeFilter extends Class {
19-
TypeFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") }
18+
class TypeUnboundIdLdapFilter extends Class {
19+
TypeUnboundIdLdapFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") }
2020
}
2121

2222
/** The class `com.unboundid.ldap.sdk.LDAPConnection`. */
2323
class TypeLDAPConnection extends Class {
2424
TypeLDAPConnection() { this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") }
2525
}
2626

27+
/** The class `org.springframework.ldap.core.LdapTemplate`. */
28+
class TypeLdapTemplate extends Class {
29+
TypeLdapTemplate() { this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") }
30+
}
31+
32+
/** The interface `org.springframework.ldap.query.LdapQuery`. */
33+
class TypeLdapQuery extends Interface {
34+
TypeLdapQuery() { this.hasQualifiedName("org.springframework.ldap.query", "LdapQuery") }
35+
}
36+
37+
/** The interface `org.springframework.ldap.query.LdapQueryBuilder`. */
38+
class TypeLdapQueryBuilder extends Class {
39+
TypeLdapQueryBuilder() {
40+
this.hasQualifiedName("org.springframework.ldap.query", "LdapQueryBuilder")
41+
}
42+
}
43+
44+
/** The interface `org.springframework.ldap.filter.HardcodedFilter`. */
45+
class TypeHardcodedFilter extends Class {
46+
TypeHardcodedFilter() {
47+
this.hasQualifiedName("org.springframework.ldap.filter", "HardcodedFilter")
48+
}
49+
}
50+
51+
/** The interface `org.springframework.ldap.filter.Filter`. */
52+
class TypeSpringLdapFilter extends Interface {
53+
TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") }
54+
}
55+
2756
/** The class `org.springframework.ldap.support.LdapEncoder`. */
2857
class TypeLdapEncoder extends Class {
2958
TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") }
3059
}
3160

61+
/** Holds if the parameter of `c` at index `paramIndex` is varargs. */
62+
bindingset[paramIndex]
63+
predicate isVarargs(Callable c, int paramIndex) {
64+
c.getParameter(min(int i | i = paramIndex or i = c.getNumberOfParameters() - 1 | i)).isVarargs()
65+
}
66+
3267
/** A data flow source for unvalidated user input that is used to construct LDAP queries. */
3368
abstract class LdapInjectionSource extends DataFlow::Node { }
3469

@@ -51,7 +86,10 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration {
5186
override predicate isSanitizer(DataFlow::Node node) { node instanceof LdapInjectionSanitizer }
5287

5388
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
54-
filterStep(node1, node2) or searchRequestStep(node1, node2)
89+
filterStep(node1, node2) or
90+
searchRequestStep(node1, node2) or
91+
ldapQueryStep(node1, node2) or
92+
hardcodedFilterStep(node1, node2)
5593
}
5694
}
5795

@@ -110,64 +148,54 @@ class JndiLdapInjectionSink extends LdapInjectionSink {
110148
*/
111149
class UnboundIdLdapInjectionSink extends LdapInjectionSink {
112150
UnboundIdLdapInjectionSink() {
113-
exists(MethodAccess ma, Method m, int index, RefType argType |
151+
exists(MethodAccess ma, Method m, int index, Parameter param |
114152
ma.getMethod() = m and
115153
ma.getArgument(index) = this.getExpr() and
116-
ma.getArgument(index).getType() = argType
154+
m.getParameter(index) = param
117155
|
118156
// LDAPConnection.search or LDAPConnection.searchForEntry method
119157
m.getDeclaringType() instanceof TypeLDAPConnection and
120158
(m.hasName("search") or m.hasName("searchForEntry")) and
159+
// Parameter is not varargs
160+
not isVarargs(m, index) and
121161
(
122162
// Parameter type is SearchRequest or ReadOnlySearchRequest
123-
(
124-
argType instanceof TypeReadOnlySearchRequest or
125-
argType instanceof TypeSearchRequest
126-
) or
163+
param.getType() instanceof TypeReadOnlySearchRequest or
164+
param.getType() instanceof TypeSearchRequest or
127165
// Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is)
128-
// but it's not the last one nor beyond the last one (varargs representing attributes)
129-
index = any(int i |
130-
(i = [2..3] or i = [5..7]) and i < ma.getMethod().getNumberOfParameters() - 1
131-
)
166+
index = any(int i | i = [2..3] or i = [5..7])
132167
)
133168
)
134169
}
135170
}
136171

137172
/**
138173
* Spring LDAP sink for LDAP injection vulnerabilities,
139-
* i.e. LDAPConnection.search or LDAPConnection.searchForEntry method.
174+
* i.e. LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method.
140175
*/
141-
// LdapTemplate:
142-
// find(LdapQuery query, Class<T> clazz)
143-
// find(Name base, Filter filter, SearchControls searchControls, Class<T> clazz)
144-
// findOne(LdapQuery query, Class<T> clazz)
145-
// search - 2nd param if String (filter)
146-
// search - 1st param if LdapQuery
147-
// searchForContext(LdapQuery query)
148-
// searchForObject - 2nd param if String (filter)
149-
// searchForObject - 1st param if LdapQuery
150176
class SpringLdapInjectionSink extends LdapInjectionSink {
151177
SpringLdapInjectionSink() {
152-
exists(MethodAccess ma, Method m, int index, RefType argType |
178+
exists(MethodAccess ma, Method m, int index, RefType paramType |
153179
ma.getMethod() = m and
154180
ma.getArgument(index) = this.getExpr() and
155-
ma.getArgument(index).getType() = argType
181+
m.getParameterType(index) = paramType
156182
|
157-
// LDAPConnection.search or LDAPConnection.searchForEntry method
158-
m.getDeclaringType() instanceof TypeLDAPConnection and
159-
(m.hasName("search") or m.hasName("searchForEntry")) and
183+
// LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method
184+
m.getDeclaringType() instanceof TypeLdapTemplate and
160185
(
161-
// Parameter type is SearchRequest or ReadOnlySearchRequest
162-
(
163-
argType instanceof TypeReadOnlySearchRequest or
164-
argType instanceof TypeSearchRequest
165-
) or
166-
// Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is)
167-
// but it's not the last one nor beyond the last one (varargs representing attributes)
168-
index = any(int i |
169-
(i = [2..3] or i = [5..7]) and i < ma.getMethod().getNumberOfParameters() - 1
170-
)
186+
m.hasName("authenticate") or
187+
m.hasName("find") or
188+
m.hasName("findOne") or
189+
m.hasName("search") or
190+
m.hasName("searchForContext") or
191+
m.hasName("searchForObject")
192+
) and
193+
(
194+
// Parameter type is LdapQuery or Filter
195+
paramType instanceof TypeLdapQuery or
196+
paramType instanceof TypeSpringLdapFilter or
197+
// Or parameter index is 1 (this is where filter parameter is)
198+
index = 1
171199
)
172200
)
173201
}
@@ -201,7 +229,7 @@ class SpringLdapSanitizer extends LdapInjectionSanitizer {
201229
/** Filter.encodeValue from UnboundID. */
202230
class UnboundIdSanitizer extends LdapInjectionSanitizer {
203231
UnboundIdSanitizer() {
204-
this.getType() instanceof TypeFilter and
232+
this.getType() instanceof TypeUnboundIdLdapFilter and
205233
this.getExpr().(MethodAccess).getMethod().hasName("encodeValue")
206234
}
207235
}
@@ -217,22 +245,53 @@ predicate filterStep(ExprNode n1, ExprNode n2) {
217245
|
218246
n2.asExpr() = ma and
219247
ma.getMethod() = m and
220-
m.getDeclaringType() instanceof TypeFilter and
248+
m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and
221249
m.hasName("create")
222250
)
223251
}
224252

225253
/**
226254
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID
227255
* `SearchRequest`, i.e. `new SearchRequest([...], tainted, [...])`, where `tainted` is
228-
* parameter number 3, 4, 7, 8 or 9, but not the last one or beyond the last one (varargs).
256+
* parameter number 3, 4, 7, 8 or 9, but is not varargs.
229257
*/
230258
predicate searchRequestStep(ExprNode n1, ExprNode n2) {
231-
exists(ConstructorCall cc, int index | cc.getConstructedType() instanceof TypeSearchRequest |
259+
exists(ConstructorCall cc, Constructor c, int index |
260+
cc.getConstructedType() instanceof TypeSearchRequest
261+
|
232262
n1.asExpr() = cc.getArgument(index) and
233263
n2.asExpr() = cc and
234-
index = any(int i |
235-
(i = [2..3] or i = [6..8]) and i < cc.getConstructor().getNumberOfParameters() - 1
236-
)
264+
c = cc.getConstructor() and
265+
// not c.getParameter(min(int i | i = index or i = c.getNumberOfParameters() - 1 | i)).isVarargs() and
266+
not isVarargs(c, index) and
267+
index = any(int i |i = [2..3] or i = [6..8])
268+
)
269+
}
270+
271+
/**
272+
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring `LdapQuery`,
273+
* i.e. `LdapQueryBuilder.query().filter(tainted)`.
274+
*/
275+
predicate ldapQueryStep(ExprNode n1, ExprNode n2) {
276+
exists(MethodAccess ma, Method m, int index |
277+
n1.asExpr() = ma.getQualifier() or
278+
n1.asExpr() = ma.getArgument(index)
279+
|
280+
n2.asExpr() = ma and
281+
ma.getMethod() = m and
282+
m.getDeclaringType() instanceof TypeLdapQueryBuilder and
283+
m.hasName("filter") and
284+
index = 0
285+
)
286+
}
287+
288+
/**
289+
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring
290+
* `HardcodedFilter`, i.e. `new HardcodedFilter(tainted)`.
291+
*/
292+
predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) {
293+
exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeHardcodedFilter |
294+
n1.asExpr() = cc.getAnArgument() and
295+
n2.asExpr() = cc
237296
)
238297
}

0 commit comments

Comments
 (0)