mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #11070 from jcogs33/java-regex-injection
Java: Promote regex injection query from experimental
This commit is contained in:
@@ -2,6 +2,27 @@
|
||||
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/** The class `java.util.regex.Pattern`. */
|
||||
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 PatternLiteralField extends Field {
|
||||
PatternLiteralField() {
|
||||
this.getDeclaringType() instanceof TypeRegexPattern and
|
||||
this.hasName("LITERAL")
|
||||
}
|
||||
}
|
||||
|
||||
private class RegexModel extends SummaryModelCsv {
|
||||
override predicate row(string s) {
|
||||
s =
|
||||
|
||||
@@ -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;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",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/** Provides classes and predicates related to regex injection in Java. */
|
||||
|
||||
import java
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.frameworks.Regex
|
||||
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 { }
|
||||
|
||||
/** 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() {
|
||||
// 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"])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, PatternLiteralField field | m = ma.getMethod() |
|
||||
ma.getArgument(0) = this.asExpr() and
|
||||
m.getDeclaringType() instanceof TypeRegexPattern and
|
||||
m.hasName("compile") and
|
||||
ma.getArgument(1) = field.getAnAccess()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/** 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.security.regexp.RegexInjection
|
||||
|
||||
/** A taint-tracking configuration for untrusted user input used to construct regular expressions. */
|
||||
class RegexInjectionConfiguration extends TaintTracking::Configuration {
|
||||
RegexInjectionConfiguration() { this = "RegexInjection" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink instanceof RegexInjectionSink }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node node) { node instanceof RegexInjectionSanitizer }
|
||||
}
|
||||
22
java/ql/src/Security/CWE/CWE-730/RegexInjection.java
Normal file
22
java/ql/src/Security/CWE/CWE-730/RegexInjection.java
Normal file
@@ -0,0 +1,22 @@
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
public class RegexInjectionDemo extends HttpServlet {
|
||||
|
||||
public boolean badExample(javax.servlet.http.HttpServletRequest request) {
|
||||
String regex = request.getParameter("regex");
|
||||
String input = request.getParameter("input");
|
||||
|
||||
// BAD: Unsanitized user input is used to construct a regular expression
|
||||
return input.matches(regex);
|
||||
}
|
||||
|
||||
public boolean goodExample(javax.servlet.http.HttpServletRequest request) {
|
||||
String regex = request.getParameter("regex");
|
||||
String input = request.getParameter("input");
|
||||
|
||||
// GOOD: User input is sanitized before constructing the regex
|
||||
return input.matches(Pattern.quote(regex));
|
||||
}
|
||||
}
|
||||
@@ -15,25 +15,25 @@ perform a Denial of Service attack.
|
||||
<recommendation>
|
||||
<p>
|
||||
Before embedding user input into a regular expression, use a sanitization function
|
||||
to escape meta-characters that have special meaning.
|
||||
such as <code>Pattern.quote</code> to escape meta-characters that have special meaning.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<sample src="RegexInjection.java" />
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
In the second case, the user input is escaped using <code>escapeSpecialRegexChars</code> before being included
|
||||
In the second case, the user input is escaped using <code>Pattern.quote</code> before being included
|
||||
in the regular expression. This ensures that the user cannot insert characters which have a special
|
||||
meaning in regular expressions.
|
||||
</p>
|
||||
<sample src="RegexInjection.java" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
@@ -44,5 +44,8 @@ OWASP:
|
||||
<li>
|
||||
Wikipedia: <a href="https://en.wikipedia.org/wiki/ReDoS">ReDoS</a>.
|
||||
</li>
|
||||
<li>
|
||||
Java API Specification: <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html#quote(java.lang.String)">Pattern.quote</a>.
|
||||
</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
23
java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
Normal file
23
java/ql/src/Security/CWE/CWE-730/RegexInjection.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Regular expression injection
|
||||
* @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
|
||||
* external/cwe/cwe-730
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.security.regexp.RegexInjectionQuery
|
||||
import DataFlow::PathGraph
|
||||
|
||||
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"
|
||||
4
java/ql/src/change-notes/2022-11-03-regex-injection.md
Normal file
4
java/ql/src/change-notes/2022-11-03-regex-injection.md
Normal file
@@ -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).
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.example.demo;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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!";
|
||||
}
|
||||
|
||||
@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!";
|
||||
|
||||
return "doesn't match!";
|
||||
}
|
||||
|
||||
Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
|
||||
|
||||
String escapeSpecialRegexChars(String str) {
|
||||
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* @name Regular expression injection
|
||||
* @description User input should not be used in regular expressions without first being sanitized,
|
||||
* 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
|
||||
* @precision high
|
||||
* @id java/regex-injection
|
||||
* @tags security
|
||||
* external/cwe/cwe-730
|
||||
* external/cwe/cwe-400
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.java.dataflow.FlowSources
|
||||
import semmle.code.java.dataflow.TaintTracking
|
||||
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
|
||||
m.hasName(["matches", "split", "replaceFirst", "replaceAll"])
|
||||
)
|
||||
or
|
||||
m.getDeclaringType().hasQualifiedName("java.util.regex", "Pattern") and
|
||||
(
|
||||
ma.getArgument(0) = this.asExpr() and
|
||||
m.hasName(["compile", "matches"])
|
||||
)
|
||||
or
|
||||
m.getDeclaringType().hasQualifiedName("org.apache.commons.lang3", "RegExUtils") and
|
||||
(
|
||||
ma.getArgument(1) = this.asExpr() and
|
||||
m.getParameterType(1) instanceof TypeString and
|
||||
m.hasName([
|
||||
"removeAll", "removeFirst", "removePattern", "replaceAll", "replaceFirst",
|
||||
"replacePattern"
|
||||
])
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
regexp = "regexp?"
|
||||
|
|
||||
calleeName
|
||||
.regexpMatch("(?i)(" + sanitize + ".*" + regexp + ".*)" + "|(" + regexp + ".*" + sanitize +
|
||||
".*)")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
@@ -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 |
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE/CWE-730/RegexInjection.ql
|
||||
@@ -1 +0,0 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/apache-commons-lang3-3.7
|
||||
@@ -7,128 +7,119 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
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.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.replaceAll(pattern, "").length() > 0; // BAD
|
||||
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
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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.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 input.matches("^" + foo(pattern) + "=.*$"); // BAD
|
||||
}
|
||||
|
||||
String foo(String str) {
|
||||
return str;
|
||||
return Pattern.matches(pattern, input); // $ hasRegexInjection
|
||||
}
|
||||
|
||||
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
|
||||
return input.matches("^" + escapeSpecialRegexChars(pattern) + "=.*$");
|
||||
return input.matches("^" + foo(pattern) + "=.*$"); // $ hasRegexInjection
|
||||
}
|
||||
|
||||
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 + "=.*$");
|
||||
}
|
||||
|
||||
Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\]><-=!.+*?^$\\\\|]");
|
||||
|
||||
String escapeSpecialRegexChars(String str) {
|
||||
return SPECIAL_REGEX_CHARS.matcher(str).replaceAll("\\\\$0");
|
||||
String foo(String str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -136,13 +127,40 @@ public class RegexInjection extends HttpServlet {
|
||||
String input = request.getParameter("input");
|
||||
|
||||
Pattern pt = (Pattern)(Object) pattern;
|
||||
return RegExUtils.replaceFirst(input, pt, "").length() > 0; // GOOD, Pattern compile is the sink instead
|
||||
return RegExUtils.replaceFirst(input, pt, "").length() > 0; // Safe: Pattern compile is the sink instead
|
||||
}
|
||||
|
||||
public boolean apache7(javax.servlet.http.HttpServletRequest request) {
|
||||
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
|
||||
}
|
||||
|
||||
// test `Pattern.quote` sanitizer
|
||||
public boolean quoteTest(javax.servlet.http.HttpServletRequest request) {
|
||||
String pattern = request.getParameter("pattern");
|
||||
String input = request.getParameter("input");
|
||||
|
||||
return input.matches(Pattern.quote(pattern)); // Safe
|
||||
}
|
||||
|
||||
// test `Pattern.LITERAL` sanitizer
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import java
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
import semmle.code.java.security.regexp.RegexInjectionQuery
|
||||
|
||||
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 = ""
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
// semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/servlet-api-2.4:${testdir}/../../../stubs/guava-30.0
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user