Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7570fa9

Browse files
committed
Query to detect LDAP injections in Java
JNDI and UnboundID sinks JNDI, UnboundID and Spring LDAP sanitizers
1 parent 367d13c commit 7570fa9

2 files changed

Lines changed: 261 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 semmle.code.java.Expr
14+
import semmle.code.java.dataflow.FlowSources
15+
import LdapInjectionLib
16+
import DataFlow::PathGraph
17+
18+
from
19+
DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf
20+
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()
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
import DataFlow
4+
5+
/** The class `com.unboundid.ldap.sdk.SearchRequest`. */
6+
class TypeSearchRequest extends Class {
7+
TypeSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") }
8+
}
9+
10+
/** The class `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */
11+
class TypeReadOnlySearchRequest extends Interface {
12+
TypeReadOnlySearchRequest() {
13+
this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest")
14+
}
15+
}
16+
17+
/** The class `com.unboundid.ldap.sdk.Filter`. */
18+
class TypeFilter extends Class {
19+
TypeFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") }
20+
}
21+
22+
/** The class `com.unboundid.ldap.sdk.LDAPConnection`. */
23+
class TypeLDAPConnection extends Class {
24+
TypeLDAPConnection() { this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") }
25+
}
26+
27+
/** The class `org.springframework.ldap.support.LdapEncoder`. */
28+
class TypeLdapEncoder extends Class {
29+
TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") }
30+
}
31+
32+
/** A data flow source for unvalidated user input that is used to construct LDAP queries. */
33+
abstract class LdapInjectionSource extends DataFlow::Node { }
34+
35+
/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */
36+
abstract class LdapInjectionSink extends DataFlow::ExprNode { }
37+
38+
/** A sanitizer for unvalidated user input that is used to construct LDAP queries. */
39+
abstract class LdapInjectionSanitizer extends DataFlow::ExprNode { }
40+
41+
/**
42+
* A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries.
43+
*/
44+
class LdapInjectionFlowConfig extends TaintTracking::Configuration {
45+
LdapInjectionFlowConfig() { this = "LdapInjectionFlowConfig" }
46+
47+
override predicate isSource(DataFlow::Node source) { source instanceof LdapInjectionSource }
48+
49+
override predicate isSink(DataFlow::Node sink) { sink instanceof LdapInjectionSink }
50+
51+
override predicate isSanitizer(DataFlow::Node node) { node instanceof LdapInjectionSanitizer }
52+
53+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
54+
filterStep(node1, node2) or searchRequestStep(node1, node2)
55+
}
56+
}
57+
58+
/** A source of remote user input. */
59+
class RemoteSource extends LdapInjectionSource {
60+
RemoteSource() { this instanceof RemoteFlowSource }
61+
}
62+
63+
/** A source of local user input. */
64+
class LocalSource extends LdapInjectionSource {
65+
LocalSource() { this instanceof LocalUserInput }
66+
}
67+
68+
abstract class Context extends RefType { }
69+
70+
/**
71+
* The interface `javax.naming.directory.DirContext` or
72+
* the class `javax.naming.directory.InitialDirContext`.
73+
*/
74+
class DirContext extends Context {
75+
DirContext() {
76+
this.hasQualifiedName("javax.naming.directory", "DirContext") or
77+
this.hasQualifiedName("javax.naming.directory", "InitialDirContext")
78+
}
79+
}
80+
81+
/**
82+
* The interface `javax.naming.ldap.LdapContext` or
83+
* the class `javax.naming.ldap.InitialLdapContext`.
84+
*/
85+
class LdapContext extends Context {
86+
LdapContext() {
87+
this.hasQualifiedName("javax.naming.ldap", "LdapContext") or
88+
this.hasQualifiedName("javax.naming.ldap", "InitialLdapContext")
89+
}
90+
}
91+
92+
/**
93+
* JNDI sink for LDAP injection vulnerabilities, i.e. 2nd argument to search method from
94+
* DirContext, InitialDirContext, LdapContext or InitialLdapContext.
95+
*/
96+
class JndiLdapInjectionSink extends LdapInjectionSink {
97+
JndiLdapInjectionSink() {
98+
exists(MethodAccess ma, Method m, int index |
99+
ma.getMethod() = m and
100+
ma.getArgument(index) = this.getExpr()
101+
|
102+
m.getDeclaringType() instanceof Context and m.hasName("search") and index = 1
103+
)
104+
}
105+
}
106+
107+
/**
108+
* UnboundID sink for LDAP injection vulnerabilities,
109+
* i.e. LDAPConnection.search or LDAPConnection.searchForEntry method.
110+
*/
111+
class UnboundIdLdapInjectionSink extends LdapInjectionSink {
112+
UnboundIdLdapInjectionSink() {
113+
exists(MethodAccess ma, Method m, int index, RefType argType |
114+
ma.getMethod() = m and
115+
ma.getArgument(index) = this.getExpr() and
116+
ma.getArgument(index).getType() = argType
117+
|
118+
// LDAPConnection.search or LDAPConnection.searchForEntry method
119+
m.getDeclaringType() instanceof TypeLDAPConnection and
120+
(m.hasName("search") or m.hasName("searchForEntry")) and
121+
(
122+
// Parameter type is SearchRequest or ReadOnlySearchRequest
123+
(
124+
argType instanceof TypeReadOnlySearchRequest or
125+
argType instanceof TypeSearchRequest
126+
) or
127+
// 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+
)
132+
)
133+
)
134+
}
135+
}
136+
137+
/**
138+
* Spring LDAP sink for LDAP injection vulnerabilities,
139+
* i.e. LDAPConnection.search or LDAPConnection.searchForEntry method.
140+
*/
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
150+
class SpringLdapInjectionSink extends LdapInjectionSink {
151+
SpringLdapInjectionSink() {
152+
exists(MethodAccess ma, Method m, int index, RefType argType |
153+
ma.getMethod() = m and
154+
ma.getArgument(index) = this.getExpr() and
155+
ma.getArgument(index).getType() = argType
156+
|
157+
// LDAPConnection.search or LDAPConnection.searchForEntry method
158+
m.getDeclaringType() instanceof TypeLDAPConnection and
159+
(m.hasName("search") or m.hasName("searchForEntry")) and
160+
(
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+
)
171+
)
172+
)
173+
}
174+
}
175+
176+
/** An expression node with a primitive type. */
177+
class PrimitiveTypeSanitizer extends LdapInjectionSanitizer {
178+
PrimitiveTypeSanitizer() { this.getType() instanceof PrimitiveType }
179+
}
180+
181+
/** An expression node with a boxed type. */
182+
class BoxedTypeSanitizer extends LdapInjectionSanitizer {
183+
BoxedTypeSanitizer() { this.getType() instanceof BoxedType }
184+
}
185+
186+
/** encodeForLDAP and encodeForDN from OWASP ESAPI. */
187+
class EsapiSanitizer extends LdapInjectionSanitizer {
188+
EsapiSanitizer() {
189+
this.getExpr().(MethodAccess).getMethod().hasName("encodeForLDAP")
190+
}
191+
}
192+
193+
/** LdapEncoder.filterEncode and LdapEncoder.nameEncode from Spring LDAP. */
194+
class SpringLdapSanitizer extends LdapInjectionSanitizer {
195+
SpringLdapSanitizer() {
196+
this.getType() instanceof TypeLdapEncoder and
197+
this.getExpr().(MethodAccess).getMethod().hasName("filterEncode")
198+
}
199+
}
200+
201+
/** Filter.encodeValue from UnboundID. */
202+
class UnboundIdSanitizer extends LdapInjectionSanitizer {
203+
UnboundIdSanitizer() {
204+
this.getType() instanceof TypeFilter and
205+
this.getExpr().(MethodAccess).getMethod().hasName("encodeValue")
206+
}
207+
}
208+
209+
/**
210+
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID `Filter`,
211+
* i.e. `Filter.create(tainted)`.
212+
*/
213+
predicate filterStep(ExprNode n1, ExprNode n2) {
214+
exists(MethodAccess ma, Method m |
215+
n1.asExpr() = ma.getQualifier() or
216+
n1.asExpr() = ma.getAnArgument()
217+
|
218+
n2.asExpr() = ma and
219+
ma.getMethod() = m and
220+
m.getDeclaringType() instanceof TypeFilter and
221+
m.hasName("create")
222+
)
223+
}
224+
225+
/**
226+
* Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID
227+
* `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).
229+
*/
230+
predicate searchRequestStep(ExprNode n1, ExprNode n2) {
231+
exists(ConstructorCall cc, int index | cc.getConstructedType() instanceof TypeSearchRequest |
232+
n1.asExpr() = cc.getArgument(index) and
233+
n2.asExpr() = cc and
234+
index = any(int i |
235+
(i = [2..3] or i = [6..8]) and i < cc.getConstructor().getNumberOfParameters() - 1
236+
)
237+
)
238+
}

0 commit comments

Comments
 (0)