add Pattern.quote sanitizer

This commit is contained in:
Jami Cogswell
2022-10-25 17:07:52 -04:00
parent 833c5edf06
commit 6545cff0ef
2 changed files with 37 additions and 10 deletions

View File

@@ -11,25 +11,26 @@ class RegexSink extends DataFlow::ExprNode {
(
m.getDeclaringType() instanceof TypeString and
(
ma.getArgument(0) = this.asExpr() and
// TODO: confirm if more/less than the below need to be handled
ma.getArgument(0) = this.asExpr() and // ! combine this line with the below at least? e.g. TypeString and TypePattern both use it
// ! test below more?
// ! (are there already classes for these methods in a regex library?)
m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
)
or
// TODO: review Java Pattern API
// ! make class for the below? (is there already a class for this and its methods in a regex library?)
m.getDeclaringType().hasQualifiedName("java.util.regex", "Pattern") and
(
ma.getArgument(0) = this.asExpr() and
// TODO: confirm if more/less than the below need to be handled
// ! look into further: Pattern.matcher, .pattern() and .toString() as taint steps, .split and .splitAsStream
m.hasName(["compile", "matches"])
)
or
// TODO: read docs about regex APIs in Java
// ! make class for the below? (is there already a class for this and its methods in a regex library?)
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "RegExUtils") and
(
ma.getArgument(1) = this.asExpr() and
m.getParameterType(1) instanceof TypeString and
// TODO: confirm if more/less than the below need to be handled
// ! test below more?
m.hasName([
"removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
"replacePattern"
@@ -40,17 +41,21 @@ class RegexSink extends DataFlow::ExprNode {
}
}
// TODO: is this abstract class needed? Are there pre-existing sanitizer classes that can be used instead?
// ! keep and rename to RegexInjectionSanitizer IF makes sense to have two sanitizers extending it?;
// ! else, ask Tony/others about if stylistically better to keep it (see default example in LogInjection.qll, etc.)
// ! 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)
abstract class Sanitizer extends DataFlow::ExprNode { }
/**
* A call to a function whose name suggests that it escapes regular
* expression meta-characters.
*/
// ! rename as DefaultRegexInjectionSanitizer?
class RegExpSanitizationCall extends Sanitizer {
RegExpSanitizationCall() {
exists(string calleeName, string sanitize, string regexp |
calleeName = this.asExpr().(Call).getCallee().getName() and
// ! add test case for sanitize? I think current tests only check escape
sanitize = "(?:escape|saniti[sz]e)" and // TODO: confirm this is sufficient
regexp = "regexp?" // TODO: confirm this is sufficient
|
@@ -58,6 +63,16 @@ class RegExpSanitizationCall extends Sanitizer {
.regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
".*)") // TODO: confirm this is sufficient
)
or
// adds Pattern.quote() as a sanitizer
// see https://rules.sonarsource.com/java/RSPEC-2631 and https://sensei.securecodewarrior.com/recipes/scw:java:regex-injection
exists(MethodAccess ma, Method m | m = ma.getMethod() |
m.getDeclaringType().hasQualifiedName("java.util.regex", "Pattern") and
(
ma.getArgument(0) = this.asExpr() and
m.hasName("quote")
)
)
}
}

View File

@@ -44,7 +44,7 @@ public class RegexInjectionTest extends HttpServlet {
Pattern pt = Pattern.compile(pattern); // $ hasRegexInjection
Matcher matcher = pt.matcher(input);
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)
return matcher.find();
}
public boolean pattern2(javax.servlet.http.HttpServletRequest request) {
@@ -76,7 +76,7 @@ public class RegexInjectionTest extends HttpServlet {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
// GOOD: User input is sanitized before constructing the regex
// Safe: User input is sanitized before constructing the regex
return input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$");
}
@@ -84,6 +84,7 @@ public class RegexInjectionTest extends HttpServlet {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
// TODO: add a "Safe" test for situation when this return value is stored in a variable, then the variable is used in the regex
escapeSpecialRegexChars(pattern);
// BAD: the pattern is not really sanitized
@@ -92,6 +93,7 @@ public class RegexInjectionTest extends HttpServlet {
Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
// TODO: check if any other inbuilt escape/sanitizers in Java APIs
String escapeSpecialRegexChars(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
}
@@ -136,7 +138,7 @@ public class RegexInjectionTest extends HttpServlet {
String input = request.getParameter("input");
Pattern pt = (Pattern)(Object) pattern;
return RegExUtils.replaceFirst(input, pt, "").length() > 0; // GOOD, Pattern compile is the sink instead
return RegExUtils.replaceFirst(input, pt, "").length() > 0; // Safe: Pattern compile is the sink instead
}
public boolean apache7(javax.servlet.http.HttpServletRequest request) {
@@ -145,4 +147,14 @@ public class RegexInjectionTest extends HttpServlet {
return RegExUtils.replacePattern(input, pattern, "").length() > 0; // $ hasRegexInjection
}
// from https://rules.sonarsource.com/java/RSPEC-2631 to test for Pattern.quote as safe
public boolean validate(javax.servlet.http.HttpServletRequest request) {
String regex = request.getParameter("regex");
String input = request.getParameter("input");
return input.matches(Pattern.quote(regex)); // Safe: with Pattern.quote, metacharacters or escape sequences will be given no special meaning
}
}
// ! see the following for potential additional test case ideas: https://www.baeldung.com/regular-expressions-java