Add check for Spring redirect

This commit is contained in:
luchua-bc
2022-07-29 01:59:47 +00:00
parent 1ce31ec32c
commit b69eba9238
5 changed files with 261 additions and 3 deletions

View File

@@ -18,6 +18,7 @@ import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.UrlRedirect
import DataFlow::PathGraph
import Regex
import SpringUrlRedirect
/** Source model of remote flow source with servlets. */
private class GetServletUriSource extends SourceModelCsv {
@@ -51,6 +52,30 @@ private class UrlFilterSink extends SinkModelCsv {
}
}
/** A Spring framework annotation indicating remote uri user input. */
class SpringUriInputAnnotation extends Annotation {
SpringUriInputAnnotation() {
exists(AnnotationType a |
a = this.getType() and
a.getPackage().getName() = "org.springframework.web.bind.annotation"
|
(
a.hasName("PathVariable") or
a.hasName("RequestParam")
)
)
}
}
class SpringUriInputParameterSource extends DataFlow::Node {
SpringUriInputParameterSource() {
this.asParameter() =
any(SpringRequestMappingParameter srmp |
srmp.getAnAnnotation() instanceof SpringUriInputAnnotation
)
}
}
/**
* `.` without a `\` prefix, which is likely not a character literal in regex
*/
@@ -121,7 +146,10 @@ class PermissiveDotRegexConfig extends DataFlow::Configuration {
class MatchRegexConfiguration extends TaintTracking::Configuration {
MatchRegexConfiguration() { this = "PermissiveDotRegex::MatchRegexConfiguration" }
override predicate isSource(DataFlow::Node source) { sourceNode(source, "uri-path") }
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
@@ -140,7 +168,8 @@ class MatchRegexConfiguration extends TaintTracking::Configuration {
(
DataFlow::exprNode(se) instanceof UrlRedirectSink or
sinkNode(DataFlow::exprNode(se), "url-dispatch") or
sinkNode(DataFlow::exprNode(se), "url-filter")
sinkNode(DataFlow::exprNode(se), "url-filter") or
DataFlow::exprNode(se) instanceof SpringUrlRedirectSink
) and
guard.controls(se.getBasicBlock(), true)
)

View File

@@ -0,0 +1,109 @@
/** Provides methods related to Spring URL redirect from /src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll. */
private import java
private import semmle.code.java.dataflow.FlowSources
/**
* A concatenate expression using the string `redirect:` or `ajaxredirect:` or `forward:` on the left.
*
* E.g: `"redirect:" + redirectUrl`
*/
class RedirectBuilderExpr extends AddExpr {
RedirectBuilderExpr() {
this.getLeftOperand().(CompileTimeConstantExpr).getStringValue() in [
"redirect:", "ajaxredirect:", "forward:"
]
}
}
/**
* A call to `StringBuilder.append` or `StringBuffer.append` method, and the parameter value is
* `"redirect:"` or `"ajaxredirect:"` or `"forward:"`.
*
* E.g: `StringBuilder.append("redirect:")`
*/
class RedirectAppendCall extends MethodAccess {
RedirectAppendCall() {
this.getMethod().hasName("append") and
this.getMethod().getDeclaringType() instanceof StringBuildingType and
this.getArgument(0).(CompileTimeConstantExpr).getStringValue() in [
"redirect:", "ajaxredirect:", "forward:"
]
}
}
/** A URL redirection sink from spring controller method. */
abstract class SpringUrlRedirectSink extends DataFlow::Node { }
/**
* A sink for URL Redirection via the Spring View classes.
*/
private class SpringViewUrlRedirectSink extends SpringUrlRedirectSink {
SpringViewUrlRedirectSink() {
// Hardcoded redirect such as "redirect:login"
this.asExpr()
.(CompileTimeConstantExpr)
.getStringValue()
.indexOf(["redirect:", "ajaxredirect:", "forward:"]) = 0 and
any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable())
or
exists(RedirectBuilderExpr rbe |
rbe.getRightOperand() = this.asExpr() and
any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable())
)
or
exists(MethodAccess ma, RedirectAppendCall rac |
DataFlow2::localExprFlow(rac.getQualifier(), ma.getQualifier()) and
ma.getMethod().hasName("append") and
ma.getArgument(0) = this.asExpr() and
any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable())
)
or
exists(MethodAccess ma |
ma.getMethod().hasName("setUrl") and
ma.getMethod()
.getDeclaringType()
.hasQualifiedName("org.springframework.web.servlet.view", "AbstractUrlBasedView") and
ma.getArgument(0) = this.asExpr()
)
or
exists(ClassInstanceExpr cie |
cie.getConstructedType()
.hasQualifiedName("org.springframework.web.servlet.view", "RedirectView") and
cie.getArgument(0) = this.asExpr()
)
or
exists(ClassInstanceExpr cie |
cie.getConstructedType().hasQualifiedName("org.springframework.web.servlet", "ModelAndView") and
exists(RedirectBuilderExpr rbe |
rbe = cie.getArgument(0) and rbe.getRightOperand() = this.asExpr()
)
)
}
}
/**
* A sink for URL Redirection via the `ResponseEntity` class.
*/
private class SpringResponseEntityUrlRedirectSink extends SpringUrlRedirectSink {
SpringResponseEntityUrlRedirectSink() {
// Find `new ResponseEntity(httpHeaders, ...)` or
// `new ResponseEntity(..., httpHeaders, ...)` sinks
exists(ClassInstanceExpr cie, Argument argument |
cie.getConstructedType() instanceof SpringResponseEntity and
argument.getType() instanceof SpringHttpHeaders and
argument = cie.getArgument([0, 1]) and
this.asExpr() = argument
)
or
// Find `ResponseEntity.status(...).headers(taintHeaders).build()` or
// `ResponseEntity.status(...).location(URI.create(taintURL)).build()` sinks
exists(MethodAccess ma |
ma.getMethod()
.getDeclaringType()
.hasQualifiedName("org.springframework.http", "ResponseEntity$HeadersBuilder<BodyBuilder>") and
ma.getMethod().getName() in ["headers", "location"] and
this.asExpr() = ma.getArgument(0)
)
}
}