create RegexInjection.qll file

This commit is contained in:
Jami Cogswell
2022-10-31 15:08:29 -04:00
parent f6f26fe6c5
commit 50d638d1b6
3 changed files with 99 additions and 88 deletions

View File

@@ -39,8 +39,7 @@ private predicate regexSinkKindInfo(string kind, boolean full, int strArg) {
}
/** A sink that is relevant for regex flow. */
class RegexFlowSink extends DataFlow::Node {
// ! switch back to private!!! - just testing if this sink is useful for regex injection as well
private class RegexFlowSink extends DataFlow::Node {
boolean full;
int strArg;

View File

@@ -0,0 +1,93 @@
/** Provides classes and predicates related to regex injection in Java. */
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.regex.RegexFlowConfigs
/**
* A data flow sink for untrusted user input used to construct regular expressions.
*/
abstract class Sink extends DataFlow::ExprNode { }
/**
* A sanitizer for untrusted user input used to construct regular expressions.
*/
abstract class Sanitizer extends DataFlow::ExprNode { }
// TODO: look into further: Pattern.matcher, .pattern() and .toString() as taint steps, .split and .splitAsStream
/**
* A data flow sink for untrusted user input used to construct regular expressions.
*/
private class RegexSink extends Sink {
RegexSink() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
(
m.getDeclaringType() instanceof TypeString and
m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
or
m.getDeclaringType() instanceof RegexPattern and
m.hasName(["compile", "matches"])
)
or
m.getDeclaringType() instanceof ApacheRegExUtils and
(
ma.getArgument(1) = this.asExpr() and
// only handles String param here because the other param option, Pattern, is already handled by `java.util.regex.Pattern` above
m.getParameterType(1) instanceof TypeString and
m.hasName([
"removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
"replacePattern"
])
)
)
}
}
/**
* A call to a function whose name suggests that it escapes regular
* expression meta-characters.
*/
class RegexInjectionSanitizer extends Sanitizer {
RegexInjectionSanitizer() {
exists(string calleeName, string sanitize, string regexp |
calleeName = this.asExpr().(Call).getCallee().getName() and
// TODO: add test case for sanitize? I think current tests only check escape
// TODO: should this be broader and only look for "escape|saniti[sz]e" and not "regexp?" as well? -- e.g. err on side of FNs?
sanitize = "(?:escape|saniti[sz]e)" and
regexp = "regexp?"
|
calleeName
.regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
".*)")
)
or
// adds Pattern.quote() as a sanitizer
// https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#quote-java.lang.String-: "Metacharacters or escape sequences in the input sequence will be given no special meaning."
// 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() instanceof RegexPattern and
(
ma.getArgument(0) = this.asExpr() and
m.hasName("quote")
)
)
}
}
// ******** HELPER CLASSES/METHODS (MAYBE MOVE ELSEWHERE?) ********
// TODO: move below to Regex.qll??
/** The Java class `java.util.regex.Pattern`. */
private class RegexPattern extends RefType {
RegexPattern() { this.hasQualifiedName("java.util.regex", "Pattern") }
}
// /** The Java class `java.util.regex.Matcher`. */
// private class RegexMatcher extends RefType {
// RegexMatcher() { this.hasQualifiedName("java.util.regex", "Matcher") }
// }
/** The Java class `org.apache.commons.lang3.RegExUtils`. */
private class ApacheRegExUtils extends RefType {
ApacheRegExUtils() { this.hasQualifiedName("org.apache.commons.lang3", "RegExUtils") }
}

View File

@@ -1,100 +1,19 @@
/** Provides configurations to be used in queries related to regex injection. */
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.regex.RegexFlowConfigs
/** The Java class `java.util.regex.Pattern`. */
private class RegexPattern extends RefType {
RegexPattern() { this.hasQualifiedName("java.util.regex", "Pattern") }
}
/** The Java class `java.util.regex.Matcher`. */
private class RegexMatcher extends RefType {
RegexMatcher() { this.hasQualifiedName("java.util.regex", "Matcher") }
}
/** The Java class `org.apache.commons.lang3.RegExUtils`. */
private class ApacheRegExUtils extends RefType {
ApacheRegExUtils() { this.hasQualifiedName("java.util.regex", "Matcher") }
}
// TODO: Look for above in pre-existing regex libraries again.
// TODO: look into further: Pattern.matcher, .pattern() and .toString() as taint steps, .split and .splitAsStream
/**
* A data flow sink for untrusted user input used to construct regular expressions.
*/
class RegexSink extends DataFlow::ExprNode {
RegexSink() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
(
m.getDeclaringType() instanceof TypeString and
m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
or
m.getDeclaringType() instanceof RegexPattern and
m.hasName(["compile", "matches"])
)
or
m.getDeclaringType() instanceof ApacheRegExUtils and
(
ma.getArgument(1) = this.asExpr() and
m.getParameterType(1) instanceof TypeString and // only does String here because other option is Pattern, but that's already handled by `java.util.regex.Pattern` above
m.hasName([
"removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
"replacePattern"
])
)
)
}
}
// ! 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
|
calleeName
.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() instanceof RegexPattern and
(
ma.getArgument(0) = this.asExpr() and
m.hasName("quote")
)
)
}
}
import semmle.code.java.security.RegexInjection
/**
* A taint-tracking configuration for untrusted user input used to construct regular expressions.
*/
class RegexInjectionConfiguration extends TaintTracking::Configuration {
RegexInjectionConfiguration() { this = "RegexInjectionConfiguration" }
RegexInjectionConfiguration() { this = "RegexInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) { sink instanceof RegexSink }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
// ! testing below RegexFlowSink from RegexFlowConfigs.qll
// ! extra results from jfinal with this... look into further...
// override predicate isSink(DataFlow::Node sink) { sink instanceof RegexFlowSink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
}