-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Java: CWE-625 Query to detect regex dot bypass #9873
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
48f143e
Query to detect regex dot bypass
luchua-bc 962069c
Add path check in a security context (redirect)
luchua-bc 1ce31ec
Add sinks of servlet dispatcher and filter
luchua-bc b69eba9
Add check for Spring redirect
luchua-bc 1d12bd1
Share SpringUrlRedirect library
atorralba 3e382fd
Optimize the query
luchua-bc e2e8798
Move pattern check to MatchRegexConfiguration::isSink
luchua-bc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
java/ql/src/experimental/Security/CWE/CWE-625/DotRegex.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
String PROTECTED_PATTERN = "/protected/.*"; | ||
String CONSTRAINT_PATTERN = "/protected/xyz\\.xml"; | ||
|
||
// BAD: A string with line return e.g. `/protected/%0dxyz` can bypass the path check | ||
Pattern p = Pattern.compile(PROTECTED_PATTERN); | ||
Matcher m = p.matcher(path); | ||
|
||
// GOOD: A string with line return e.g. `/protected/%0dxyz` cannot bypass the path check | ||
Pattern p = Pattern.compile(PROTECTED_PATTERN, Pattern.DOTALL); | ||
Matcher m = p.matcher(path); | ||
|
||
// GOOD: Only a specific path can pass the validation | ||
Pattern p = Pattern.compile(CONSTRAINT_PATTERN); | ||
Matcher m = p.matcher(path); | ||
|
||
if (m.matches()) { | ||
// Protected page - check access token and redirect to login page | ||
} else { | ||
// Not protected page - render content | ||
} | ||
|
||
// BAD: A string with line return e.g. `/protected/%0axyz` can bypass the path check | ||
boolean matches = path.matches(PROTECTED_PATTERN); | ||
|
||
// BAD: A string with line return e.g. `/protected/%0axyz` can bypass the path check | ||
boolean matches = Pattern.matches(PROTECTED_PATTERN, path); | ||
|
||
if (matches) { | ||
// Protected page - check access token and redirect to login page | ||
} else { | ||
// Not protected page - render content | ||
} |
40 changes: 40 additions & 0 deletions
40
java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegex.qhelp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<!DOCTYPE qhelp PUBLIC | ||
"-//Semmle//qhelp//EN" | ||
"qhelp.dtd"> | ||
<qhelp> | ||
|
||
<overview> | ||
<p>By default, "dot" (<code>.</code>) in regular expressions matches all characters except newline characters <code>\n</code> and | ||
<code>\r</code>. Regular expressions containing a dot can be bypassed with the characters \r(%0a) , \n(%0d) when the default regex | ||
matching implementations of Java are used. When regular expressions serve to match protected resource patterns to grant access | ||
to protected application resources, attackers can gain access to unauthorized paths.</p> | ||
</overview> | ||
|
||
<recommendation> | ||
<p>To guard against unauthorized access, it is advisable to properly specify regex patterns for validating user input. The Java | ||
Pattern Matcher API <code>Pattern.compile(PATTERN, Pattern.DOTALL)</code> with the <code>DOTALL</code> flag set can be adopted | ||
to address this vulnerability.</p> | ||
</recommendation> | ||
|
||
<example> | ||
<p>The following examples show the bad case and the good case respectively. The <code>bad</code> methods show a regex pattern allowing | ||
bypass. In the <code>good</code> methods, it is shown how to solve this problem by either specifying the regex pattern correctly or | ||
use the Java API that can detect new line characters. | ||
</p> | ||
|
||
<sample src="DotRegex.java" /> | ||
</example> | ||
|
||
<references> | ||
<li>Lay0us1: | ||
<a href="https://github.com/Lay0us1/CVE-2022-32532">CVE 2022-22978: Authorization Bypass in RegexRequestMatcher</a>. | ||
</li> | ||
<li>Apache Shiro: | ||
<a href="https://github.com/apache/shiro/commit/6bcb92e06fa588b9c7790dd01bc02135d58d3f5b">Address the RegexRequestMatcher issue in 1.9.1</a>. | ||
</li> | ||
<li>CVE-2022-32532: | ||
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-32532">Applications using RegExPatternMatcher with "." in the regular expression are possibly vulnerable to an authorization bypass</a>. | ||
</li> | ||
</references> | ||
|
||
</qhelp> |
40 changes: 40 additions & 0 deletions
40
java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegex.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/** | ||
* @name URL matched by permissive `.` in the regular expression | ||
* @description URL validated with permissive `.` in regex are possibly vulnerable | ||
* to an authorization bypass. | ||
* @kind path-problem | ||
* @problem.severity warning | ||
* @precision high | ||
* @id java/permissive-dot-regex | ||
* @tags security | ||
* external/cwe-625 | ||
* external/cwe-863 | ||
*/ | ||
|
||
import java | ||
import semmle.code.java.dataflow.FlowSources | ||
import DataFlow::PathGraph | ||
import PermissiveDotRegexQuery | ||
|
||
from DataFlow::PathNode source, DataFlow::PathNode sink, MatchRegexConfiguration conf | ||
where | ||
conf.hasFlowPath(source, sink) and | ||
exists(MethodAccess ma | any(PermissiveDotRegexConfig conf2).hasFlowToExpr(ma.getArgument(0)) | | ||
// input.matches(regexPattern) | ||
ma.getMethod() instanceof StringMatchMethod and | ||
ma.getQualifier() = sink.getNode().asExpr() | ||
or | ||
// p = Pattern.compile(regexPattern); p.matcher(input) | ||
ma.getMethod() instanceof PatternCompileMethod and | ||
exists(MethodAccess pma | | ||
pma.getMethod() instanceof PatternMatcherMethod and | ||
sink.getNode().asExpr() = pma.getArgument(0) and | ||
DataFlow::localExprFlow(ma, pma.getQualifier()) | ||
) | ||
or | ||
// p = Pattern.matches(regexPattern, input) | ||
ma.getMethod() instanceof PatternMatchMethod and | ||
sink.getNode().asExpr() = ma.getArgument(1) | ||
) | ||
select sink.getNode(), source, sink, "Potentially authentication bypass due to $@.", | ||
source.getNode(), "user-provided value" |
201 changes: 201 additions & 0 deletions
201
java/ql/src/experimental/Security/CWE/CWE-625/PermissiveDotRegexQuery.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
/** Provides classes related to security-centered regular expression matching. */ | ||
|
||
import java | ||
private import semmle.code.java.dataflow.ExternalFlow | ||
private import semmle.code.java.dataflow.FlowSources | ||
import experimental.semmle.code.java.security.SpringUrlRedirect | ||
import semmle.code.java.controlflow.Guards | ||
import semmle.code.java.security.UrlRedirect | ||
import Regex | ||
|
||
/** A string that ends with `.*` not prefixed with `\`. */ | ||
class PermissiveDotStr extends StringLiteral { | ||
PermissiveDotStr() { | ||
exists(string s, int i | this.getValue() = s | | ||
s.indexOf(".*") = i and | ||
not s.charAt(i - 1) = "\\" and | ||
s.length() = i + 2 | ||
) | ||
} | ||
} | ||
|
||
/** Source model of remote flow source with servlets. */ | ||
private class GetServletUriSource extends SourceModelCsv { | ||
override predicate row(string row) { | ||
row = | ||
[ | ||
"javax.servlet.http;HttpServletRequest;false;getPathInfo;();;ReturnValue;uri-path;manual", | ||
"javax.servlet.http;HttpServletRequest;false;getPathTranslated;();;ReturnValue;uri-path;manual", | ||
"javax.servlet.http;HttpServletRequest;false;getRequestURI;();;ReturnValue;uri-path;manual", | ||
"javax.servlet.http;HttpServletRequest;false;getRequestURL;();;ReturnValue;uri-path;manual", | ||
"javax.servlet.http;HttpServletRequest;false;getServletPath;();;ReturnValue;uri-path;manual" | ||
] | ||
} | ||
} | ||
|
||
/** Sink of servlet dispatcher. */ | ||
private class UrlDispatchSink extends UrlRedirectSink { | ||
UrlDispatchSink() { | ||
exists(MethodAccess ma | | ||
ma.getMethod() instanceof RequestDispatchMethod and | ||
this.asExpr() = ma.getQualifier() | ||
) | ||
} | ||
} | ||
|
||
/** The `doFilter` method of `javax.servlet.FilterChain`. */ | ||
private class ServletFilterMethod extends Method { | ||
ServletFilterMethod() { | ||
this.getDeclaringType().getASupertype*().hasQualifiedName("javax.servlet", "FilterChain") and | ||
this.hasName("doFilter") | ||
} | ||
} | ||
|
||
/** Sink of servlet filter. */ | ||
private class UrlFilterSink extends UrlRedirectSink { | ||
UrlFilterSink() { | ||
exists(MethodAccess ma | | ||
ma.getMethod() instanceof ServletFilterMethod and | ||
this.asExpr() = ma.getQualifier() | ||
) | ||
} | ||
} | ||
|
||
/** A Spring framework annotation indicating remote uri user input. */ | ||
class SpringUriInputAnnotation extends Annotation { | ||
SpringUriInputAnnotation() { | ||
this.getType() | ||
.hasQualifiedName("org.springframework.web.bind.annotation", | ||
["PathVariable", "RequestParam"]) | ||
} | ||
} | ||
|
||
class SpringUriInputParameterSource extends DataFlow::Node { | ||
SpringUriInputParameterSource() { | ||
this.asParameter() = | ||
any(SpringRequestMappingParameter srmp | | ||
srmp.getAnAnnotation() instanceof SpringUriInputAnnotation | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A data flow sink to construct regular expressions. | ||
*/ | ||
class CompileRegexSink extends DataFlow::ExprNode { | ||
CompileRegexSink() { | ||
exists(MethodAccess ma, Method m | m = ma.getMethod() | | ||
( | ||
ma.getArgument(0) = this.asExpr() and | ||
( | ||
m instanceof StringMatchMethod // input.matches(regexPattern) | ||
or | ||
m instanceof PatternCompileMethod // p = Pattern.compile(regexPattern) | ||
or | ||
m instanceof PatternMatchMethod // p = Pattern.matches(regexPattern, input) | ||
) | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A flow configuration for permissive dot regex. | ||
*/ | ||
class PermissiveDotRegexConfig extends DataFlow::Configuration { | ||
PermissiveDotRegexConfig() { this = "PermissiveDotRegex::PermissiveDotRegexConfig" } | ||
|
||
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof PermissiveDotStr } | ||
|
||
override predicate isSink(DataFlow::Node sink) { sink instanceof CompileRegexSink } | ||
|
||
override predicate isBarrier(DataFlow::Node node) { | ||
exists( | ||
MethodAccess ma, Field f // Pattern.compile(PATTERN, Pattern.DOTALL) | ||
| | ||
ma.getMethod() instanceof PatternCompileMethod and | ||
ma.getArgument(1) = f.getAnAccess() and | ||
f.hasName("DOTALL") and | ||
f.getDeclaringType() instanceof Pattern and | ||
node.asExpr() = ma.getArgument(0) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A taint-tracking configuration for untrusted user input used to match regular expressions. | ||
*/ | ||
class MatchRegexConfiguration extends TaintTracking::Configuration { | ||
MatchRegexConfiguration() { this = "PermissiveDotRegex::MatchRegexConfiguration" } | ||
|
||
override predicate isSource(DataFlow::Node source) { | ||
sourceNode(source, "uri-path") or // Servlet uri source | ||
source instanceof SpringUriInputParameterSource // Spring uri source | ||
} | ||
|
||
override predicate isSink(DataFlow::Node sink) { | ||
sink instanceof MatchRegexSink and | ||
exists( | ||
Guard guard, Expr se, Expr ce // used in a condition to control url redirect, which is a typical security enforcement | ||
| | ||
( | ||
sink.asExpr() = ce.(MethodAccess).getQualifier() or | ||
sink.asExpr() = ce.(MethodAccess).getAnArgument() or | ||
sink.asExpr() = ce | ||
) and | ||
( | ||
DataFlow::localExprFlow(ce, guard.(MethodAccess).getQualifier()) or | ||
DataFlow::localExprFlow(ce, guard.(MethodAccess).getAnArgument()) | ||
) and | ||
( | ||
DataFlow::exprNode(se) instanceof UrlRedirectSink or | ||
DataFlow::exprNode(se) instanceof SpringUrlRedirectSink | ||
) and | ||
guard.controls(se.getBasicBlock(), true) | ||
) | ||
} | ||
} | ||
|
||
abstract class MatchRegexSink extends DataFlow::ExprNode { } | ||
|
||
/** | ||
* A data flow sink to string match regular expressions. | ||
*/ | ||
class StringMatchRegexSink extends MatchRegexSink { | ||
StringMatchRegexSink() { | ||
exists(MethodAccess ma, Method m | m = ma.getMethod() | | ||
( | ||
m instanceof StringMatchMethod and | ||
ma.getQualifier() = this.asExpr() | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A data flow sink to `pattern.matches` regular expressions. | ||
*/ | ||
class PatternMatchRegexSink extends MatchRegexSink { | ||
PatternMatchRegexSink() { | ||
exists(MethodAccess ma, Method m | m = ma.getMethod() | | ||
( | ||
m instanceof PatternMatchMethod and | ||
ma.getArgument(1) = this.asExpr() | ||
) | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* A data flow sink to `pattern.matcher` match regular expressions. | ||
*/ | ||
class PatternMatcherRegexSink extends MatchRegexSink { | ||
PatternMatcherRegexSink() { | ||
exists(MethodAccess ma, Method m | m = ma.getMethod() | | ||
( | ||
m instanceof PatternMatcherMethod and | ||
ma.getArgument(0) = this.asExpr() | ||
) | ||
) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/** Provides methods related to regular expression matching. */ | ||
|
||
import java | ||
|
||
/** | ||
* The class `Pattern` for pattern match. | ||
*/ | ||
class Pattern extends RefType { | ||
Pattern() { this.hasQualifiedName("java.util.regex", "Pattern") } | ||
} | ||
|
||
/** | ||
* The method `compile` for `Pattern`. | ||
*/ | ||
class PatternCompileMethod extends Method { | ||
PatternCompileMethod() { | ||
this.getDeclaringType().getASupertype*() instanceof Pattern and | ||
this.hasName("compile") | ||
} | ||
} | ||
|
||
/** | ||
* The method `matches` for `Pattern`. | ||
*/ | ||
class PatternMatchMethod extends Method { | ||
PatternMatchMethod() { | ||
this.getDeclaringType().getASupertype*() instanceof Pattern and | ||
this.hasName("matches") | ||
} | ||
} | ||
|
||
/** | ||
* The method `matcher` for `Pattern`. | ||
*/ | ||
class PatternMatcherMethod extends Method { | ||
PatternMatcherMethod() { | ||
this.getDeclaringType().getASupertype*() instanceof Pattern and | ||
this.hasName("matcher") | ||
} | ||
} | ||
|
||
/** | ||
* The method `matches` for `String`. | ||
*/ | ||
class StringMatchMethod extends Method { | ||
StringMatchMethod() { | ||
this.getDeclaringType().getASupertype*() instanceof TypeString and | ||
this.hasName("matches") | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.