Skip to content

Commit 3e382fd

Browse files
committed
Optimize the query
1 parent 1d12bd1 commit 3e382fd

File tree

3 files changed

+209
-218
lines changed

3 files changed

+209
-218
lines changed

java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegex.ql

Lines changed: 3 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -12,177 +12,14 @@
1212
*/
1313

1414
import java
15-
import experimental.semmle.code.java.security.SpringUrlRedirect
16-
import semmle.code.java.controlflow.Guards
17-
import semmle.code.java.dataflow.ExternalFlow
1815
import semmle.code.java.dataflow.FlowSources
19-
import semmle.code.java.security.UrlRedirect
2016
import DataFlow::PathGraph
21-
import Regex
17+
import PermissiveDotRegexQuery
2218

23-
/** Source model of remote flow source with servlets. */
24-
private class GetServletUriSource extends SourceModelCsv {
25-
override predicate row(string row) {
26-
row =
27-
[
28-
"javax.servlet.http;HttpServletRequest;false;getPathInfo;();;ReturnValue;uri-path;manual",
29-
"javax.servlet.http;HttpServletRequest;false;getPathTranslated;();;ReturnValue;uri-path;manual",
30-
"javax.servlet.http;HttpServletRequest;false;getRequestURI;();;ReturnValue;uri-path;manual",
31-
"javax.servlet.http;HttpServletRequest;false;getRequestURL;();;ReturnValue;uri-path;manual",
32-
"javax.servlet.http;HttpServletRequest;false;getServletPath;();;ReturnValue;uri-path;manual"
33-
]
34-
}
35-
}
36-
37-
/** Sink model of servlet dispatcher. */
38-
private class UrlDispatchSink extends SinkModelCsv {
39-
override predicate row(string row) {
40-
row =
41-
[
42-
"javax.servlet;RequestDispatcher;false;forward;;;Argument[-1];url-dispatch;manual",
43-
"javax.servlet;RequestDispatcher;false;include;;;Argument[-1];url-dispatch;manual"
44-
]
45-
}
46-
}
47-
48-
/** Sink model of servlet filter. */
49-
private class UrlFilterSink extends SinkModelCsv {
50-
override predicate row(string row) {
51-
row = ["javax.servlet;FilterChain;true;doFilter;;;Argument[-1];url-filter;manual"]
52-
}
53-
}
54-
55-
/** A Spring framework annotation indicating remote uri user input. */
56-
class SpringUriInputAnnotation extends Annotation {
57-
SpringUriInputAnnotation() {
58-
exists(AnnotationType a |
59-
a = this.getType() and
60-
a.getPackage().getName() = "org.springframework.web.bind.annotation"
61-
|
62-
(
63-
a.hasName("PathVariable") or
64-
a.hasName("RequestParam")
65-
)
66-
)
67-
}
68-
}
69-
70-
class SpringUriInputParameterSource extends DataFlow::Node {
71-
SpringUriInputParameterSource() {
72-
this.asParameter() =
73-
any(SpringRequestMappingParameter srmp |
74-
srmp.getAnAnnotation() instanceof SpringUriInputAnnotation
75-
)
76-
}
77-
}
78-
79-
/**
80-
* `.` without a `\` prefix, which is likely not a character literal in regex
81-
*/
82-
class PermissiveDotStr extends StringLiteral {
83-
PermissiveDotStr() {
84-
// Find `.` in a string that is not prefixed with `\` and ends with `.*` (no suffix like file extension)
85-
exists(string s, int i | this.getValue() = s |
86-
s.indexOf(".*") = i and
87-
not s.charAt(i - 1) = "\\" and
88-
s.length() = i + 2
89-
)
90-
}
91-
}
92-
93-
/**
94-
* Permissive `.` in a regular expression.
95-
*/
96-
class PermissiveDotEx extends Expr {
97-
PermissiveDotEx() { this instanceof PermissiveDotStr }
98-
}
99-
100-
/**
101-
* A data flow sink to construct regular expressions.
102-
*/
103-
class CompileRegexSink extends DataFlow::ExprNode {
104-
CompileRegexSink() {
105-
exists(MethodAccess ma, Method m | m = ma.getMethod() |
106-
(
107-
ma.getArgument(0) = this.asExpr() and
108-
(
109-
m instanceof StringMatchMethod // input.matches(regexPattern)
110-
or
111-
m instanceof PatternCompileMethod // p = Pattern.compile(regexPattern)
112-
or
113-
m instanceof PatternMatchMethod // p = Pattern.matches(regexPattern, input)
114-
)
115-
)
116-
)
117-
}
118-
}
119-
120-
/**
121-
* A flow configuration for permissive dot regex.
122-
*/
123-
class PermissiveDotRegexConfig extends DataFlow::Configuration {
124-
PermissiveDotRegexConfig() { this = "PermissiveDotRegex::PermissiveDotRegexConfig" }
125-
126-
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof PermissiveDotEx }
127-
128-
override predicate isSink(DataFlow::Node sink) { sink instanceof CompileRegexSink }
129-
130-
override predicate isBarrier(DataFlow::Node node) {
131-
exists(
132-
MethodAccess ma, Field f // Pattern.compile(PATTERN, Pattern.DOTALL)
133-
|
134-
ma.getMethod() instanceof PatternCompileMethod and
135-
ma.getArgument(1) = f.getAnAccess() and
136-
f.hasName("DOTALL") and
137-
f.getDeclaringType() instanceof Pattern and
138-
node.asExpr() = ma.getArgument(0)
139-
)
140-
}
141-
}
142-
143-
/**
144-
* A taint-tracking configuration for untrusted user input used to match regular expressions.
145-
*/
146-
class MatchRegexConfiguration extends TaintTracking::Configuration {
147-
MatchRegexConfiguration() { this = "PermissiveDotRegex::MatchRegexConfiguration" }
148-
149-
override predicate isSource(DataFlow::Node source) {
150-
sourceNode(source, "uri-path") or // Servlet uri source
151-
source instanceof SpringUriInputParameterSource // Spring uri source
152-
}
153-
154-
override predicate isSink(DataFlow::Node sink) {
155-
sink instanceof MatchRegexSink and
156-
exists(
157-
Guard guard, Expr se, Expr ce // used in a condition to control url redirect, which is a typical security enforcement
158-
|
159-
(
160-
sink.asExpr() = ce.(MethodAccess).getQualifier() or
161-
sink.asExpr() = ce.(MethodAccess).getAnArgument() or
162-
sink.asExpr() = ce
163-
) and
164-
(
165-
DataFlow::localExprFlow(ce, guard.(MethodAccess).getQualifier()) or
166-
DataFlow::localExprFlow(ce, guard.(MethodAccess).getAnArgument())
167-
) and
168-
(
169-
DataFlow::exprNode(se) instanceof UrlRedirectSink or
170-
sinkNode(DataFlow::exprNode(se), "url-dispatch") or
171-
sinkNode(DataFlow::exprNode(se), "url-filter") or
172-
DataFlow::exprNode(se) instanceof SpringUrlRedirectSink
173-
) and
174-
guard.controls(se.getBasicBlock(), true)
175-
)
176-
}
177-
}
178-
179-
from
180-
DataFlow::PathNode source, DataFlow::PathNode sink, MatchRegexConfiguration conf,
181-
DataFlow::PathNode source2, DataFlow::PathNode sink2, PermissiveDotRegexConfig conf2
19+
from DataFlow::PathNode source, DataFlow::PathNode sink, MatchRegexConfiguration conf
18220
where
18321
conf.hasFlowPath(source, sink) and
184-
conf2.hasFlowPath(source2, sink2) and
185-
exists(MethodAccess ma | ma.getArgument(0) = sink2.getNode().asExpr() |
22+
exists(MethodAccess ma | any(PermissiveDotRegexConfig conf2).hasFlowToExpr(ma.getArgument(0)) |
18623
// input.matches(regexPattern)
18724
ma.getMethod() instanceof StringMatchMethod and
18825
ma.getQualifier() = sink.getNode().asExpr()
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/** Provides classes related to security-centered regular expression matching. */
2+
3+
import java
4+
private import semmle.code.java.dataflow.ExternalFlow
5+
private import semmle.code.java.dataflow.FlowSources
6+
import experimental.semmle.code.java.security.SpringUrlRedirect
7+
import semmle.code.java.controlflow.Guards
8+
import semmle.code.java.security.UrlRedirect
9+
import Regex
10+
11+
/** A string that ends with `.*` not prefixed with `\`. */
12+
class PermissiveDotStr extends StringLiteral {
13+
PermissiveDotStr() {
14+
exists(string s, int i | this.getValue() = s |
15+
s.indexOf(".*") = i and
16+
not s.charAt(i - 1) = "\\" and
17+
s.length() = i + 2
18+
)
19+
}
20+
}
21+
22+
/** Source model of remote flow source with servlets. */
23+
private class GetServletUriSource extends SourceModelCsv {
24+
override predicate row(string row) {
25+
row =
26+
[
27+
"javax.servlet.http;HttpServletRequest;false;getPathInfo;();;ReturnValue;uri-path;manual",
28+
"javax.servlet.http;HttpServletRequest;false;getPathTranslated;();;ReturnValue;uri-path;manual",
29+
"javax.servlet.http;HttpServletRequest;false;getRequestURI;();;ReturnValue;uri-path;manual",
30+
"javax.servlet.http;HttpServletRequest;false;getRequestURL;();;ReturnValue;uri-path;manual",
31+
"javax.servlet.http;HttpServletRequest;false;getServletPath;();;ReturnValue;uri-path;manual"
32+
]
33+
}
34+
}
35+
36+
/** Sink of servlet dispatcher. */
37+
private class UrlDispatchSink extends UrlRedirectSink {
38+
UrlDispatchSink() {
39+
exists(MethodAccess ma |
40+
ma.getMethod() instanceof RequestDispatchMethod and
41+
this.asExpr() = ma.getQualifier()
42+
)
43+
}
44+
}
45+
46+
/** The `doFilter` method of `javax.servlet.FilterChain`. */
47+
private class ServletFilterMethod extends Method {
48+
ServletFilterMethod() {
49+
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.servlet", "FilterChain") and
50+
this.hasName("doFilter")
51+
}
52+
}
53+
54+
/** Sink of servlet filter. */
55+
private class UrlFilterSink extends UrlRedirectSink {
56+
UrlFilterSink() {
57+
exists(MethodAccess ma |
58+
ma.getMethod() instanceof ServletFilterMethod and
59+
this.asExpr() = ma.getQualifier()
60+
)
61+
}
62+
}
63+
64+
/** A Spring framework annotation indicating remote uri user input. */
65+
class SpringUriInputAnnotation extends Annotation {
66+
SpringUriInputAnnotation() {
67+
this.getType()
68+
.hasQualifiedName("org.springframework.web.bind.annotation",
69+
["PathVariable", "RequestParam"])
70+
}
71+
}
72+
73+
class SpringUriInputParameterSource extends DataFlow::Node {
74+
SpringUriInputParameterSource() {
75+
this.asParameter() =
76+
any(SpringRequestMappingParameter srmp |
77+
srmp.getAnAnnotation() instanceof SpringUriInputAnnotation
78+
)
79+
}
80+
}
81+
82+
/**
83+
* A data flow sink to construct regular expressions.
84+
*/
85+
class CompileRegexSink extends DataFlow::ExprNode {
86+
CompileRegexSink() {
87+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
88+
(
89+
ma.getArgument(0) = this.asExpr() and
90+
(
91+
m instanceof StringMatchMethod // input.matches(regexPattern)
92+
or
93+
m instanceof PatternCompileMethod // p = Pattern.compile(regexPattern)
94+
or
95+
m instanceof PatternMatchMethod // p = Pattern.matches(regexPattern, input)
96+
)
97+
)
98+
)
99+
}
100+
}
101+
102+
/**
103+
* A flow configuration for permissive dot regex.
104+
*/
105+
class PermissiveDotRegexConfig extends DataFlow::Configuration {
106+
PermissiveDotRegexConfig() { this = "PermissiveDotRegex::PermissiveDotRegexConfig" }
107+
108+
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof PermissiveDotStr }
109+
110+
override predicate isSink(DataFlow::Node sink) { sink instanceof CompileRegexSink }
111+
112+
override predicate isBarrier(DataFlow::Node node) {
113+
exists(
114+
MethodAccess ma, Field f // Pattern.compile(PATTERN, Pattern.DOTALL)
115+
|
116+
ma.getMethod() instanceof PatternCompileMethod and
117+
ma.getArgument(1) = f.getAnAccess() and
118+
f.hasName("DOTALL") and
119+
f.getDeclaringType() instanceof Pattern and
120+
node.asExpr() = ma.getArgument(0)
121+
)
122+
}
123+
}
124+
125+
/**
126+
* A taint-tracking configuration for untrusted user input used to match regular expressions.
127+
*/
128+
class MatchRegexConfiguration extends TaintTracking::Configuration {
129+
MatchRegexConfiguration() { this = "PermissiveDotRegex::MatchRegexConfiguration" }
130+
131+
override predicate isSource(DataFlow::Node source) {
132+
sourceNode(source, "uri-path") or // Servlet uri source
133+
source instanceof SpringUriInputParameterSource // Spring uri source
134+
}
135+
136+
override predicate isSink(DataFlow::Node sink) {
137+
sink instanceof MatchRegexSink and
138+
exists(
139+
Guard guard, Expr se, Expr ce // used in a condition to control url redirect, which is a typical security enforcement
140+
|
141+
(
142+
sink.asExpr() = ce.(MethodAccess).getQualifier() or
143+
sink.asExpr() = ce.(MethodAccess).getAnArgument() or
144+
sink.asExpr() = ce
145+
) and
146+
(
147+
DataFlow::localExprFlow(ce, guard.(MethodAccess).getQualifier()) or
148+
DataFlow::localExprFlow(ce, guard.(MethodAccess).getAnArgument())
149+
) and
150+
(
151+
DataFlow::exprNode(se) instanceof UrlRedirectSink or
152+
DataFlow::exprNode(se) instanceof SpringUrlRedirectSink
153+
) and
154+
guard.controls(se.getBasicBlock(), true)
155+
)
156+
}
157+
}
158+
159+
abstract class MatchRegexSink extends DataFlow::ExprNode { }
160+
161+
/**
162+
* A data flow sink to string match regular expressions.
163+
*/
164+
class StringMatchRegexSink extends MatchRegexSink {
165+
StringMatchRegexSink() {
166+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
167+
(
168+
m instanceof StringMatchMethod and
169+
ma.getQualifier() = this.asExpr()
170+
)
171+
)
172+
}
173+
}
174+
175+
/**
176+
* A data flow sink to `pattern.matches` regular expressions.
177+
*/
178+
class PatternMatchRegexSink extends MatchRegexSink {
179+
PatternMatchRegexSink() {
180+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
181+
(
182+
m instanceof PatternMatchMethod and
183+
ma.getArgument(1) = this.asExpr()
184+
)
185+
)
186+
}
187+
}
188+
189+
/**
190+
* A data flow sink to `pattern.matcher` match regular expressions.
191+
*/
192+
class PatternMatcherRegexSink extends MatchRegexSink {
193+
PatternMatcherRegexSink() {
194+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
195+
(
196+
m instanceof PatternMatcherMethod and
197+
ma.getArgument(0) = this.asExpr()
198+
)
199+
)
200+
}
201+
}

0 commit comments

Comments
 (0)