mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
JS: recognize sanitizing slashes in URL redirection queries
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user