mirror of
https://github.com/github/codeql.git
synced 2025-12-20 02:44:30 +01:00
610 lines
20 KiB
Plaintext
610 lines
20 KiB
Plaintext
/**
|
|
* Provides classes for modeling common HTTP concepts.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.DynamicPropertyAccess
|
|
private import semmle.javascript.dataflow.internal.StepSummary
|
|
private import semmle.javascript.dataflow.internal.CallGraphs
|
|
private import DataFlow::PseudoProperties as PseudoProperties
|
|
|
|
module Http {
|
|
/**
|
|
* A function invocation that causes a redirect response to be sent.
|
|
*/
|
|
abstract class RedirectInvocation extends DataFlow::CallNode {
|
|
/** Gets the argument specifying the URL to redirect to. */
|
|
abstract DataFlow::Node getUrlArgument();
|
|
|
|
/** Gets the route handler this redirect occurs in. */
|
|
abstract RouteHandler getRouteHandler();
|
|
}
|
|
|
|
/**
|
|
* An expression that sets HTTP response headers.
|
|
*
|
|
* Note that header names are case-insensitive, so this class
|
|
* always normalizes them to lower case: arguments representing
|
|
* header names are expected to be lower case, and similarly
|
|
* results representing header names are always lower case.
|
|
*/
|
|
abstract class HeaderDefinition extends DataFlow::Node {
|
|
/**
|
|
* Gets the (lower-case) name of a header set by this definition.
|
|
*/
|
|
abstract string getAHeaderName();
|
|
|
|
/**
|
|
* Holds if the header with (lower-case) name `headerName` is set to `headerValue`.
|
|
*/
|
|
abstract predicate defines(string headerName, string headerValue);
|
|
|
|
/**
|
|
* Gets the handler this definition occurs in.
|
|
*/
|
|
abstract RouteHandler getRouteHandler();
|
|
}
|
|
|
|
/**
|
|
* An expression that sets HTTP response headers implicitly.
|
|
*/
|
|
abstract class ImplicitHeaderDefinition extends HeaderDefinition {
|
|
override string getAHeaderName() { this.defines(result, _) }
|
|
}
|
|
|
|
/**
|
|
* An expression that sets HTTP response headers explicitly.
|
|
*/
|
|
abstract class ExplicitHeaderDefinition extends HeaderDefinition {
|
|
override string getAHeaderName() { this.definesHeaderValue(result, _) }
|
|
|
|
override predicate defines(string headerName, string headerValue) {
|
|
exists(DataFlow::Node e |
|
|
this.definesHeaderValue(headerName, e) and
|
|
headerValue = e.getStringValue()
|
|
)
|
|
}
|
|
|
|
/** Holds if the header with (lower-case) name `headerName` is set to the value of `headerValue`. */
|
|
abstract predicate definesHeaderValue(string headerName, DataFlow::Node headerValue);
|
|
|
|
/** Returns the expression used to compute the header name. */
|
|
abstract DataFlow::Node getNameNode();
|
|
}
|
|
|
|
/**
|
|
* The name of an HTTP request method, in all-uppercase.
|
|
*/
|
|
class RequestMethodName extends string {
|
|
RequestMethodName() {
|
|
this =
|
|
[
|
|
"CHECKOUT", "COPY", "DELETE", "GET", "HEAD", "LOCK", "MERGE", "MKACTIVITY", "MKCOL",
|
|
"MOVE", "M-SEARCH", "NOTIFY", "OPTIONS", "PATCH", "POST", "PURGE", "PUT", "REPORT",
|
|
"SEARCH", "SUBSCRIBE", "TRACE", "UNLOCK", "UNSUBSCRIBE"
|
|
]
|
|
}
|
|
|
|
/**
|
|
* Holds if this kind of HTTP request should be considered free of side effects,
|
|
* such as for `GET` and `HEAD` requests.
|
|
*/
|
|
predicate isSafe() {
|
|
this = ["GET", "HEAD", "OPTIONS", "PRI", "PROPFIND", "REPORT", "SEARCH", "TRACE"]
|
|
}
|
|
|
|
/**
|
|
* Holds if this kind of HTTP request should not generally be considered free of side effects,
|
|
* such as for `POST` or `PUT` requests.
|
|
*/
|
|
predicate isUnsafe() { not this.isSafe() }
|
|
}
|
|
|
|
/**
|
|
* An expression whose value is sent as (part of) the body of an HTTP response.
|
|
*/
|
|
abstract class ResponseBody extends DataFlow::Node {
|
|
/**
|
|
* Gets the route handler that sends this expression.
|
|
*/
|
|
abstract RouteHandler getRouteHandler();
|
|
}
|
|
|
|
/**
|
|
* An expression whose value is included directly (and not, say, via a template)
|
|
* in the body of an HTTP response.
|
|
*/
|
|
abstract class ResponseSendArgument extends ResponseBody { }
|
|
|
|
/**
|
|
* An expression that sets a cookie in an HTTP response.
|
|
*/
|
|
abstract class CookieDefinition extends DataFlow::Node {
|
|
/**
|
|
* Gets the argument, if any, specifying the raw cookie header.
|
|
*/
|
|
DataFlow::Node getHeaderArgument() { none() }
|
|
|
|
/**
|
|
* Gets the argument, if any, specifying the cookie name.
|
|
*/
|
|
DataFlow::Node getNameArgument() { none() }
|
|
|
|
/**
|
|
* Gets the argument, if any, specifying the cookie value.
|
|
*/
|
|
DataFlow::Node getValueArgument() { none() }
|
|
|
|
/** Gets the route handler that sets this cookie. */
|
|
abstract RouteHandler getRouteHandler();
|
|
}
|
|
|
|
/**
|
|
* An expression that sets the `Set-Cookie` header of an HTTP response.
|
|
*/
|
|
class SetCookieHeader extends CookieDefinition instanceof HeaderDefinition {
|
|
SetCookieHeader() { super.getAHeaderName() = "set-cookie" }
|
|
|
|
override DataFlow::Node getHeaderArgument() {
|
|
this.(ExplicitHeaderDefinition).definesHeaderValue("set-cookie", result)
|
|
}
|
|
|
|
override RouteHandler getRouteHandler() { result = HeaderDefinition.super.getRouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* An expression that creates a new server.
|
|
*/
|
|
abstract class ServerDefinition extends DataFlow::Node {
|
|
/**
|
|
* Gets a route handler of the server.
|
|
*/
|
|
abstract RouteHandler getARouteHandler();
|
|
}
|
|
|
|
/**
|
|
* A callback for handling a request on some route on a server.
|
|
*/
|
|
abstract class RouteHandler extends DataFlow::Node {
|
|
/**
|
|
* Gets a header this handler sets.
|
|
*
|
|
* Note that header names are case-insensitive; this predicate always converts
|
|
* header names to lower case.
|
|
*/
|
|
abstract HeaderDefinition getAResponseHeader(string name);
|
|
|
|
/**
|
|
* Gets a request object originating from this route handler.
|
|
*
|
|
* Use `RequestSource.ref()` to get reference to this request object.
|
|
*/
|
|
final Servers::RequestSource getARequestSource() { result.getRouteHandler() = this }
|
|
|
|
/**
|
|
* Gets a response object originating from this route handler.
|
|
*
|
|
* Use `ResponseSource.ref()` to get reference to this response object.
|
|
*/
|
|
final Servers::ResponseSource getAResponseSource() { result.getRouteHandler() = this }
|
|
|
|
/**
|
|
* Gets an expression that contains a request object handled
|
|
* by this handler.
|
|
*/
|
|
RequestNode getARequestNode() { result.getRouteHandler() = this }
|
|
|
|
/**
|
|
* Gets an expression that contains a response object provided
|
|
* by this handler.
|
|
*/
|
|
ResponseNode getAResponseNode() { result.getRouteHandler() = this }
|
|
}
|
|
|
|
/**
|
|
* Holds if there exists a step from `pred` to `succ` for a RouteHandler - beyond the usual steps defined by TypeTracking.
|
|
*/
|
|
predicate routeHandlerStep(DataFlow::SourceNode pred, DataFlow::SourceNode succ) {
|
|
// A forwarding call
|
|
DataFlow::functionOneWayForwardingStep(pred.getALocalUse(), succ)
|
|
or
|
|
// a container containing route-handlers.
|
|
exists(Http::RouteHandlerCandidateContainer container | pred = container.getRouteHandler(succ))
|
|
or
|
|
// (function (req, res) {}).bind(this);
|
|
exists(DataFlow::PartialInvokeNode call |
|
|
succ = call.getBoundFunction(any(DataFlow::Node n | pred.flowsTo(n)), 0)
|
|
)
|
|
or
|
|
// references to class methods
|
|
succ = CallGraph::callgraphStep(pred, DataFlow::TypeTracker::end())
|
|
}
|
|
|
|
/**
|
|
* An expression that sets up a route on a server.
|
|
*/
|
|
abstract class RouteSetup extends DataFlow::Node { }
|
|
|
|
/** A dataflow node that may contain a request object. */
|
|
abstract class RequestNode extends DataFlow::Node {
|
|
/** Gets the route handler that handles this request. */
|
|
abstract RouteHandler getRouteHandler();
|
|
}
|
|
|
|
/** An dataflow node that may contain a response object. */
|
|
abstract class ResponseNode extends DataFlow::Node {
|
|
/** Gets the route handler that handles this request. */
|
|
abstract RouteHandler getRouteHandler();
|
|
}
|
|
|
|
/**
|
|
* Boiler-plate implementation of a `Server` and its associated classes.
|
|
* Made for easily defining new HTTP servers
|
|
*/
|
|
module Servers {
|
|
/**
|
|
* A standard server definition.
|
|
*/
|
|
abstract class StandardServerDefinition extends ServerDefinition {
|
|
override RouteHandler getARouteHandler() { result.(StandardRouteHandler).getServer() = this }
|
|
|
|
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
result = this.getALocalSource()
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = this.ref(t2).track(t2, t))
|
|
}
|
|
|
|
/** Gets a data flow node referring to this server. */
|
|
DataFlow::SourceNode ref() { result = this.ref(DataFlow::TypeTracker::end()) }
|
|
}
|
|
|
|
/**
|
|
* A standard route handler.
|
|
*/
|
|
abstract class StandardRouteHandler extends RouteHandler {
|
|
override HeaderDefinition getAResponseHeader(string name) {
|
|
result.getRouteHandler() = this and
|
|
result.getAHeaderName() = name
|
|
}
|
|
|
|
/**
|
|
* Gets the server this route handler is registered on.
|
|
*/
|
|
DataFlow::Node getServer() {
|
|
exists(StandardRouteSetup setup | setup.getARouteHandler() = this |
|
|
result = setup.getServer()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A request source, that is, a data flow node through which
|
|
* a request object enters the flow graph, such as the request
|
|
* parameter of a route handler.
|
|
*/
|
|
abstract class RequestSource extends DataFlow::Node {
|
|
/**
|
|
* Gets the route handler that handles this request.
|
|
*/
|
|
abstract RouteHandler getRouteHandler();
|
|
|
|
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
result = this
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = this.ref(t2).track(t2, t))
|
|
}
|
|
|
|
/** Gets a `SourceNode` that refers to this request object. */
|
|
DataFlow::SourceNode ref() { result = this.ref(DataFlow::TypeTracker::end()) }
|
|
}
|
|
|
|
/**
|
|
* A response source, that is, a data flow node through which
|
|
* a response object enters the flow graph, such as the response
|
|
* parameter of a route handler.
|
|
*/
|
|
abstract class ResponseSource extends DataFlow::Node {
|
|
/**
|
|
* Gets the route handler that provides this response.
|
|
*/
|
|
abstract RouteHandler getRouteHandler();
|
|
|
|
private DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
|
t.start() and
|
|
result = this
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = this.ref(t2).track(t2, t))
|
|
}
|
|
|
|
/** Gets a `SourceNode` that refers to this response object. */
|
|
DataFlow::SourceNode ref() { result = this.ref(DataFlow::TypeTracker::end()) }
|
|
}
|
|
|
|
/**
|
|
* A request expression arising from a request source.
|
|
*/
|
|
class StandardRequestNode extends RequestNode {
|
|
RequestSource src;
|
|
|
|
StandardRequestNode() { src.ref().flowsTo(this) }
|
|
|
|
override RouteHandler getRouteHandler() { result = src.getRouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* A response expression arising from a response source.
|
|
*/
|
|
class StandardResponseNode extends ResponseNode {
|
|
ResponseSource src;
|
|
|
|
StandardResponseNode() { src.ref().flowsTo(this) }
|
|
|
|
override RouteHandler getRouteHandler() { result = src.getRouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* A standard header definition.
|
|
*/
|
|
abstract class StandardHeaderDefinition extends ExplicitHeaderDefinition,
|
|
DataFlow::MethodCallNode
|
|
{
|
|
override predicate definesHeaderValue(string headerName, DataFlow::Node headerValue) {
|
|
headerName = this.getNameNode().getStringValue().toLowerCase() and
|
|
headerValue = this.getArgument(1)
|
|
}
|
|
|
|
override DataFlow::Node getNameNode() { result = this.getArgument(0) }
|
|
}
|
|
|
|
/**
|
|
* A standard route setup on a server.
|
|
*/
|
|
abstract class StandardRouteSetup extends RouteSetup {
|
|
/**
|
|
* Gets a route handler that is defined by this setup.
|
|
*/
|
|
pragma[nomagic]
|
|
abstract DataFlow::SourceNode getARouteHandler();
|
|
|
|
/**
|
|
* Gets the server on which this route setup sets up routes.
|
|
*/
|
|
abstract DataFlow::Node getServer();
|
|
}
|
|
|
|
/**
|
|
* A parameter containing data received by a NodeJS HTTP server.
|
|
* E.g. `chunk` in: `http.createServer().on('request', (req, res) => req.on("data", (chunk) => ...))`.
|
|
*/
|
|
private class ServerRequestDataEvent extends RemoteFlowSource, DataFlow::ParameterNode {
|
|
ServerRequestDataEvent() {
|
|
exists(DataFlow::MethodCallNode mcn, RequestSource req |
|
|
mcn = req.ref().getAMethodCall(EventEmitter::on())
|
|
|
|
|
mcn.getArgument(0).mayHaveStringValue("data") and
|
|
this = mcn.getABoundCallbackParameter(1, 0)
|
|
)
|
|
}
|
|
|
|
override string getSourceType() { result = "NodeJS HTTP server data event" }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An access to a user-controlled HTTP request input.
|
|
*/
|
|
abstract class RequestInputAccess extends RemoteFlowSource {
|
|
override string getSourceType() { result = "Server request " + this.getKind() }
|
|
|
|
/**
|
|
* Gets the route handler whose request input is accessed.
|
|
*/
|
|
abstract RouteHandler getRouteHandler();
|
|
|
|
/**
|
|
* Gets the kind of the accessed input,
|
|
* Can be one of "parameter", "header", "body", "url", "cookie".
|
|
*
|
|
* Note that this predicate is functional.
|
|
*/
|
|
abstract string getKind();
|
|
|
|
/**
|
|
* Holds if this part of the request may be controlled by a third party,
|
|
* that is, an agent other than the one who sent the request.
|
|
*
|
|
* This is true for the URL, query parameters, and request body.
|
|
* These can be controlled by a malicious third party in the following scenarios:
|
|
*
|
|
* - The user clicks a malicious link or is otherwise redirected to a malicious URL.
|
|
* - The user visits a web site that initiates a form submission or AJAX request on their behalf.
|
|
*
|
|
* In these cases, the request is technically sent from the user's browser, but
|
|
* the user is not in direct control of the URL or POST body.
|
|
*
|
|
* Headers are never considered third-party controllable by this predicate, although the
|
|
* third party does have some control over the the Referer and Origin headers.
|
|
*/
|
|
predicate isThirdPartyControllable() { this.getKind() = ["parameter", "url", "body"] }
|
|
}
|
|
|
|
/**
|
|
* An access to a header on an incoming HTTP request.
|
|
*/
|
|
abstract class RequestHeaderAccess extends RequestInputAccess {
|
|
/**
|
|
* Gets the lower-case name of an HTTP header from which this input is derived,
|
|
* if this can be determined.
|
|
*
|
|
* When the name of the header is unknown, this has no result.
|
|
*/
|
|
abstract string getAHeaderName();
|
|
}
|
|
|
|
/**
|
|
* A node that looks like a route setup on a server.
|
|
*
|
|
* This is useful for tasks such as heuristic analyses
|
|
* and exploratory queries.
|
|
*/
|
|
abstract class RouteSetupCandidate extends DataFlow::ValueNode {
|
|
/**
|
|
* Gets an expression that contains a route handler of this setup.
|
|
*/
|
|
abstract DataFlow::ValueNode getARouteHandlerArg();
|
|
}
|
|
|
|
/**
|
|
* A function that looks like a route handler.
|
|
*
|
|
* This is useful for tasks such as heuristic analyses
|
|
* and exploratory queries.
|
|
*/
|
|
abstract class RouteHandlerCandidate extends DataFlow::FunctionNode { }
|
|
|
|
/**
|
|
* An expression that creates a route handler that parses cookies
|
|
*/
|
|
abstract class CookieMiddlewareInstance extends DataFlow::SourceNode {
|
|
/**
|
|
* Gets a secret key used for signed cookies.
|
|
*/
|
|
abstract DataFlow::Node getASecretKey();
|
|
}
|
|
|
|
/**
|
|
* A key used for signed cookies, viewed as a `CryptographicKey`.
|
|
*/
|
|
class CookieCryptographicKey extends CryptographicKey {
|
|
CookieCryptographicKey() { this = any(CookieMiddlewareInstance instance).getASecretKey() }
|
|
}
|
|
|
|
/**
|
|
* An object that contains one or more potential route handlers.
|
|
*/
|
|
class RouteHandlerCandidateContainer extends DataFlow::Node instanceof RouteHandlerCandidateContainer::Range
|
|
{
|
|
/**
|
|
* Gets the route handler in this container that is accessed at `access`.
|
|
*/
|
|
DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
|
|
result = super.getRouteHandler(access)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides classes for working with objects that may contain one or more route handlers.
|
|
*/
|
|
module RouteHandlerCandidateContainer {
|
|
private DataFlow::SourceNode ref(DataFlow::TypeTracker t, RouteHandlerCandidateContainer c) {
|
|
t.start() and result = c
|
|
or
|
|
exists(DataFlow::TypeTracker t2 | result = ref(t2, c).track(t2, t))
|
|
}
|
|
|
|
private DataFlow::SourceNode ref(RouteHandlerCandidateContainer c) {
|
|
result = ref(DataFlow::TypeTracker::end(), c)
|
|
}
|
|
|
|
/**
|
|
* A container for one or more potential route handlers.
|
|
*
|
|
* Extend this class and implement its abstract member predicates to model additional
|
|
* containers.
|
|
*/
|
|
abstract class Range extends DataFlow::SourceNode {
|
|
/**
|
|
* Gets the route handler in this container that is accessed at `access`.
|
|
*/
|
|
abstract DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access);
|
|
}
|
|
|
|
/**
|
|
* An object that contains one or more potential route handlers.
|
|
*/
|
|
private class ContainerObject extends Range {
|
|
ContainerObject() {
|
|
(
|
|
this instanceof DataFlow::ObjectLiteralNode
|
|
or
|
|
exists(DataFlow::CallNode create | this = create |
|
|
create = DataFlow::globalVarRef("Object").getAMemberCall("create") and
|
|
create.getArgument(0).asExpr() instanceof NullLiteral
|
|
)
|
|
) and
|
|
getAPossiblyDecoratedHandler(_).flowsTo(this.getAPropertyWrite().getRhs())
|
|
}
|
|
|
|
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
|
|
result instanceof RouteHandlerCandidate and
|
|
exists(DataFlow::PropWrite write, DataFlow::PropRead read |
|
|
access = read and
|
|
ref(this).getAPropertyRead() = read and
|
|
getAPossiblyDecoratedHandler(result).flowsTo(write.getRhs()) and
|
|
write = this.getAPropertyWrite()
|
|
|
|
|
write.getPropertyName() = read.getPropertyName()
|
|
or
|
|
exists(EnumeratedPropName prop | access = prop.getASourceProp())
|
|
or
|
|
read = DataFlow::lvalueNode(any(ForOfStmt stmt).getLValue())
|
|
or
|
|
// for forwarding calls to an element where the key is determined by the request.
|
|
getRequestParameterRead().flowsToExpr(read.getPropertyNameExpr())
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a (chained) property-read/method-call on the request parameter of the route-handler `f`.
|
|
*/
|
|
private DataFlow::SourceNode getRequestParameterRead() {
|
|
result = any(RouteHandlerCandidate f).getParameter(0)
|
|
or
|
|
result = getRequestParameterRead().getAPropertyRead()
|
|
or
|
|
result = getRequestParameterRead().getAMethodCall()
|
|
}
|
|
|
|
/**
|
|
* Gets a node that is either `candidate`, or a call that decorates `candidate`.
|
|
*/
|
|
DataFlow::SourceNode getAPossiblyDecoratedHandler(RouteHandlerCandidate candidate) {
|
|
result = candidate
|
|
or
|
|
DataFlow::functionOneWayForwardingStep(candidate, result)
|
|
}
|
|
|
|
private string mapValueProp() {
|
|
result = [PseudoProperties::mapValueAll(), PseudoProperties::mapValueUnknownKey()]
|
|
}
|
|
|
|
/**
|
|
* A collection that contains one or more route potential handlers.
|
|
*/
|
|
private class ContainerCollection extends Http::RouteHandlerCandidateContainer::Range,
|
|
DataFlow::NewNode
|
|
{
|
|
ContainerCollection() {
|
|
this = DataFlow::globalVarRef("Map").getAnInstantiation() and // restrict to Map for now
|
|
exists(DataFlow::Node use |
|
|
DataFlow::SharedTypeTrackingStep::storeStep(use, this, mapValueProp()) and
|
|
use.getALocalSource() instanceof RouteHandlerCandidate
|
|
)
|
|
}
|
|
|
|
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
|
|
exists(DataFlow::Node input, string key, DataFlow::Node loadFrom |
|
|
getAPossiblyDecoratedHandler(result).flowsTo(input) and
|
|
DataFlow::SharedTypeTrackingStep::storeStep(input, this, key) and
|
|
ref(this).flowsTo(loadFrom) and
|
|
DataFlow::SharedTypeTrackingStep::loadStep(loadFrom, access,
|
|
[key, PseudoProperties::mapValueAll()])
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|