From 32b140045e81ff32ac451007f94b1c72bbdf2bad Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Sun, 23 Oct 2022 20:03:10 -0400
Subject: [PATCH 01/23] move files out of experimental
---
.../Security/CWE/CWE-730/RegexInjection.java | 2 +-
.../Security/CWE/CWE-730/RegexInjection.qhelp | 0
.../Security/CWE/CWE-730/RegexInjection.ql | 22 ++++++++++++++++---
.../security/CWE-730/RegexInjection.qlref | 1 -
.../security/CWE-730/RegexInjection.expected | 0
.../security/CWE-730/RegexInjection.java | 0
.../security/CWE-730/RegexInjection.qlref | 1 +
.../security/CWE-730/options-regexInjection} | 0
8 files changed, 21 insertions(+), 5 deletions(-)
rename java/ql/src/{experimental => }/Security/CWE/CWE-730/RegexInjection.java (99%)
rename java/ql/src/{experimental => }/Security/CWE/CWE-730/RegexInjection.qhelp (100%)
rename java/ql/src/{experimental => }/Security/CWE/CWE-730/RegexInjection.ql (71%)
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref
rename java/ql/test/{experimental => }/query-tests/security/CWE-730/RegexInjection.expected (100%)
rename java/ql/test/{experimental => }/query-tests/security/CWE-730/RegexInjection.java (100%)
create mode 100644 java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
rename java/ql/test/{experimental/query-tests/security/CWE-730/options => query-tests/security/CWE-730/options-regexInjection} (100%)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.java b/java/ql/src/Security/CWE/CWE-730/RegexInjection.java
similarity index 99%
rename from java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.java
rename to java/ql/src/Security/CWE/CWE-730/RegexInjection.java
index 387648a443e6..30b74df0a0ff 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.java
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.java
@@ -35,4 +35,4 @@ public String string2(@RequestParam(value = "input", defaultValue = "test") Stri
String escapeSpecialRegexChars(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
}
-}
\ No newline at end of file
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.qhelp b/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.qhelp
rename to java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
diff --git a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.ql b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
similarity index 71%
rename from java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.ql
rename to java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
index f60e5d9070bb..94bfccd9d3e2 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-730/RegexInjection.ql
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
@@ -27,19 +27,24 @@ 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
m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
)
or
+ // TODO: review Java Pattern API
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
m.hasName(["compile", "matches"])
)
or
+ // TODO: read docs about regex APIs in Java
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
m.hasName([
"removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
"replacePattern"
@@ -50,6 +55,7 @@ class RegexSink extends DataFlow::ExprNode {
}
}
+// TODO: is this abstract class needed? Are there pre-existing sanitizer classes that can be used instead?
abstract class Sanitizer extends DataFlow::ExprNode { }
/**
@@ -60,12 +66,12 @@ class RegExpSanitizationCall extends Sanitizer {
RegExpSanitizationCall() {
exists(string calleeName, string sanitize, string regexp |
calleeName = this.asExpr().(Call).getCallee().getName() and
- sanitize = "(?:escape|saniti[sz]e)" and
- regexp = "regexp?"
+ 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
)
}
}
@@ -87,3 +93,13 @@ from DataFlow::PathNode source, DataFlow::PathNode sink, RegexInjectionConfigura
where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This regular expression is constructed from a $@.",
source.getNode(), "user-provided value"
+// from MethodAccess ma
+// where
+// // ma.getMethod().hasName("startsWith") and // graphhopper
+// // ma.getFile().getBaseName() = "NavigateResource.java" // graphhopper
+// // ma.getMethod().hasName("substring") and // jfinal
+// // ma.getFile().getBaseName() = "FileManager.java" // jfinal
+// ma.getMethod().hasName("startsWith") and // roller
+// ma.getFile().getBaseName() = "PageServlet.java" // roller (or RegexUtil.java)
+// ProteinArraySignificanceTestJSON.java or MockRKeys.java for cbioportal
+// select ma, "method access"
diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref
deleted file mode 100644
index dca594b38d22..000000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.qlref
+++ /dev/null
@@ -1 +0,0 @@
-experimental/Security/CWE/CWE-730/RegexInjection.ql
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.expected b/java/ql/test/query-tests/security/CWE-730/RegexInjection.expected
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.expected
rename to java/ql/test/query-tests/security/CWE-730/RegexInjection.expected
diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.java b/java/ql/test/query-tests/security/CWE-730/RegexInjection.java
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-730/RegexInjection.java
rename to java/ql/test/query-tests/security/CWE-730/RegexInjection.java
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref b/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
new file mode 100644
index 000000000000..36e4f927f0a7
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-730/RegexInjection.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-730/options b/java/ql/test/query-tests/security/CWE-730/options-regexInjection
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-730/options
rename to java/ql/test/query-tests/security/CWE-730/options-regexInjection
From 25436fe55569f274da218a8d0235831d9828c995 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 13:46:24 -0400
Subject: [PATCH 02/23] update options and qlref files
---
java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref | 2 +-
java/ql/test/query-tests/security/CWE-730/options | 2 +-
.../ql/test/query-tests/security/CWE-730/options-regexInjection | 1 -
3 files changed, 2 insertions(+), 3 deletions(-)
delete mode 100644 java/ql/test/query-tests/security/CWE-730/options-regexInjection
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref b/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
index 36e4f927f0a7..c94b15751304 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
@@ -1 +1 @@
-experimental/Security/CWE/CWE-730/RegexInjection.ql
+Security/CWE/CWE-730/RegexInjection.ql
diff --git a/java/ql/test/query-tests/security/CWE-730/options b/java/ql/test/query-tests/security/CWE-730/options
index 2f7d22dc61ca..884cb21114c3 100644
--- a/java/ql/test/query-tests/security/CWE-730/options
+++ b/java/ql/test/query-tests/security/CWE-730/options
@@ -1 +1 @@
-// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/guava-30.0
\ No newline at end of file
+// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/guava-30.0:${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/apache-commons-lang3-3.7
diff --git a/java/ql/test/query-tests/security/CWE-730/options-regexInjection b/java/ql/test/query-tests/security/CWE-730/options-regexInjection
deleted file mode 100644
index 73f1b5a3c3eb..000000000000
--- a/java/ql/test/query-tests/security/CWE-730/options-regexInjection
+++ /dev/null
@@ -1 +0,0 @@
-// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/apache-commons-lang3-3.7
\ No newline at end of file
From 833c5edf067f01e9e4d3d62bc20c60811044b6a8 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 24 Oct 2022 14:29:06 -0400
Subject: [PATCH 03/23] move to .qll file and switch to InlineExpectations
tests
---
.../java/security/RegexInjectionQuery.qll | 75 ++++++++++++++++
.../Security/CWE/CWE-730/RegexInjection.ql | 85 +------------------
.../security/CWE-730/RegexInjection.expected | 73 ----------------
.../security/CWE-730/RegexInjection.qlref | 1 -
.../CWE-730/RegexInjectionTest.expected | 0
...Injection.java => RegexInjectionTest.java} | 34 ++++----
.../security/CWE-730/RegexInjectionTest.ql | 21 +++++
7 files changed, 114 insertions(+), 175 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
delete mode 100644 java/ql/test/query-tests/security/CWE-730/RegexInjection.expected
delete mode 100644 java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
create mode 100644 java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.expected
rename java/ql/test/query-tests/security/CWE-730/{RegexInjection.java => RegexInjectionTest.java} (82%)
create mode 100644 java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
new file mode 100644
index 000000000000..c91a49243ad4
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -0,0 +1,75 @@
+import java
+import semmle.code.java.dataflow.FlowSources
+import semmle.code.java.dataflow.TaintTracking
+
+/**
+ * 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() |
+ (
+ m.getDeclaringType() instanceof TypeString and
+ (
+ ma.getArgument(0) = this.asExpr() and
+ // TODO: confirm if more/less than the below need to be handled
+ m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
+ )
+ or
+ // TODO: review Java Pattern API
+ 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
+ m.hasName(["compile", "matches"])
+ )
+ or
+ // TODO: read docs about regex APIs in Java
+ 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
+ m.hasName([
+ "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
+ "replacePattern"
+ ])
+ )
+ )
+ )
+ }
+}
+
+// TODO: is this abstract class needed? Are there pre-existing sanitizer classes that can be used instead?
+abstract class Sanitizer extends DataFlow::ExprNode { }
+
+/**
+ * A call to a function whose name suggests that it escapes regular
+ * expression meta-characters.
+ */
+class RegExpSanitizationCall extends Sanitizer {
+ RegExpSanitizationCall() {
+ exists(string calleeName, string sanitize, string regexp |
+ calleeName = this.asExpr().(Call).getCallee().getName() and
+ 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
+ )
+ }
+}
+
+/**
+ * A taint-tracking configuration for untrusted user input used to construct regular expressions.
+ */
+class RegexInjectionConfiguration extends TaintTracking::Configuration {
+ RegexInjectionConfiguration() { this = "RegexInjectionConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof RegexSink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+}
diff --git a/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
index 94bfccd9d3e2..5f56eefb25ad 100644
--- a/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
@@ -13,93 +13,10 @@
*/
import java
-import semmle.code.java.dataflow.FlowSources
-import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.security.RegexInjectionQuery
import DataFlow::PathGraph
-/**
- * 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() |
- (
- m.getDeclaringType() instanceof TypeString and
- (
- ma.getArgument(0) = this.asExpr() and
- // TODO: confirm if more/less than the below need to be handled
- m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
- )
- or
- // TODO: review Java Pattern API
- 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
- m.hasName(["compile", "matches"])
- )
- or
- // TODO: read docs about regex APIs in Java
- 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
- m.hasName([
- "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
- "replacePattern"
- ])
- )
- )
- )
- }
-}
-
-// TODO: is this abstract class needed? Are there pre-existing sanitizer classes that can be used instead?
-abstract class Sanitizer extends DataFlow::ExprNode { }
-
-/**
- * A call to a function whose name suggests that it escapes regular
- * expression meta-characters.
- */
-class RegExpSanitizationCall extends Sanitizer {
- RegExpSanitizationCall() {
- exists(string calleeName, string sanitize, string regexp |
- calleeName = this.asExpr().(Call).getCallee().getName() and
- 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
- )
- }
-}
-
-/**
- * A taint-tracking configuration for untrusted user input used to construct regular expressions.
- */
-class RegexInjectionConfiguration extends TaintTracking::Configuration {
- RegexInjectionConfiguration() { this = "RegexInjectionConfiguration" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof RegexSink }
-
- override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
-}
-
from DataFlow::PathNode source, DataFlow::PathNode sink, RegexInjectionConfiguration c
where c.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "This regular expression is constructed from a $@.",
source.getNode(), "user-provided value"
-// from MethodAccess ma
-// where
-// // ma.getMethod().hasName("startsWith") and // graphhopper
-// // ma.getFile().getBaseName() = "NavigateResource.java" // graphhopper
-// // ma.getMethod().hasName("substring") and // jfinal
-// // ma.getFile().getBaseName() = "FileManager.java" // jfinal
-// ma.getMethod().hasName("startsWith") and // roller
-// ma.getFile().getBaseName() = "PageServlet.java" // roller (or RegexUtil.java)
-// ProteinArraySignificanceTestJSON.java or MockRKeys.java for cbioportal
-// select ma, "method access"
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjection.expected b/java/ql/test/query-tests/security/CWE-730/RegexInjection.expected
deleted file mode 100644
index a795f1591ada..000000000000
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjection.expected
+++ /dev/null
@@ -1,73 +0,0 @@
-edges
-| RegexInjection.java:13:22:13:52 | getParameter(...) : String | RegexInjection.java:16:26:16:47 | ... + ... |
-| RegexInjection.java:20:22:20:52 | getParameter(...) : String | RegexInjection.java:23:24:23:30 | pattern |
-| RegexInjection.java:27:22:27:52 | getParameter(...) : String | RegexInjection.java:30:31:30:37 | pattern |
-| RegexInjection.java:34:22:34:52 | getParameter(...) : String | RegexInjection.java:37:29:37:35 | pattern |
-| RegexInjection.java:41:22:41:52 | getParameter(...) : String | RegexInjection.java:44:34:44:40 | pattern |
-| RegexInjection.java:51:22:51:52 | getParameter(...) : String | RegexInjection.java:54:28:54:34 | pattern |
-| RegexInjection.java:58:22:58:52 | getParameter(...) : String | RegexInjection.java:61:28:61:34 | pattern |
-| RegexInjection.java:65:22:65:52 | getParameter(...) : String | RegexInjection.java:68:36:68:42 | pattern : String |
-| RegexInjection.java:68:32:68:43 | foo(...) : String | RegexInjection.java:68:26:68:52 | ... + ... |
-| RegexInjection.java:68:36:68:42 | pattern : String | RegexInjection.java:68:32:68:43 | foo(...) : String |
-| RegexInjection.java:68:36:68:42 | pattern : String | RegexInjection.java:71:14:71:23 | str : String |
-| RegexInjection.java:71:14:71:23 | str : String | RegexInjection.java:72:12:72:14 | str : String |
-| RegexInjection.java:84:22:84:52 | getParameter(...) : String | RegexInjection.java:90:26:90:47 | ... + ... |
-| RegexInjection.java:100:22:100:52 | getParameter(...) : String | RegexInjection.java:103:40:103:46 | pattern |
-| RegexInjection.java:107:22:107:52 | getParameter(...) : String | RegexInjection.java:110:42:110:48 | pattern |
-| RegexInjection.java:114:22:114:52 | getParameter(...) : String | RegexInjection.java:117:44:117:50 | pattern |
-| RegexInjection.java:121:22:121:52 | getParameter(...) : String | RegexInjection.java:124:41:124:47 | pattern |
-| RegexInjection.java:128:22:128:52 | getParameter(...) : String | RegexInjection.java:131:43:131:49 | pattern |
-| RegexInjection.java:143:22:143:52 | getParameter(...) : String | RegexInjection.java:146:45:146:51 | pattern |
-nodes
-| RegexInjection.java:13:22:13:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:16:26:16:47 | ... + ... | semmle.label | ... + ... |
-| RegexInjection.java:20:22:20:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:23:24:23:30 | pattern | semmle.label | pattern |
-| RegexInjection.java:27:22:27:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:30:31:30:37 | pattern | semmle.label | pattern |
-| RegexInjection.java:34:22:34:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:37:29:37:35 | pattern | semmle.label | pattern |
-| RegexInjection.java:41:22:41:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:44:34:44:40 | pattern | semmle.label | pattern |
-| RegexInjection.java:51:22:51:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:54:28:54:34 | pattern | semmle.label | pattern |
-| RegexInjection.java:58:22:58:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:61:28:61:34 | pattern | semmle.label | pattern |
-| RegexInjection.java:65:22:65:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:68:26:68:52 | ... + ... | semmle.label | ... + ... |
-| RegexInjection.java:68:32:68:43 | foo(...) : String | semmle.label | foo(...) : String |
-| RegexInjection.java:68:36:68:42 | pattern : String | semmle.label | pattern : String |
-| RegexInjection.java:71:14:71:23 | str : String | semmle.label | str : String |
-| RegexInjection.java:72:12:72:14 | str : String | semmle.label | str : String |
-| RegexInjection.java:84:22:84:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:90:26:90:47 | ... + ... | semmle.label | ... + ... |
-| RegexInjection.java:100:22:100:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:103:40:103:46 | pattern | semmle.label | pattern |
-| RegexInjection.java:107:22:107:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:110:42:110:48 | pattern | semmle.label | pattern |
-| RegexInjection.java:114:22:114:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:117:44:117:50 | pattern | semmle.label | pattern |
-| RegexInjection.java:121:22:121:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:124:41:124:47 | pattern | semmle.label | pattern |
-| RegexInjection.java:128:22:128:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:131:43:131:49 | pattern | semmle.label | pattern |
-| RegexInjection.java:143:22:143:52 | getParameter(...) : String | semmle.label | getParameter(...) : String |
-| RegexInjection.java:146:45:146:51 | pattern | semmle.label | pattern |
-subpaths
-| RegexInjection.java:68:36:68:42 | pattern : String | RegexInjection.java:71:14:71:23 | str : String | RegexInjection.java:72:12:72:14 | str : String | RegexInjection.java:68:32:68:43 | foo(...) : String |
-#select
-| RegexInjection.java:16:26:16:47 | ... + ... | RegexInjection.java:13:22:13:52 | getParameter(...) : String | RegexInjection.java:16:26:16:47 | ... + ... | This regular expression is constructed from a $@. | RegexInjection.java:13:22:13:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:23:24:23:30 | pattern | RegexInjection.java:20:22:20:52 | getParameter(...) : String | RegexInjection.java:23:24:23:30 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:20:22:20:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:30:31:30:37 | pattern | RegexInjection.java:27:22:27:52 | getParameter(...) : String | RegexInjection.java:30:31:30:37 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:27:22:27:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:37:29:37:35 | pattern | RegexInjection.java:34:22:34:52 | getParameter(...) : String | RegexInjection.java:37:29:37:35 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:34:22:34:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:44:34:44:40 | pattern | RegexInjection.java:41:22:41:52 | getParameter(...) : String | RegexInjection.java:44:34:44:40 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:41:22:41:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:54:28:54:34 | pattern | RegexInjection.java:51:22:51:52 | getParameter(...) : String | RegexInjection.java:54:28:54:34 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:51:22:51:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:61:28:61:34 | pattern | RegexInjection.java:58:22:58:52 | getParameter(...) : String | RegexInjection.java:61:28:61:34 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:58:22:58:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:68:26:68:52 | ... + ... | RegexInjection.java:65:22:65:52 | getParameter(...) : String | RegexInjection.java:68:26:68:52 | ... + ... | This regular expression is constructed from a $@. | RegexInjection.java:65:22:65:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:90:26:90:47 | ... + ... | RegexInjection.java:84:22:84:52 | getParameter(...) : String | RegexInjection.java:90:26:90:47 | ... + ... | This regular expression is constructed from a $@. | RegexInjection.java:84:22:84:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:103:40:103:46 | pattern | RegexInjection.java:100:22:100:52 | getParameter(...) : String | RegexInjection.java:103:40:103:46 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:100:22:100:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:110:42:110:48 | pattern | RegexInjection.java:107:22:107:52 | getParameter(...) : String | RegexInjection.java:110:42:110:48 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:107:22:107:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:117:44:117:50 | pattern | RegexInjection.java:114:22:114:52 | getParameter(...) : String | RegexInjection.java:117:44:117:50 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:114:22:114:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:124:41:124:47 | pattern | RegexInjection.java:121:22:121:52 | getParameter(...) : String | RegexInjection.java:124:41:124:47 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:121:22:121:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:131:43:131:49 | pattern | RegexInjection.java:128:22:128:52 | getParameter(...) : String | RegexInjection.java:131:43:131:49 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:128:22:128:52 | getParameter(...) | user-provided value |
-| RegexInjection.java:146:45:146:51 | pattern | RegexInjection.java:143:22:143:52 | getParameter(...) : String | RegexInjection.java:146:45:146:51 | pattern | This regular expression is constructed from a $@. | RegexInjection.java:143:22:143:52 | getParameter(...) | user-provided value |
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref b/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
deleted file mode 100644
index c94b15751304..000000000000
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjection.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-730/RegexInjection.ql
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.expected b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.expected
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjection.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
similarity index 82%
rename from java/ql/test/query-tests/security/CWE-730/RegexInjection.java
rename to java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index 2aace93aeb8c..fa86089515b2 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjection.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -8,64 +8,64 @@
import org.apache.commons.lang3.RegExUtils;
-public class RegexInjection extends HttpServlet {
+public class RegexInjectionTest extends HttpServlet {
public boolean string1(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.matches("^" + pattern + "=.*$"); // BAD
+ return input.matches("^" + pattern + "=.*$"); // $ hasRegexInjection
}
public boolean string2(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.split(pattern).length > 0; // BAD
+ return input.split(pattern).length > 0; // $ hasRegexInjection
}
public boolean string3(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.replaceFirst(pattern, "").length() > 0; // BAD
+ return input.replaceFirst(pattern, "").length() > 0; // $ hasRegexInjection
}
public boolean string4(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.replaceAll(pattern, "").length() > 0; // BAD
+ return input.replaceAll(pattern, "").length() > 0; // $ hasRegexInjection
}
public boolean pattern1(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- Pattern pt = Pattern.compile(pattern);
+ Pattern pt = Pattern.compile(pattern); // $ hasRegexInjection
Matcher matcher = pt.matcher(input);
- return matcher.find(); // BAD
+ 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)
}
public boolean pattern2(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return Pattern.compile(pattern).matcher(input).matches(); // BAD
+ return Pattern.compile(pattern).matcher(input).matches(); // $ hasRegexInjection
}
public boolean pattern3(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return Pattern.matches(pattern, input); // BAD
+ return Pattern.matches(pattern, input); // $ hasRegexInjection
}
public boolean pattern4(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.matches("^" + foo(pattern) + "=.*$"); // BAD
+ return input.matches("^" + foo(pattern) + "=.*$"); // $ hasRegexInjection
}
String foo(String str) {
@@ -87,7 +87,7 @@ public boolean pattern6(javax.servlet.http.HttpServletRequest request) {
escapeSpecialRegexChars(pattern);
// BAD: the pattern is not really sanitized
- return input.matches("^" + pattern + "=.*$");
+ return input.matches("^" + pattern + "=.*$"); // $ hasRegexInjection
}
Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
@@ -100,35 +100,35 @@ public boolean apache1(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return RegExUtils.removeAll(input, pattern).length() > 0; // BAD
+ return RegExUtils.removeAll(input, pattern).length() > 0; // $ hasRegexInjection
}
public boolean apache2(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return RegExUtils.removeFirst(input, pattern).length() > 0; // BAD
+ return RegExUtils.removeFirst(input, pattern).length() > 0; // $ hasRegexInjection
}
public boolean apache3(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return RegExUtils.removePattern(input, pattern).length() > 0; // BAD
+ return RegExUtils.removePattern(input, pattern).length() > 0; // $ hasRegexInjection
}
public boolean apache4(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return RegExUtils.replaceAll(input, pattern, "").length() > 0; // BAD
+ return RegExUtils.replaceAll(input, pattern, "").length() > 0; // $ hasRegexInjection
}
public boolean apache5(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return RegExUtils.replaceFirst(input, pattern, "").length() > 0; // BAD
+ return RegExUtils.replaceFirst(input, pattern, "").length() > 0; // $ hasRegexInjection
}
public boolean apache6(javax.servlet.http.HttpServletRequest request) {
@@ -143,6 +143,6 @@ public boolean apache7(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return RegExUtils.replacePattern(input, pattern, "").length() > 0; // BAD
+ return RegExUtils.replacePattern(input, pattern, "").length() > 0; // $ hasRegexInjection
}
}
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
new file mode 100644
index 000000000000..596decf98af9
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
@@ -0,0 +1,21 @@
+import java
+import TestUtilities.InlineExpectationsTest
+import semmle.code.java.security.RegexInjectionQuery
+
+//import semmle.code.java.security.regexp.PolynomialReDoSQuery
+class RegexInjectionTest extends InlineExpectationsTest {
+ RegexInjectionTest() { this = "RegexInjectionTest" }
+
+ override string getARelevantTag() { result = "hasRegexInjection" }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ tag = "hasRegexInjection" and
+ exists(DataFlow::PathNode source, DataFlow::PathNode sink, RegexInjectionConfiguration c |
+ c.hasFlowPath(source, sink)
+ |
+ location = sink.getNode().getLocation() and
+ element = sink.getNode().toString() and
+ value = ""
+ )
+ }
+}
From 6545cff0efc0767f7360a9ebed014b21eb4ef5d6 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 25 Oct 2022 17:07:52 -0400
Subject: [PATCH 04/23] add Pattern.quote sanitizer
---
.../java/security/RegexInjectionQuery.qll | 29 ++++++++++++++-----
.../security/CWE-730/RegexInjectionTest.java | 18 ++++++++++--
2 files changed, 37 insertions(+), 10 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index c91a49243ad4..a56d26b71831 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -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")
+ )
+ )
}
}
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index fa86089515b2..966893b053f1 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -44,7 +44,7 @@ public boolean pattern1(javax.servlet.http.HttpServletRequest request) {
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 boolean pattern5(javax.servlet.http.HttpServletRequest request) {
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 boolean pattern6(javax.servlet.http.HttpServletRequest request) {
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 boolean pattern6(javax.servlet.http.HttpServletRequest request) {
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 boolean apache6(javax.servlet.http.HttpServletRequest request) {
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 boolean apache7(javax.servlet.http.HttpServletRequest request) {
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
From 6ba7449df7d06341807301834c6fc052fc3d9aa5 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 27 Oct 2022 16:04:22 -0400
Subject: [PATCH 05/23] adjust imports
From 037a05cd66d5d0be4dd689b5a4d22bc349a320bf Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 27 Oct 2022 16:38:12 -0400
Subject: [PATCH 06/23] add classes for Pattern, Matcher, and RegExUtils
---
.../java/security/RegexInjectionQuery.qll | 58 ++++++++++---------
1 file changed, 32 insertions(+), 26 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index a56d26b71831..00553d78cbd1 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -2,40 +2,46 @@ import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
+/** 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: Are there already classes for any of below(above) in a pre-existing regex library?
+// 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
- (
- 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
- // ! 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
- // ! look into further: Pattern.matcher, .pattern() and .toString() as taint steps, .split and .splitAsStream
- m.hasName(["compile", "matches"])
- )
+ m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
or
- // ! 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
- // ! test below more?
- m.hasName([
- "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
- "replacePattern"
- ])
- )
+ 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 Patter, but that's already handled by `java.util.regex.Pattern` above
+ m.hasName([
+ "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
+ "replacePattern"
+ ])
)
)
}
@@ -67,7 +73,7 @@ class RegExpSanitizationCall extends Sanitizer {
// 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
+ m.getDeclaringType() instanceof RegexPattern and
(
ma.getArgument(0) = this.asExpr() and
m.hasName("quote")
From f6f26fe6c5b4dba43982d1b938dc0b00b2c39162 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 31 Oct 2022 09:53:28 -0400
Subject: [PATCH 07/23] refactor code; add change note
---
java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll | 3 ++-
.../lib/semmle/code/java/security/RegexInjectionQuery.qll | 8 ++++++--
java/ql/src/Security/CWE/CWE-730/RegexInjection.ql | 3 ++-
java/ql/src/change-notes/2022-10-28-regex-injection.md | 4 ++++
4 files changed, 14 insertions(+), 4 deletions(-)
create mode 100644 java/ql/src/change-notes/2022-10-28-regex-injection.md
diff --git a/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll b/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
index 8936de5a9239..08bf770b7640 100644
--- a/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
+++ b/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
@@ -39,7 +39,8 @@ private predicate regexSinkKindInfo(string kind, boolean full, int strArg) {
}
/** A sink that is relevant for regex flow. */
-private class RegexFlowSink extends DataFlow::Node {
+class RegexFlowSink extends DataFlow::Node {
+ // ! switch back to private!!! - just testing if this sink is useful for regex injection as well
boolean full;
int strArg;
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index 00553d78cbd1..b28a6e7d4d6d 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -1,6 +1,7 @@
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 {
@@ -17,7 +18,7 @@ private class ApacheRegExUtils extends RefType {
ApacheRegExUtils() { this.hasQualifiedName("java.util.regex", "Matcher") }
}
-// TODO: Are there already classes for any of below(above) in a pre-existing regex library?
+// 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.
@@ -37,7 +38,7 @@ class RegexSink extends DataFlow::ExprNode {
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 Patter, but that's already handled by `java.util.regex.Pattern` above
+ 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"
@@ -92,5 +93,8 @@ class RegexInjectionConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof RegexSink }
+ // ! 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 }
}
diff --git a/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
index 5f56eefb25ad..0f0175b30cd8 100644
--- a/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
@@ -1,10 +1,11 @@
/**
* @name Regular expression injection
- * @description User input should not be used in regular expressions without first being sanitized,
+ * @description User input should not be used in regular expressions without first being escaped,
* otherwise a malicious user may be able to provide a regex that could require
* exponential time on certain inputs.
* @kind path-problem
* @problem.severity error
+ * @security-severity 7.5
* @precision high
* @id java/regex-injection
* @tags security
diff --git a/java/ql/src/change-notes/2022-10-28-regex-injection.md b/java/ql/src/change-notes/2022-10-28-regex-injection.md
new file mode 100644
index 000000000000..f2376f34c8a0
--- /dev/null
+++ b/java/ql/src/change-notes/2022-10-28-regex-injection.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* Added a new query, `java/regex-injection`, to detect unescaped user input used in regular expressions.
From 50d638d1b6c9b055a15fe0e8bd24d84c732905cd Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 31 Oct 2022 15:08:29 -0400
Subject: [PATCH 08/23] create RegexInjection.qll file
---
.../code/java/regex/RegexFlowConfigs.qll | 3 +-
.../code/java/security/RegexInjection.qll | 93 +++++++++++++++++++
.../java/security/RegexInjectionQuery.qll | 91 +-----------------
3 files changed, 99 insertions(+), 88 deletions(-)
create mode 100644 java/ql/lib/semmle/code/java/security/RegexInjection.qll
diff --git a/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll b/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
index 08bf770b7640..8936de5a9239 100644
--- a/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
+++ b/java/ql/lib/semmle/code/java/regex/RegexFlowConfigs.qll
@@ -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;
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
new file mode 100644
index 000000000000..614f3ff67bc0
--- /dev/null
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -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") }
+}
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index b28a6e7d4d6d..e30ea091f3e0 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -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 }
}
From 91491d9a7b0929492951a99f0c84515b199d42a0 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 31 Oct 2022 18:14:39 -0400
Subject: [PATCH 09/23] refactor into more classes; add more test cases; add
LITERAL sanitizer
---
.../lib/semmle/code/java/frameworks/Regex.qll | 5 +
.../code/java/frameworks/apache/Lang.qll | 5 +
.../code/java/security/RegexInjection.qll | 130 ++++++++++--------
.../java/security/RegexInjectionQuery.qll | 4 +-
.../security/CWE-730/RegexInjectionTest.java | 53 ++++++-
5 files changed, 132 insertions(+), 65 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/frameworks/Regex.qll b/java/ql/lib/semmle/code/java/frameworks/Regex.qll
index 4e83981d8574..4915cfbeeed5 100644
--- a/java/ql/lib/semmle/code/java/frameworks/Regex.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/Regex.qll
@@ -2,6 +2,11 @@
private import semmle.code.java.dataflow.ExternalFlow
+/** The class `java.util.regex.Pattern`. */
+class TypeRegexPattern extends Class {
+ TypeRegexPattern() { this.hasQualifiedName("java.util.regex", "Pattern") }
+}
+
private class RegexModel extends SummaryModelCsv {
override predicate row(string s) {
s =
diff --git a/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll
index 84db672e9355..d2c94bcb376d 100644
--- a/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll
@@ -46,3 +46,8 @@ class TypeApacheSystemUtils extends Class {
this.hasQualifiedName(["org.apache.commons.lang", "org.apache.commons.lang3"], "SystemUtils")
}
}
+
+/** The class `org.apache.commons.lang3.RegExUtils`. */
+class TypeApacheRegExUtils extends Class {
+ TypeApacheRegExUtils() { this.hasQualifiedName("org.apache.commons.lang3", "RegExUtils") }
+}
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index 614f3ff67bc0..b2b64587fcdc 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -1,60 +1,37 @@
/** 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
+private import semmle.code.java.dataflow.DataFlow
+private import semmle.code.java.frameworks.Regex
+private import semmle.code.java.frameworks.apache.Lang
-/**
- * A data flow sink for untrusted user input used to construct regular expressions.
- */
+/** 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.
- */
+/** 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() {
+private class RegexInjectionSink extends Sink {
+ RegexInjectionSink() {
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"])
+ m instanceof StringRegexMethod or
+ m instanceof PatternRegexMethod
)
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"
- ])
- )
+ ma.getArgument(1) = this.asExpr() and
+ m instanceof ApacheRegExUtilsMethod
)
}
}
-/**
- * A call to a function whose name suggests that it escapes regular
- * expression meta-characters.
- */
-class RegexInjectionSanitizer extends Sanitizer {
+/** A call to a function which escapes regular expression meta-characters. */
+private class RegexInjectionSanitizer extends Sanitizer {
RegexInjectionSanitizer() {
+ // a function whose name suggests that it escapes regular expression meta-characters
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?"
|
@@ -63,31 +40,70 @@ class RegexInjectionSanitizer extends Sanitizer {
".*)")
)
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
+ // a call to the `Pattern.quote` method, which gives metacharacters or escape sequences no special meaning
exists(MethodAccess ma, Method m | m = ma.getMethod() |
- m.getDeclaringType() instanceof RegexPattern and
- (
- ma.getArgument(0) = this.asExpr() and
- m.hasName("quote")
- )
+ ma.getArgument(0) = this.asExpr() and
+ m instanceof PatternQuoteMethod
+ )
+ or
+ // use of Pattern.LITERAL flag with `Pattern.compile` which gives metacharacters or escape sequences no special meaning
+ exists(MethodAccess ma, Method m, Field field | m = ma.getMethod() |
+ ma.getArgument(0) = this.asExpr() and
+ m instanceof PatternRegexMethod and
+ m.hasName("compile") and
+ //ma.getArgument(1).toString() = "Pattern.LITERAL" and
+ field instanceof PatternLiteral and
+ ma.getArgument(1) = field.getAnAccess()
)
}
}
-// ******** 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 methods of the class `java.lang.String` that take a regular expression
+ * as a parameter.
+ */
+private class StringRegexMethod extends Method {
+ StringRegexMethod() {
+ this.getDeclaringType() instanceof TypeString and
+ this.hasName(["matches", "split", "replaceFirst", "replaceAll"])
+ }
+}
+
+/**
+ * The methods of the class `java.util.regex.Pattern` that take a regular
+ * expression as a parameter.
+ */
+private class PatternRegexMethod extends Method {
+ PatternRegexMethod() {
+ this.getDeclaringType() instanceof TypeRegexPattern and
+ this.hasName(["compile", "matches"])
+ }
}
-// /** 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") }
+/** The `quote` method of the `java.util.regex.Pattern` class. */
+private class PatternQuoteMethod extends Method {
+ PatternQuoteMethod() { this.hasName(["quote"]) }
+}
+
+/** The `LITERAL` field of the `java.util.regex.Pattern` class. */
+private class PatternLiteral extends Field {
+ PatternLiteral() {
+ this.getDeclaringType() instanceof TypeRegexPattern and
+ this.hasName("LITERAL")
+ }
+}
+
+/**
+ * The methods of the class `org.apache.commons.lang3.RegExUtils` that take
+ * a regular expression of type `String` as a parameter.
+ */
+private class ApacheRegExUtilsMethod extends Method {
+ ApacheRegExUtilsMethod() {
+ this.getDeclaringType() instanceof TypeApacheRegExUtils and
+ // only handles String param here because the other param option, Pattern, is already handled by `java.util.regex.Pattern`
+ this.getParameterType(1) instanceof TypeString and
+ this.hasName([
+ "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst", "replacePattern"
+ ])
+ }
}
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index e30ea091f3e0..ce4cab80bb45 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -5,9 +5,7 @@ import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.security.RegexInjection
-/**
- * A taint-tracking configuration for untrusted user input used to construct regular expressions.
- */
+/** A taint-tracking configuration for untrusted user input used to construct regular expressions. */
class RegexInjectionConfiguration extends TaintTracking::Configuration {
RegexInjectionConfiguration() { this = "RegexInjection" }
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index 966893b053f1..d914272278dc 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -84,20 +84,55 @@ public boolean pattern6(javax.servlet.http.HttpServletRequest request) {
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
return input.matches("^" + pattern + "=.*$"); // $ hasRegexInjection
}
+ public boolean pattern7(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ String input = request.getParameter("input");
+
+ String escapedPattern = escapeSpecialRegexChars(pattern);
+
+ // Safe: User input is sanitized before constructing the regex
+ return input.matches("^" + escapedPattern + "=.*$");
+ }
+
+ public boolean pattern8(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ String input = request.getParameter("input");
+
+ // Safe: User input is sanitized before constructing the regex
+ return input.matches("^" + sanitizeSpecialRegexChars(pattern) + "=.*$");
+ }
+
+ public boolean pattern9(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ String input = request.getParameter("input");
+
+ // Safe: User input is sanitized before constructing the regex
+ return input.matches("^" + sanitiseSpecialRegexChars(pattern) + "=.*$");
+ }
+
Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
- // TODO: check if any other inbuilt escape/sanitizers in Java APIs
+ // test `escape...regex`
String escapeSpecialRegexChars(String str) {
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
}
+ // test `sanitize...regex`
+ String sanitizeSpecialRegexChars(String str) {
+ return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
+ }
+
+ // test `sanitise...regex`
+ String sanitiseSpecialRegexChars(String str) {
+ return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
+ }
+
public boolean apache1(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
@@ -148,12 +183,20 @@ public boolean apache7(javax.servlet.http.HttpServletRequest request) {
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) {
+ // test `Pattern.quote` as safe
+ public boolean quoteTest(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
+ return input.matches(Pattern.quote(regex)); // Safe
+ }
+
+ // test `Pattern.LITERAL` as safe
+ public boolean literalTest(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ String input = request.getParameter("input");
+
+ return Pattern.compile(pattern, Pattern.LITERAL).matcher(input).matches(); // Safe
}
}
From 5b089bbb9c3143d04e0d862d2b0b4bf1eed8afa4 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 1 Nov 2022 11:02:36 -0400
Subject: [PATCH 10/23] split sanitizer into three
---
.../code/java/security/RegexInjection.qll | 32 +++++++++++++------
.../java/security/RegexInjectionQuery.qll | 2 +-
.../security/CWE-730/RegexInjectionTest.java | 4 +--
3 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index b2b64587fcdc..9430a55d0a56 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -9,7 +9,7 @@ private import semmle.code.java.frameworks.apache.Lang
abstract class Sink extends DataFlow::ExprNode { }
/** A sanitizer for untrusted user input used to construct regular expressions. */
-abstract class Sanitizer extends DataFlow::ExprNode { }
+abstract class RegexInjectionSanitizer extends DataFlow::ExprNode { }
private class RegexInjectionSink extends Sink {
RegexInjectionSink() {
@@ -26,10 +26,9 @@ private class RegexInjectionSink extends Sink {
}
}
-/** A call to a function which escapes regular expression meta-characters. */
-private class RegexInjectionSanitizer extends Sanitizer {
- RegexInjectionSanitizer() {
- // a function whose name suggests that it escapes regular expression meta-characters
+/** A call to a function whose name suggests that it escapes regular expression meta-characters. */
+private class RegexSanitizationCall extends RegexInjectionSanitizer {
+ RegexSanitizationCall() {
exists(string calleeName, string sanitize, string regexp |
calleeName = this.asExpr().(Call).getCallee().getName() and
sanitize = "(?:escape|saniti[sz]e)" and
@@ -39,19 +38,32 @@ private class RegexInjectionSanitizer extends Sanitizer {
.regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
".*)")
)
- or
- // a call to the `Pattern.quote` method, which gives metacharacters or escape sequences no special meaning
+ }
+}
+
+/**
+ * A call to the `Pattern.quote` method, which gives metacharacters or escape sequences
+ * no special meaning.
+ */
+private class PatternQuoteCall extends RegexInjectionSanitizer {
+ PatternQuoteCall() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
m instanceof PatternQuoteMethod
)
- or
- // use of Pattern.LITERAL flag with `Pattern.compile` which gives metacharacters or escape sequences no special meaning
+ }
+}
+
+/**
+ * Use of the `Pattern.LITERAL` flag with `Pattern.compile`, which gives metacharacters
+ * or escape sequences no special meaning.
+ */
+private class PatternLiteralFlag extends RegexInjectionSanitizer {
+ PatternLiteralFlag() {
exists(MethodAccess ma, Method m, Field field | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
m instanceof PatternRegexMethod and
m.hasName("compile") and
- //ma.getArgument(1).toString() = "Pattern.LITERAL" and
field instanceof PatternLiteral and
ma.getArgument(1) = field.getAnAccess()
)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index ce4cab80bb45..56337dc7d7b3 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -13,5 +13,5 @@ class RegexInjectionConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
- override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof RegexInjectionSanitizer }
}
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index d914272278dc..e9d6a1d7f3e8 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -183,7 +183,7 @@ public boolean apache7(javax.servlet.http.HttpServletRequest request) {
return RegExUtils.replacePattern(input, pattern, "").length() > 0; // $ hasRegexInjection
}
- // test `Pattern.quote` as safe
+ // test `Pattern.quote` sanitizer
public boolean quoteTest(javax.servlet.http.HttpServletRequest request) {
String regex = request.getParameter("regex");
String input = request.getParameter("input");
@@ -191,7 +191,7 @@ public boolean quoteTest(javax.servlet.http.HttpServletRequest request) {
return input.matches(Pattern.quote(regex)); // Safe
}
- // test `Pattern.LITERAL` as safe
+ // test `Pattern.LITERAL` sanitizer
public boolean literalTest(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
From 81ad10bab5bbc9c856e6fd22d2263436269f9b57 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 1 Nov 2022 11:07:28 -0400
Subject: [PATCH 11/23] update sink names
---
java/ql/lib/semmle/code/java/security/RegexInjection.qll | 6 +++---
.../lib/semmle/code/java/security/RegexInjectionQuery.qll | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index 9430a55d0a56..b5bf94e0f8d7 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -6,13 +6,13 @@ private import semmle.code.java.frameworks.Regex
private import semmle.code.java.frameworks.apache.Lang
/** A data flow sink for untrusted user input used to construct regular expressions. */
-abstract class Sink extends DataFlow::ExprNode { }
+abstract class RegexInjectionSink extends DataFlow::ExprNode { }
/** A sanitizer for untrusted user input used to construct regular expressions. */
abstract class RegexInjectionSanitizer extends DataFlow::ExprNode { }
-private class RegexInjectionSink extends Sink {
- RegexInjectionSink() {
+private class DefaultRegexInjectionSink extends RegexInjectionSink {
+ DefaultRegexInjectionSink() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
(
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
index 56337dc7d7b3..914b3222d9f7 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
@@ -11,7 +11,7 @@ class RegexInjectionConfiguration extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+ override predicate isSink(DataFlow::Node sink) { sink instanceof RegexInjectionSink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof RegexInjectionSanitizer }
}
From eb30e8fe9e798d87e1dec7ff72abf4bded331086 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 1 Nov 2022 11:23:55 -0400
Subject: [PATCH 12/23] move Pattern.quote and Pattern.LITERAL models to
Regex.qll
---
.../ql/lib/semmle/code/java/frameworks/Regex.qll | 16 ++++++++++++++++
.../semmle/code/java/security/RegexInjection.qll | 13 -------------
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/frameworks/Regex.qll b/java/ql/lib/semmle/code/java/frameworks/Regex.qll
index 4915cfbeeed5..fc157634b9b6 100644
--- a/java/ql/lib/semmle/code/java/frameworks/Regex.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/Regex.qll
@@ -7,6 +7,22 @@ class TypeRegexPattern extends Class {
TypeRegexPattern() { this.hasQualifiedName("java.util.regex", "Pattern") }
}
+/** The `quote` method of the `java.util.regex.Pattern` class. */
+class PatternQuoteMethod extends Method {
+ PatternQuoteMethod() {
+ this.getDeclaringType() instanceof TypeRegexPattern and
+ this.hasName(["quote"])
+ }
+}
+
+/** The `LITERAL` field of the `java.util.regex.Pattern` class. */
+class PatternLiteral extends Field {
+ PatternLiteral() {
+ this.getDeclaringType() instanceof TypeRegexPattern and
+ this.hasName("LITERAL")
+ }
+}
+
private class RegexModel extends SummaryModelCsv {
override predicate row(string s) {
s =
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index b5bf94e0f8d7..0a4bb1656be1 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -92,19 +92,6 @@ private class PatternRegexMethod extends Method {
}
}
-/** The `quote` method of the `java.util.regex.Pattern` class. */
-private class PatternQuoteMethod extends Method {
- PatternQuoteMethod() { this.hasName(["quote"]) }
-}
-
-/** The `LITERAL` field of the `java.util.regex.Pattern` class. */
-private class PatternLiteral extends Field {
- PatternLiteral() {
- this.getDeclaringType() instanceof TypeRegexPattern and
- this.hasName("LITERAL")
- }
-}
-
/**
* The methods of the class `org.apache.commons.lang3.RegExUtils` that take
* a regular expression of type `String` as a parameter.
From 32f7348d30b049fd11c1fb02bfd1c0a4a44db839 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 1 Nov 2022 14:50:58 -0400
Subject: [PATCH 13/23] update help file
---
.../code/java/security/RegexInjection.qll | 1 +
.../Security/CWE/CWE-730/RegexInjection.java | 46 ++++++-------------
.../Security/CWE/CWE-730/RegexInjection.qhelp | 11 +++--
3 files changed, 23 insertions(+), 35 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index 0a4bb1656be1..24520bab2302 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -11,6 +11,7 @@ abstract class RegexInjectionSink extends DataFlow::ExprNode { }
/** A sanitizer for untrusted user input used to construct regular expressions. */
abstract class RegexInjectionSanitizer extends DataFlow::ExprNode { }
+/** A method call that takes a regular expression as an argument. */
private class DefaultRegexInjectionSink extends RegexInjectionSink {
DefaultRegexInjectionSink() {
exists(MethodAccess ma, Method m | m = ma.getMethod() |
diff --git a/java/ql/src/Security/CWE/CWE-730/RegexInjection.java b/java/ql/src/Security/CWE/CWE-730/RegexInjection.java
index 30b74df0a0ff..673d5a2fa409 100644
--- a/java/ql/src/Security/CWE/CWE-730/RegexInjection.java
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.java
@@ -1,38 +1,22 @@
-package com.example.demo;
-
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-public class DemoApplication {
-
- @GetMapping("/string1")
- public String string1(@RequestParam(value = "input", defaultValue = "test") String input,
- @RequestParam(value = "pattern", defaultValue = ".*") String pattern) {
- // BAD: Unsanitized user input is used to construct a regular expression
- if (input.matches("^" + pattern + "=.*$"))
- return "match!";
-
- return "doesn't match!";
- }
+public class RegexInjectionDemo extends HttpServlet {
- @GetMapping("/string2")
- public String string2(@RequestParam(value = "input", defaultValue = "test") String input,
- @RequestParam(value = "pattern", defaultValue = ".*") String pattern) {
- // GOOD: User input is sanitized before constructing the regex
- if (input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$"))
- return "match!";
+ public boolean badExample(javax.servlet.http.HttpServletRequest request) {
+ String regex = request.getParameter("regex");
+ String input = request.getParameter("input");
- return "doesn't match!";
- }
+ // BAD: Unsanitized user input is used to construct a regular expression
+ return input.matches(regex);
+ }
- Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
+ public boolean goodExample(javax.servlet.http.HttpServletRequest request) {
+ String regex = request.getParameter("regex");
+ String input = request.getParameter("input");
- String escapeSpecialRegexChars(String str) {
- return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
- }
+ // GOOD: User input is sanitized before constructing the regex
+ return input.matches(Pattern.quote(regex));
+ }
}
diff --git a/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp b/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
index 6cd80b52f755..fc8ab33ca80a 100644
--- a/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
@@ -15,25 +15,25 @@ perform a Denial of Service attack.
Before embedding user input into a regular expression, use a sanitization function
-to escape meta-characters that have special meaning.
+such as Pattern.quote
to escape meta-characters that have special meaning.
-The following example shows a HTTP request parameter that is used to construct a regular expression:
+The following example shows an HTTP request parameter that is used to construct a regular expression.
-
In the first case the user-provided regex is not escaped.
If a malicious user provides a regex that has exponential worst case performance,
then this could lead to a Denial of Service.
-In the second case, the user input is escaped using escapeSpecialRegexChars
before being included
+In the second case, the user input is escaped using Pattern.quote
before being included
in the regular expression. This ensures that the user cannot insert characters which have a special
meaning in regular expressions.
+
@@ -44,5 +44,8 @@ OWASP:
Wikipedia: ReDoS.
+
+Java API Specification: Pattern.quote.
+
From 5dcd3b2c0fef6692f2484ec9ec192fbccf259134 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 1 Nov 2022 15:21:23 -0400
Subject: [PATCH 14/23] clean up files
---
.../lib/semmle/code/java/security/RegexInjection.qll | 10 +++++-----
.../security/CWE-730/RegexInjectionTest.java | 2 --
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index 24520bab2302..66e315020b2d 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -43,7 +43,7 @@ private class RegexSanitizationCall extends RegexInjectionSanitizer {
}
/**
- * A call to the `Pattern.quote` method, which gives metacharacters or escape sequences
+ * A call to the `Pattern.quote` method, which gives meta-characters or escape sequences
* no special meaning.
*/
private class PatternQuoteCall extends RegexInjectionSanitizer {
@@ -56,7 +56,7 @@ private class PatternQuoteCall extends RegexInjectionSanitizer {
}
/**
- * Use of the `Pattern.LITERAL` flag with `Pattern.compile`, which gives metacharacters
+ * Use of the `Pattern.LITERAL` flag with `Pattern.compile`, which gives meta-characters
* or escape sequences no special meaning.
*/
private class PatternLiteralFlag extends RegexInjectionSanitizer {
@@ -72,7 +72,7 @@ private class PatternLiteralFlag extends RegexInjectionSanitizer {
}
/**
- * The methods of the class `java.lang.String` that take a regular expression
+ * A method of the class `java.lang.String` that takes a regular expression
* as a parameter.
*/
private class StringRegexMethod extends Method {
@@ -83,7 +83,7 @@ private class StringRegexMethod extends Method {
}
/**
- * The methods of the class `java.util.regex.Pattern` that take a regular
+ * A method of the class `java.util.regex.Pattern` that takes a regular
* expression as a parameter.
*/
private class PatternRegexMethod extends Method {
@@ -94,7 +94,7 @@ private class PatternRegexMethod extends Method {
}
/**
- * The methods of the class `org.apache.commons.lang3.RegExUtils` that take
+ * A methods of the class `org.apache.commons.lang3.RegExUtils` that takes
* a regular expression of type `String` as a parameter.
*/
private class ApacheRegExUtilsMethod extends Method {
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index e9d6a1d7f3e8..d78c8930e87c 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -199,5 +199,3 @@ public boolean literalTest(javax.servlet.http.HttpServletRequest request) {
return Pattern.compile(pattern, Pattern.LITERAL).matcher(input).matches(); // Safe
}
}
-
-// ! see the following for potential additional test case ideas: https://www.baeldung.com/regular-expressions-java
From be548c13e1baaeeee216ab1087380f1f7b15dcc9 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 3 Nov 2022 13:16:47 -0400
Subject: [PATCH 15/23] switch sink to use csv models
---
.../lib/semmle/code/java/frameworks/Regex.qll | 6 +-
.../code/java/frameworks/apache/Lang.qll | 5 --
.../code/java/regex/RegexFlowModels.qll | 6 ++
.../code/java/security/RegexInjection.qll | 83 ++++++-------------
4 files changed, 35 insertions(+), 65 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/frameworks/Regex.qll b/java/ql/lib/semmle/code/java/frameworks/Regex.qll
index fc157634b9b6..790255c97031 100644
--- a/java/ql/lib/semmle/code/java/frameworks/Regex.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/Regex.qll
@@ -11,13 +11,13 @@ class TypeRegexPattern extends Class {
class PatternQuoteMethod extends Method {
PatternQuoteMethod() {
this.getDeclaringType() instanceof TypeRegexPattern and
- this.hasName(["quote"])
+ this.hasName("quote")
}
}
/** The `LITERAL` field of the `java.util.regex.Pattern` class. */
-class PatternLiteral extends Field {
- PatternLiteral() {
+class PatternLiteralField extends Field {
+ PatternLiteralField() {
this.getDeclaringType() instanceof TypeRegexPattern and
this.hasName("LITERAL")
}
diff --git a/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll b/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll
index d2c94bcb376d..84db672e9355 100644
--- a/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/apache/Lang.qll
@@ -46,8 +46,3 @@ class TypeApacheSystemUtils extends Class {
this.hasQualifiedName(["org.apache.commons.lang", "org.apache.commons.lang3"], "SystemUtils")
}
}
-
-/** The class `org.apache.commons.lang3.RegExUtils`. */
-class TypeApacheRegExUtils extends Class {
- TypeApacheRegExUtils() { this.hasQualifiedName("org.apache.commons.lang3", "RegExUtils") }
-}
diff --git a/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll b/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
index de0b5465fe49..2e6be8b9bab1 100644
--- a/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
+++ b/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
@@ -27,6 +27,12 @@ private class RegexSinkCsv extends SinkModelCsv {
"com.google.common.base;Splitter;false;split;(CharSequence);;Argument[-1];regex-use[0];manual",
"com.google.common.base;Splitter;false;splitToList;(CharSequence);;Argument[-1];regex-use[0];manual",
"com.google.common.base;Splitter$MapSplitter;false;split;(CharSequence);;Argument[-1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;removeAll;(String,String);;Argument[1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;removeFirst;(String,String);;Argument[1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;removePattern;(String,String);;Argument[1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;replaceAll;(String,String,String);;Argument[1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;replaceFirst;(String,String,String);;Argument[1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;replacePattern;(String,String,String);;Argument[1];regex-use[0];manual",
]
}
}
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index 66e315020b2d..2bfeeaeb43da 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -3,7 +3,8 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.frameworks.Regex
-private import semmle.code.java.frameworks.apache.Lang
+//private import semmle.code.java.frameworks.apache.Lang
+private import semmle.code.java.regex.RegexFlowModels
/** A data flow sink for untrusted user input used to construct regular expressions. */
abstract class RegexInjectionSink extends DataFlow::ExprNode { }
@@ -14,15 +15,11 @@ abstract class RegexInjectionSanitizer extends DataFlow::ExprNode { }
/** A method call that takes a regular expression as an argument. */
private class DefaultRegexInjectionSink extends RegexInjectionSink {
DefaultRegexInjectionSink() {
- exists(MethodAccess ma, Method m | m = ma.getMethod() |
- ma.getArgument(0) = this.asExpr() and
- (
- m instanceof StringRegexMethod or
- m instanceof PatternRegexMethod
- )
- or
- ma.getArgument(1) = this.asExpr() and
- m instanceof ApacheRegExUtilsMethod
+ exists(string kind |
+ kind.matches([
+ "regex-use[]", "regex-use[f1]", "regex-use[f-1]", "regex-use[-1]", "regex-use[0]"
+ ]) and
+ sinkNode(this, kind)
)
}
}
@@ -30,20 +27,29 @@ private class DefaultRegexInjectionSink extends RegexInjectionSink {
/** A call to a function whose name suggests that it escapes regular expression meta-characters. */
private class RegexSanitizationCall extends RegexInjectionSanitizer {
RegexSanitizationCall() {
- exists(string calleeName, string sanitize, string regexp |
+ // original
+ // exists(string calleeName, string sanitize, string regexp |
+ // calleeName = this.asExpr().(Call).getCallee().getName() and
+ // sanitize = "(?:escape|saniti[sz]e)" and
+ // regexp = "regexp?"
+ // |
+ // calleeName
+ // .regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
+ // ".*)")
+ // )
+ // without regexp
+ exists(string calleeName, string sanitize |
calleeName = this.asExpr().(Call).getCallee().getName() and
- sanitize = "(?:escape|saniti[sz]e)" and
- regexp = "regexp?"
+ sanitize = "(?:escape|saniti[sz]e)"
|
- calleeName
- .regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
- ".*)")
+ calleeName.regexpMatch("(?i)(.*" + sanitize + ".*)")
+ //calleeName.matches("handleEscapes")
)
}
}
/**
- * A call to the `Pattern.quote` method, which gives meta-characters or escape sequences
+ * A call to the `Pattern.quote` method, which gives metacharacters or escape sequences
* no special meaning.
*/
private class PatternQuoteCall extends RegexInjectionSanitizer {
@@ -56,54 +62,17 @@ private class PatternQuoteCall extends RegexInjectionSanitizer {
}
/**
- * Use of the `Pattern.LITERAL` flag with `Pattern.compile`, which gives meta-characters
+ * Use of the `Pattern.LITERAL` flag with `Pattern.compile`, which gives metacharacters
* or escape sequences no special meaning.
*/
private class PatternLiteralFlag extends RegexInjectionSanitizer {
PatternLiteralFlag() {
exists(MethodAccess ma, Method m, Field field | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
- m instanceof PatternRegexMethod and
+ m.getDeclaringType() instanceof TypeRegexPattern and
m.hasName("compile") and
- field instanceof PatternLiteral and
+ field instanceof PatternLiteralField and
ma.getArgument(1) = field.getAnAccess()
)
}
}
-
-/**
- * A method of the class `java.lang.String` that takes a regular expression
- * as a parameter.
- */
-private class StringRegexMethod extends Method {
- StringRegexMethod() {
- this.getDeclaringType() instanceof TypeString and
- this.hasName(["matches", "split", "replaceFirst", "replaceAll"])
- }
-}
-
-/**
- * A method of the class `java.util.regex.Pattern` that takes a regular
- * expression as a parameter.
- */
-private class PatternRegexMethod extends Method {
- PatternRegexMethod() {
- this.getDeclaringType() instanceof TypeRegexPattern and
- this.hasName(["compile", "matches"])
- }
-}
-
-/**
- * A methods of the class `org.apache.commons.lang3.RegExUtils` that takes
- * a regular expression of type `String` as a parameter.
- */
-private class ApacheRegExUtilsMethod extends Method {
- ApacheRegExUtilsMethod() {
- this.getDeclaringType() instanceof TypeApacheRegExUtils and
- // only handles String param here because the other param option, Pattern, is already handled by `java.util.regex.Pattern`
- this.getParameterType(1) instanceof TypeString and
- this.hasName([
- "removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst", "replacePattern"
- ])
- }
-}
From 54020013626ce0d973d26cf01e9b69712455585f Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 3 Nov 2022 13:25:08 -0400
Subject: [PATCH 16/23] remove original sanitizer
---
.../code/java/security/RegexInjection.qll | 25 --------
.../security/CWE-730/RegexInjectionTest.java | 61 -------------------
2 files changed, 86 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
index 2bfeeaeb43da..859fac475d17 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/RegexInjection.qll
@@ -3,7 +3,6 @@
import java
private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.frameworks.Regex
-//private import semmle.code.java.frameworks.apache.Lang
private import semmle.code.java.regex.RegexFlowModels
/** A data flow sink for untrusted user input used to construct regular expressions. */
@@ -24,30 +23,6 @@ private class DefaultRegexInjectionSink extends RegexInjectionSink {
}
}
-/** A call to a function whose name suggests that it escapes regular expression meta-characters. */
-private class RegexSanitizationCall extends RegexInjectionSanitizer {
- RegexSanitizationCall() {
- // original
- // exists(string calleeName, string sanitize, string regexp |
- // calleeName = this.asExpr().(Call).getCallee().getName() and
- // sanitize = "(?:escape|saniti[sz]e)" and
- // regexp = "regexp?"
- // |
- // calleeName
- // .regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
- // ".*)")
- // )
- // without regexp
- exists(string calleeName, string sanitize |
- calleeName = this.asExpr().(Call).getCallee().getName() and
- sanitize = "(?:escape|saniti[sz]e)"
- |
- calleeName.regexpMatch("(?i)(.*" + sanitize + ".*)")
- //calleeName.matches("handleEscapes")
- )
- }
-}
-
/**
* A call to the `Pattern.quote` method, which gives metacharacters or escape sequences
* no special meaning.
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index d78c8930e87c..37ea99cd4f01 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -72,67 +72,6 @@ String foo(String str) {
return str;
}
- public boolean pattern5(javax.servlet.http.HttpServletRequest request) {
- String pattern = request.getParameter("pattern");
- String input = request.getParameter("input");
-
- // Safe: User input is sanitized before constructing the regex
- return input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$");
- }
-
- public boolean pattern6(javax.servlet.http.HttpServletRequest request) {
- String pattern = request.getParameter("pattern");
- String input = request.getParameter("input");
-
- escapeSpecialRegexChars(pattern);
-
- // BAD: the pattern is not really sanitized
- return input.matches("^" + pattern + "=.*$"); // $ hasRegexInjection
- }
-
- public boolean pattern7(javax.servlet.http.HttpServletRequest request) {
- String pattern = request.getParameter("pattern");
- String input = request.getParameter("input");
-
- String escapedPattern = escapeSpecialRegexChars(pattern);
-
- // Safe: User input is sanitized before constructing the regex
- return input.matches("^" + escapedPattern + "=.*$");
- }
-
- public boolean pattern8(javax.servlet.http.HttpServletRequest request) {
- String pattern = request.getParameter("pattern");
- String input = request.getParameter("input");
-
- // Safe: User input is sanitized before constructing the regex
- return input.matches("^" + sanitizeSpecialRegexChars(pattern) + "=.*$");
- }
-
- public boolean pattern9(javax.servlet.http.HttpServletRequest request) {
- String pattern = request.getParameter("pattern");
- String input = request.getParameter("input");
-
- // Safe: User input is sanitized before constructing the regex
- return input.matches("^" + sanitiseSpecialRegexChars(pattern) + "=.*$");
- }
-
- Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
-
- // test `escape...regex`
- String escapeSpecialRegexChars(String str) {
- return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
- }
-
- // test `sanitize...regex`
- String sanitizeSpecialRegexChars(String str) {
- return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
- }
-
- // test `sanitise...regex`
- String sanitiseSpecialRegexChars(String str) {
- return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
- }
-
public boolean apache1(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
From 695d6f0e4e5389dbf6864017b5be9daeac7002df Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 3 Nov 2022 13:42:41 -0400
Subject: [PATCH 17/23] move files to regexp directory
---
.../semmle/code/java/security/{ => regexp}/RegexInjection.qll | 0
.../code/java/security/{ => regexp}/RegexInjectionQuery.qll | 2 +-
java/ql/src/Security/CWE/CWE-730/RegexInjection.ql | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename java/ql/lib/semmle/code/java/security/{ => regexp}/RegexInjection.qll (100%)
rename java/ql/lib/semmle/code/java/security/{ => regexp}/RegexInjectionQuery.qll (92%)
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
similarity index 100%
rename from java/ql/lib/semmle/code/java/security/RegexInjection.qll
rename to java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
diff --git a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll b/java/ql/lib/semmle/code/java/security/regexp/RegexInjectionQuery.qll
similarity index 92%
rename from java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
rename to java/ql/lib/semmle/code/java/security/regexp/RegexInjectionQuery.qll
index 914b3222d9f7..e2453c08dcae 100644
--- a/java/ql/lib/semmle/code/java/security/RegexInjectionQuery.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/RegexInjectionQuery.qll
@@ -3,7 +3,7 @@
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
-import semmle.code.java.security.RegexInjection
+import semmle.code.java.security.regexp.RegexInjection
/** A taint-tracking configuration for untrusted user input used to construct regular expressions. */
class RegexInjectionConfiguration extends TaintTracking::Configuration {
diff --git a/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
index 0f0175b30cd8..820e0949d220 100644
--- a/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
@@ -14,7 +14,7 @@
*/
import java
-import semmle.code.java.security.RegexInjectionQuery
+import semmle.code.java.security.regexp.RegexInjectionQuery
import DataFlow::PathGraph
from DataFlow::PathNode source, DataFlow::PathNode sink, RegexInjectionConfiguration c
From 0e93e7112732e735d99e85c74910b7a5185a75ea Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 3 Nov 2022 14:14:32 -0400
Subject: [PATCH 18/23] update tests
---
.../security/CWE-730/RegexInjectionTest.java | 18 ++++++++++++++++--
.../security/CWE-730/RegexInjectionTest.ql | 2 +-
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index 37ea99cd4f01..57a1ea926679 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -27,13 +27,20 @@ public boolean string3(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.replaceFirst(pattern, "").length() > 0; // $ hasRegexInjection
+ return input.split(pattern, 0).length > 0; // $ hasRegexInjection
}
public boolean string4(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
+ return input.replaceFirst(pattern, "").length() > 0; // $ hasRegexInjection
+ }
+
+ public boolean string5(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ String input = request.getParameter("input");
+
return input.replaceAll(pattern, "").length() > 0; // $ hasRegexInjection
}
@@ -58,13 +65,20 @@ public boolean pattern3(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return Pattern.matches(pattern, input); // $ hasRegexInjection
+ return Pattern.compile(pattern, 0).matcher(input).matches(); // $ hasRegexInjection
}
public boolean pattern4(javax.servlet.http.HttpServletRequest request) {
String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
+ return Pattern.matches(pattern, input); // $ hasRegexInjection
+ }
+
+ public boolean pattern5(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ String input = request.getParameter("input");
+
return input.matches("^" + foo(pattern) + "=.*$"); // $ hasRegexInjection
}
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
index 596decf98af9..9603ad37b8f4 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
@@ -1,6 +1,6 @@
import java
import TestUtilities.InlineExpectationsTest
-import semmle.code.java.security.RegexInjectionQuery
+import semmle.code.java.security.regexp.RegexInjectionQuery
//import semmle.code.java.security.regexp.PolynomialReDoSQuery
class RegexInjectionTest extends InlineExpectationsTest {
From e49c5213ca0b32ce38c65a5fb5df16c2db7b0758 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Thu, 3 Nov 2022 14:21:37 -0400
Subject: [PATCH 19/23] update change note
---
java/ql/src/change-notes/2022-10-28-regex-injection.md | 4 ----
java/ql/src/change-notes/2022-11-03-regex-injection.md | 4 ++++
2 files changed, 4 insertions(+), 4 deletions(-)
delete mode 100644 java/ql/src/change-notes/2022-10-28-regex-injection.md
create mode 100644 java/ql/src/change-notes/2022-11-03-regex-injection.md
diff --git a/java/ql/src/change-notes/2022-10-28-regex-injection.md b/java/ql/src/change-notes/2022-10-28-regex-injection.md
deleted file mode 100644
index f2376f34c8a0..000000000000
--- a/java/ql/src/change-notes/2022-10-28-regex-injection.md
+++ /dev/null
@@ -1,4 +0,0 @@
----
-category: newQuery
----
-* Added a new query, `java/regex-injection`, to detect unescaped user input used in regular expressions.
diff --git a/java/ql/src/change-notes/2022-11-03-regex-injection.md b/java/ql/src/change-notes/2022-11-03-regex-injection.md
new file mode 100644
index 000000000000..759aa2a86e1e
--- /dev/null
+++ b/java/ql/src/change-notes/2022-11-03-regex-injection.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* The query, `java/regex-injection`, has been promoted from experimental to the main query pack. Its results will now appear by default. This query was originally [submitted as an experimental query by @edvraa](https://github.com/github/codeql/pull/5704).
From b99a1d2cd9924b7261a205eb82ecbcfb1ae2b160 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Fri, 4 Nov 2022 11:41:54 -0400
Subject: [PATCH 20/23] update sink and tests
---
.../semmle/code/java/regex/RegexFlowModels.qll | 12 ++++++------
.../code/java/security/regexp/RegexInjection.qll | 4 +---
.../security/CWE-730/RegexInjectionTest.java | 16 ++++++++++++++--
3 files changed, 21 insertions(+), 11 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll b/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
index 2e6be8b9bab1..20ba2c14dc8f 100644
--- a/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
+++ b/java/ql/lib/semmle/code/java/regex/RegexFlowModels.qll
@@ -27,12 +27,12 @@ private class RegexSinkCsv extends SinkModelCsv {
"com.google.common.base;Splitter;false;split;(CharSequence);;Argument[-1];regex-use[0];manual",
"com.google.common.base;Splitter;false;splitToList;(CharSequence);;Argument[-1];regex-use[0];manual",
"com.google.common.base;Splitter$MapSplitter;false;split;(CharSequence);;Argument[-1];regex-use[0];manual",
- "org.apache.commons.lang3;RegExUtils;false;removeAll;(String,String);;Argument[1];regex-use[0];manual",
- "org.apache.commons.lang3;RegExUtils;false;removeFirst;(String,String);;Argument[1];regex-use[0];manual",
- "org.apache.commons.lang3;RegExUtils;false;removePattern;(String,String);;Argument[1];regex-use[0];manual",
- "org.apache.commons.lang3;RegExUtils;false;replaceAll;(String,String,String);;Argument[1];regex-use[0];manual",
- "org.apache.commons.lang3;RegExUtils;false;replaceFirst;(String,String,String);;Argument[1];regex-use[0];manual",
- "org.apache.commons.lang3;RegExUtils;false;replacePattern;(String,String,String);;Argument[1];regex-use[0];manual",
+ "org.apache.commons.lang3;RegExUtils;false;removeAll;(String,String);;Argument[1];regex-use;manual",
+ "org.apache.commons.lang3;RegExUtils;false;removeFirst;(String,String);;Argument[1];regex-use;manual",
+ "org.apache.commons.lang3;RegExUtils;false;removePattern;(String,String);;Argument[1];regex-use;manual",
+ "org.apache.commons.lang3;RegExUtils;false;replaceAll;(String,String,String);;Argument[1];regex-use;manual",
+ "org.apache.commons.lang3;RegExUtils;false;replaceFirst;(String,String,String);;Argument[1];regex-use;manual",
+ "org.apache.commons.lang3;RegExUtils;false;replacePattern;(String,String,String);;Argument[1];regex-use;manual",
]
}
}
diff --git a/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
index 859fac475d17..21dde6edb41d 100644
--- a/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
@@ -15,9 +15,7 @@ abstract class RegexInjectionSanitizer extends DataFlow::ExprNode { }
private class DefaultRegexInjectionSink extends RegexInjectionSink {
DefaultRegexInjectionSink() {
exists(string kind |
- kind.matches([
- "regex-use[]", "regex-use[f1]", "regex-use[f-1]", "regex-use[-1]", "regex-use[0]"
- ]) and
+ kind.matches(["regex-use[]", "regex-use[f1]", "regex-use[f-1]", "regex-use[-1]", "regex-use"]) and
sinkNode(this, kind)
)
}
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
index 57a1ea926679..5c7a3ca05746 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.java
@@ -7,6 +7,7 @@
import javax.servlet.ServletException;
import org.apache.commons.lang3.RegExUtils;
+import com.google.common.base.Splitter;
public class RegexInjectionTest extends HttpServlet {
public boolean string1(javax.servlet.http.HttpServletRequest request) {
@@ -138,10 +139,10 @@ public boolean apache7(javax.servlet.http.HttpServletRequest request) {
// test `Pattern.quote` sanitizer
public boolean quoteTest(javax.servlet.http.HttpServletRequest request) {
- String regex = request.getParameter("regex");
+ String pattern = request.getParameter("pattern");
String input = request.getParameter("input");
- return input.matches(Pattern.quote(regex)); // Safe
+ return input.matches(Pattern.quote(pattern)); // Safe
}
// test `Pattern.LITERAL` sanitizer
@@ -151,4 +152,15 @@ public boolean literalTest(javax.servlet.http.HttpServletRequest request) {
return Pattern.compile(pattern, Pattern.LITERAL).matcher(input).matches(); // Safe
}
+
+ public Splitter guava1(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ return Splitter.onPattern(pattern); // $ hasRegexInjection
+ }
+
+ public Splitter guava2(javax.servlet.http.HttpServletRequest request) {
+ String pattern = request.getParameter("pattern");
+ // sink is `Pattern.compile`
+ return Splitter.on(Pattern.compile(pattern)); // $ hasRegexInjection
+ }
}
From bada9864336f065a066ec14c34c989c0df094d14 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 8 Nov 2022 09:34:46 -0500
Subject: [PATCH 21/23] apply review comments
---
.../ql/lib/semmle/code/java/security/regexp/RegexInjection.qll | 3 +--
.../ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql | 1 -
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
index 21dde6edb41d..4df736e62416 100644
--- a/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
@@ -40,11 +40,10 @@ private class PatternQuoteCall extends RegexInjectionSanitizer {
*/
private class PatternLiteralFlag extends RegexInjectionSanitizer {
PatternLiteralFlag() {
- exists(MethodAccess ma, Method m, Field field | m = ma.getMethod() |
+ exists(MethodAccess ma, Method m, PatternLiteralField field | m = ma.getMethod() |
ma.getArgument(0) = this.asExpr() and
m.getDeclaringType() instanceof TypeRegexPattern and
m.hasName("compile") and
- field instanceof PatternLiteralField and
ma.getArgument(1) = field.getAnAccess()
)
}
diff --git a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
index 9603ad37b8f4..368b5170bfc4 100644
--- a/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
+++ b/java/ql/test/query-tests/security/CWE-730/RegexInjectionTest.ql
@@ -2,7 +2,6 @@ import java
import TestUtilities.InlineExpectationsTest
import semmle.code.java.security.regexp.RegexInjectionQuery
-//import semmle.code.java.security.regexp.PolynomialReDoSQuery
class RegexInjectionTest extends InlineExpectationsTest {
RegexInjectionTest() { this = "RegexInjectionTest" }
From 13decd38d90d30449f0a875278c35d8a2d9f3772 Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Tue, 8 Nov 2022 15:27:37 -0500
Subject: [PATCH 22/23] update sink
---
.../lib/semmle/code/java/security/regexp/RegexInjection.qll | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
index 4df736e62416..3c1e2e982296 100644
--- a/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
+++ b/java/ql/lib/semmle/code/java/security/regexp/RegexInjection.qll
@@ -14,10 +14,8 @@ abstract class RegexInjectionSanitizer extends DataFlow::ExprNode { }
/** A method call that takes a regular expression as an argument. */
private class DefaultRegexInjectionSink extends RegexInjectionSink {
DefaultRegexInjectionSink() {
- exists(string kind |
- kind.matches(["regex-use[]", "regex-use[f1]", "regex-use[f-1]", "regex-use[-1]", "regex-use"]) and
- sinkNode(this, kind)
- )
+ // we only select sinks where there is direct regex creation, not regex uses
+ sinkNode(this, ["regex-use[]", "regex-use[f1]", "regex-use[f-1]", "regex-use[-1]", "regex-use"])
}
}
From 9e2ec9d12f599408f20a28d036163316b6eb077a Mon Sep 17 00:00:00 2001
From: Jami Cogswell
Date: Mon, 21 Nov 2022 13:39:46 -0500
Subject: [PATCH 23/23] apply docs review suggestion
---
java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp b/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
index fc8ab33ca80a..3e239d07107b 100644
--- a/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
+++ b/java/ql/src/Security/CWE/CWE-730/RegexInjection.qhelp
@@ -25,7 +25,7 @@ The following example shows an HTTP request parameter that is used to construct
In the first case the user-provided regex is not escaped.
-If a malicious user provides a regex that has exponential worst case performance,
+If a malicious user provides a regex whose worst-case performance is exponential,
then this could lead to a Denial of Service.