Skip to content

Commit 6545cff

Browse files
Jami CogswellJami Cogswell
authored andcommitted
add Pattern.quote sanitizer
1 parent 833c5ed commit 6545cff

File tree

2 files changed

+37
-10
lines changed

2 files changed

+37
-10
lines changed

java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,26 @@ class RegexSink extends DataFlow::ExprNode {
1111
(
1212
m.getDeclaringType() instanceof TypeString and
1313
(
14-
ma.getArgument(0) = this.asExpr() and
15-
// TODO: confirm if more/less than the below need to be handled
14+
ma.getArgument(0) = this.asExpr() and // ! combine this line with the below at least? e.g. TypeString and TypePattern both use it
15+
// ! test below more?
16+
// ! (are there already classes for these methods in a regex library?)
1617
m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
1718
)
1819
or
19-
// TODO: review Java Pattern API
20+
// ! make class for the below? (is there already a class for this and its methods in a regex library?)
2021
m.getDeclaringType().hasQualifiedName("java.util.regex", "Pattern") and
2122
(
2223
ma.getArgument(0) = this.asExpr() and
23-
// TODO: confirm if more/less than the below need to be handled
24+
// ! look into further: Pattern.matcher, .pattern() and .toString() as taint steps, .split and .splitAsStream
2425
m.hasName(["compile", "matches"])
2526
)
2627
or
27-
// TODO: read docs about regex APIs in Java
28+
// ! make class for the below? (is there already a class for this and its methods in a regex library?)
2829
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "RegExUtils") and
2930
(
3031
ma.getArgument(1) = this.asExpr() and
3132
m.getParameterType(1) instanceof TypeString and
32-
// TODO: confirm if more/less than the below need to be handled
33+
// ! test below more?
3334
m.hasName([
3435
"removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
3536
"replacePattern"
@@ -40,24 +41,38 @@ class RegexSink extends DataFlow::ExprNode {
4041
}
4142
}
4243

43-
// TODO: is this abstract class needed? Are there pre-existing sanitizer classes that can be used instead?
44+
// ! keep and rename to RegexInjectionSanitizer IF makes sense to have two sanitizers extending it?;
45+
// ! else, ask Tony/others about if stylistically better to keep it (see default example in LogInjection.qll, etc.)
46+
// ! maybe make abstract classes for source and sink as well (if you do this, mention it in PR description as an attempt to be similar to the other languages' implementations)
4447
abstract class Sanitizer extends DataFlow::ExprNode { }
4548

4649
/**
4750
* A call to a function whose name suggests that it escapes regular
4851
* expression meta-characters.
4952
*/
53+
// ! rename as DefaultRegexInjectionSanitizer?
5054
class RegExpSanitizationCall extends Sanitizer {
5155
RegExpSanitizationCall() {
5256
exists(string calleeName, string sanitize, string regexp |
5357
calleeName = this.asExpr().(Call).getCallee().getName() and
58+
// ! add test case for sanitize? I think current tests only check escape
5459
sanitize = "(?:escape|saniti[sz]e)" and // TODO: confirm this is sufficient
5560
regexp = "regexp?" // TODO: confirm this is sufficient
5661
|
5762
calleeName
5863
.regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
5964
".*)") // TODO: confirm this is sufficient
6065
)
66+
or
67+
// adds Pattern.quote() as a sanitizer
68+
// see https://rules.sonarsource.com/java/RSPEC-2631 and https://sensei.securecodewarrior.com/recipes/scw:java:regex-injection
69+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
70+
m.getDeclaringType().hasQualifiedName("java.util.regex", "Pattern") and
71+
(
72+
ma.getArgument(0) = this.asExpr() and
73+
m.hasName("quote")
74+
)
75+
)
6176
}
6277
}
6378

java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public boolean pattern1(javax.servlet.http.HttpServletRequest request) {
4444
Pattern pt = Pattern.compile(pattern); // $ hasRegexInjection
4545
Matcher matcher = pt.matcher(input);
4646

47-
return matcher.find(); // BAD // ! double-check that line 44 is the correct location for this alert (seems like it based on the old .expected file)
47+
return matcher.find();
4848
}
4949

5050
public boolean pattern2(javax.servlet.http.HttpServletRequest request) {
@@ -76,14 +76,15 @@ public boolean pattern5(javax.servlet.http.HttpServletRequest request) {
7676
String pattern = request.getParameter("pattern");
7777
String input = request.getParameter("input");
7878

79-
// GOOD: User input is sanitized before constructing the regex
79+
// Safe: User input is sanitized before constructing the regex
8080
return input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$");
8181
}
8282

8383
public boolean pattern6(javax.servlet.http.HttpServletRequest request) {
8484
String pattern = request.getParameter("pattern");
8585
String input = request.getParameter("input");
8686

87+
// TODO: add a "Safe" test for situation when this return value is stored in a variable, then the variable is used in the regex
8788
escapeSpecialRegexChars(pattern);
8889

8990
// BAD: the pattern is not really sanitized
@@ -92,6 +93,7 @@ public boolean pattern6(javax.servlet.http.HttpServletRequest request) {
9293

9394
Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
9495

96+
// TODO: check if any other inbuilt escape/sanitizers in Java APIs
9597
String escapeSpecialRegexChars(String str) {
9698
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
9799
}
@@ -136,7 +138,7 @@ public boolean apache6(javax.servlet.http.HttpServletRequest request) {
136138
String input = request.getParameter("input");
137139

138140
Pattern pt = (Pattern)(Object) pattern;
139-
return RegExUtils.replaceFirst(input, pt, "").length() > 0; // GOOD, Pattern compile is the sink instead
141+
return RegExUtils.replaceFirst(input, pt, "").length() > 0; // Safe: Pattern compile is the sink instead
140142
}
141143

142144
public boolean apache7(javax.servlet.http.HttpServletRequest request) {
@@ -145,4 +147,14 @@ public boolean apache7(javax.servlet.http.HttpServletRequest request) {
145147

146148
return RegExUtils.replacePattern(input, pattern, "").length() > 0; // $ hasRegexInjection
147149
}
150+
151+
// from https://rules.sonarsource.com/java/RSPEC-2631 to test for Pattern.quote as safe
152+
public boolean validate(javax.servlet.http.HttpServletRequest request) {
153+
String regex = request.getParameter("regex");
154+
String input = request.getParameter("input");
155+
156+
return input.matches(Pattern.quote(regex)); // Safe: with Pattern.quote, metacharacters or escape sequences will be given no special meaning
157+
}
148158
}
159+
160+
// ! see the following for potential additional test case ideas: https://www.baeldung.com/regular-expressions-java

0 commit comments

Comments
 (0)