JS: recognize sanitizing slashes in URL redirection queries

This commit is contained in:
Asger F
2018-11-08 11:02:46 +00:00
parent 0647743333
commit 6ec13feab4
4 changed files with 52 additions and 62 deletions

View File

@@ -16,46 +16,8 @@ module ServerSideUrlRedirect {
/**
* A data flow sink for unvalidated URL redirect vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/**
* Holds if this sink may redirect to a non-local URL.
*/
predicate maybeNonLocal() {
exists (DataFlow::Node prefix | prefix = getAPrefix(this) |
not exists(prefix.asExpr().getStringValue())
or
exists (string prefixVal | prefixVal = prefix.asExpr().getStringValue() |
// local URLs (i.e., URLs that start with `/` not followed by `\` or `/`,
// or that start with `~/`) are unproblematic
not prefixVal.regexpMatch("/[^\\\\/].*|~/.*") and
// so are localhost URLs
not prefixVal.regexpMatch("(\\w+:)?//localhost[:/].*")
)
)
}
}
abstract class Sink extends DataFlow::Node { }
/**
* Gets a node that is transitively reachable from `nd` along prefix predecessor edges.
*/
private DataFlow::Node prefixCandidate(Sink sink) {
result = sink or
result = prefixCandidate(sink).getAPredecessor() or
result = StringConcatenation::getFirstOperand(prefixCandidate(sink))
}
/**
* Gets an expression that may end up being a prefix of the string concatenation `nd`.
*/
private DataFlow::Node getAPrefix(Sink sink) {
exists (DataFlow::Node prefix |
prefix = prefixCandidate(sink) and
not exists(StringConcatenation::getFirstOperand(prefix)) and
not exists(prefix.getAPredecessor()) and
result = prefix
)
}
/**
* A sanitizer for unvalidated URL redirect vulnerabilities.
*/
@@ -72,7 +34,7 @@ module ServerSideUrlRedirect {
}
override predicate isSink(DataFlow::Node sink) {
sink.(Sink).maybeNonLocal()
sink instanceof Sink
}
override predicate isSanitizer(DataFlow::Node node) {

View File

@@ -7,11 +7,19 @@
import javascript
/**
* Holds if a string value containing `?` or `#` may flow into
* `nd` or one of its operands, assuming that it is a concatenation.
* Holds if the string value of `nd` prevents anything appended after it
* from affecting the hostname of a URL.
*
* Specifically, this holds if the string contains any of the following:
* - `?` (any suffix becomes part of query)
* - `#` (any suffix becomes part of fragment)
* - `/` or `\`, immediately prefixed by a character other than `:`, `/`, or `\` (any suffix becomes part of the path)
*
* In the latter case, the additional prefix check is necessary to avoid a `/` that could be interpreted as
* the `//` separating the (optional) scheme from the hostname.
*/
private predicate hasSanitizingSubstring(DataFlow::Node nd) {
nd.asExpr().getStringValue().regexpMatch(".*[?#].*")
predicate hasSanitizingSubstring(DataFlow::Node nd) {
nd.asExpr().getStringValue().regexpMatch(".*([?#]|[^?#:/\\\\][/\\\\]).*")
or
hasSanitizingSubstring(StringConcatenation::getAnOperand(nd))
or
@@ -21,8 +29,8 @@ private predicate hasSanitizingSubstring(DataFlow::Node nd) {
}
/**
* Holds if data that flows from `source` to `sink` may have a string
* containing the character `?` or `#` prepended to it.
* Holds if data that flows from `source` to `sink` cannot affect the
* hostname of the resulting string when interpreted as a URL.
*
* This is considered as a sanitizing edge for the URL redirection queries.
*/