SSRF query: add sanitizer looking for a variety of ways of prepending a sanitizing prefix, such as one that restricts the hostname a URI will refer to.

This commit is contained in:
Chris Smowton
2021-03-30 19:35:30 +01:00
parent 487c1db6ed
commit b5a450b881
5 changed files with 194 additions and 0 deletions

View File

@@ -25,6 +25,8 @@ class RequestForgeryConfiguration extends TaintTracking::Configuration {
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
requestForgeryStep(pred, succ)
}
override predicate isSanitizer(DataFlow::Node node) { node instanceof RequestForgerySanitizer }
}
from DataFlow::PathNode source, DataFlow::PathNode sink, RequestForgeryConfiguration conf

View File

@@ -5,6 +5,8 @@ import semmle.code.java.frameworks.spring.Spring
import semmle.code.java.frameworks.JaxWS
import semmle.code.java.frameworks.javase.Http
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.StringFormat
predicate requestForgeryStep(DataFlow::Node pred, DataFlow::Node succ) {
// propagate to a URI when its host is assigned to
@@ -190,3 +192,83 @@ private class SpringRestTemplateUrlMethods extends Method {
result = ma.getArgument(0)
}
}
/** A sanitizer for request forgery vulnerabilities. */
abstract class RequestForgerySanitizer extends DataFlow::Node { }
private class HostnameSanitzingPrefix extends CompileTimeConstantExpr {
int offset;
HostnameSanitzingPrefix() {
exists(
this.getStringValue().regexpFind(".*([?#]|[^?#:/\\\\][/\\\\]).*|[/\\\\][^/\\\\].*", 0, offset)
)
}
int getOffset() { result = offset }
}
private AddExpr getParentAdd(AddExpr e) { result = e.getParent() }
private AddExpr getAnAddContainingHostnameSanitizingPrefix() {
result = getParentAdd*(any(HostnameSanitzingPrefix p).getParent())
}
private Expr getASanitizedAddOperand() {
exists(AddExpr e |
e = getAnAddContainingHostnameSanitizingPrefix() and
(
e.getLeftOperand() = getAnAddContainingHostnameSanitizingPrefix() or
e.getLeftOperand() instanceof HostnameSanitzingPrefix
) and
result = e.getRightOperand()
)
}
private MethodAccess getNextAppend(MethodAccess append) {
result = any(StringBuilderVar sbv).getNextAppend(append)
}
class HostnameSanitizedExpr extends Expr {
HostnameSanitizedExpr() {
// Sanitize expressions that come after a sanitizing prefix in a tree of string additions:
this = getASanitizedAddOperand()
or
// Sanitize expressions that come after a sanitizing prefix in a sequence of StringBuilder operations:
exists(MethodAccess appendSanitizingConstant, MethodAccess subsequentAppend |
appendSanitizingConstant.getArgument(0) instanceof HostnameSanitzingPrefix and
getNextAppend*(appendSanitizingConstant) = subsequentAppend and
this = subsequentAppend.getArgument(0)
)
or
// Sanitize expressions that come after a sanitizing prefix in the args to a format call:
exists(
FormattingCall formatCall, FormatString formatString, HostnameSanitzingPrefix prefix,
int sanitizedFromOffset, int laterOffset, int sanitizedArg
|
formatString = unique(FormatString fs | fs = formatCall.getAFormatString()) and
(
// An argument that sanitizes will be come before this:
exists(int argIdx |
formatCall.getArgumentToBeFormatted(argIdx) = prefix and
sanitizedFromOffset = formatString.getAnArgUsageOffset(argIdx)
)
or
// The format string itself sanitizes subsequent arguments:
formatString = prefix.getStringValue() and
sanitizedFromOffset = prefix.getOffset()
) and
laterOffset > sanitizedFromOffset and
laterOffset = formatString.getAnArgUsageOffset(sanitizedArg) and
this = formatCall.getArgumentToBeFormatted(sanitizedArg)
)
}
}
/**
* A value that is the result of prepending a string that prevents any value from controlling the
* host of a URL.
*/
class HostnameSantizer extends RequestForgerySanitizer {
HostnameSantizer() { this.asExpr() instanceof HostnameSanitizedExpr }
}