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