-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathPermissiveDotRegexQuery.qll
More file actions
215 lines (195 loc) · 6.51 KB
/
PermissiveDotRegexQuery.qll
File metadata and controls
215 lines (195 loc) · 6.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/** Provides classes related to security-centered regular expression matching. */
deprecated module;
import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
import experimental.semmle.code.java.security.SpringUrlRedirect
import semmle.code.java.controlflow.Guards
import semmle.code.java.security.UrlRedirect
import Regex
overlay[local?]
private class ActivateModels extends ActiveExperimentalModels {
ActivateModels() { this = "permissive-dot-regex-query" }
}
/** A string that ends with `.*` not prefixed with `\`. */
private class PermissiveDotStr extends StringLiteral {
PermissiveDotStr() {
exists(string s, int i | this.getValue() = s |
s.indexOf(".*") = i and
not s.charAt(i - 1) = "\\" and
s.length() = i + 2
)
}
}
/** The qualifier of a request dispatch method call. */
private class UrlDispatchSink extends UrlRedirectSink {
UrlDispatchSink() {
exists(MethodCall ma |
ma.getMethod() instanceof RequestDispatchMethod and
this.asExpr() = ma.getQualifier()
)
}
}
/** The `doFilter` method of `javax.servlet.FilterChain`. */
private class ServletFilterMethod extends Method {
ServletFilterMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.servlet", "FilterChain") and
this.hasName("doFilter")
}
}
/** The qualifier of a servlet filter method call. */
private class UrlFilterSink extends UrlRedirectSink {
UrlFilterSink() {
exists(MethodCall ma |
ma.getMethod() instanceof ServletFilterMethod and
this.asExpr() = ma.getQualifier()
)
}
}
/** A Spring framework annotation indicating that a URI is user-provided. */
private class SpringUriInputAnnotation extends Annotation {
SpringUriInputAnnotation() {
this.getType()
.hasQualifiedName("org.springframework.web.bind.annotation",
["PathVariable", "RequestParam"])
}
}
/** A user-provided URI parameter of a request mapping method. */
private class SpringUriInputParameterSource extends DataFlow::Node {
SpringUriInputParameterSource() {
this.asParameter() =
any(SpringRequestMappingParameter srmp |
srmp.getAnAnnotation() instanceof SpringUriInputAnnotation
)
}
}
/**
* A data flow sink to construct regular expressions.
*/
private class CompileRegexSink extends DataFlow::ExprNode {
CompileRegexSink() {
exists(MethodCall ma, Method m | m = ma.getMethod() |
(
ma.getArgument(0) = this.asExpr() and
(
m instanceof StringMatchMethod // input.matches(regexPattern)
or
m instanceof PatternCompileMethod // p = Pattern.compile(regexPattern)
or
m instanceof PatternMatchMethod // p = Pattern.matches(regexPattern, input)
)
)
)
}
}
/**
* A data flow configuration for regular expressions that include permissive dots.
*/
private module PermissiveDotRegexConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof PermissiveDotStr }
predicate isSink(DataFlow::Node sink) { sink instanceof CompileRegexSink }
predicate isBarrier(DataFlow::Node node) {
exists(
MethodCall ma, Field f // Pattern.compile(PATTERN, Pattern.DOTALL)
|
ma.getMethod() instanceof PatternCompileMethod and
ma.getArgument(1) = f.getAnAccess() and
f.hasName("DOTALL") and
f.getDeclaringType() instanceof Pattern and
node.asExpr() = ma.getArgument(0)
)
}
}
private module PermissiveDotRegexFlow = DataFlow::Global<PermissiveDotRegexConfig>;
/**
* A taint-tracking configuration for untrusted user input used to match regular expressions
* that include permissive dots.
*/
module MatchRegexConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
sourceNode(source, "uri-path") or // Servlet uri source
source instanceof SpringUriInputParameterSource // Spring uri source
}
predicate isSink(DataFlow::Node sink) {
sink instanceof MatchRegexSink and
exists(
Guard guard, Expr se, Expr ce // used in a condition to control url redirect, which is a typical security enforcement
|
(
sink.asExpr() = ce.(MethodCall).getQualifier() or
sink.asExpr() = ce.(MethodCall).getAnArgument() or
sink.asExpr() = ce
) and
(
DataFlow::localExprFlow(ce, guard.(MethodCall).getQualifier()) or
DataFlow::localExprFlow(ce, guard.(MethodCall).getAnArgument())
) and
(
DataFlow::exprNode(se) instanceof UrlRedirectSink or
DataFlow::exprNode(se) instanceof SpringUrlRedirectSink
) and
guard.controls(se.getBasicBlock(), true)
) and
exists(MethodCall ma | PermissiveDotRegexFlow::flowToExpr(ma.getArgument(0)) |
// input.matches(regexPattern)
ma.getMethod() instanceof StringMatchMethod and
ma.getQualifier() = sink.asExpr()
or
// p = Pattern.compile(regexPattern); p.matcher(input)
ma.getMethod() instanceof PatternCompileMethod and
exists(MethodCall pma |
pma.getMethod() instanceof PatternMatcherMethod and
sink.asExpr() = pma.getArgument(0) and
DataFlow::localExprFlow(ma, pma.getQualifier())
)
or
// p = Pattern.matches(regexPattern, input)
ma.getMethod() instanceof PatternMatchMethod and
sink.asExpr() = ma.getArgument(1)
)
}
}
module MatchRegexFlow = TaintTracking::Global<MatchRegexConfig>;
/**
* A data flow sink representing a string being matched against a regular expression.
*/
abstract class MatchRegexSink extends DataFlow::ExprNode { }
/**
* A string being matched against a regular expression.
*/
private class StringMatchRegexSink extends MatchRegexSink {
StringMatchRegexSink() {
exists(MethodCall ma, Method m | m = ma.getMethod() |
(
m instanceof StringMatchMethod and
ma.getQualifier() = this.asExpr()
)
)
}
}
/**
* A string being matched against a regular expression using a pattern.
*/
private class PatternMatchRegexSink extends MatchRegexSink {
PatternMatchRegexSink() {
exists(MethodCall ma, Method m | m = ma.getMethod() |
(
m instanceof PatternMatchMethod and
ma.getArgument(1) = this.asExpr()
)
)
}
}
/**
* A string being used to create a pattern matcher.
*/
private class PatternMatcherRegexSink extends MatchRegexSink {
PatternMatcherRegexSink() {
exists(MethodCall ma, Method m | m = ma.getMethod() |
(
m instanceof PatternMatcherMethod and
ma.getArgument(0) = this.asExpr()
)
)
}
}