mirror of
https://github.com/github/codeql.git
synced 2026-02-24 02:43:40 +01:00
316 lines
9.5 KiB
Plaintext
316 lines
9.5 KiB
Plaintext
/**
|
|
* Provides classes for working with [Hapi](https://hapijs.com/) servers.
|
|
*/
|
|
|
|
import javascript
|
|
import semmle.javascript.frameworks.HTTP
|
|
|
|
module Hapi {
|
|
/**
|
|
* An expression that creates a new Hapi server.
|
|
*/
|
|
class ServerDefinition extends Http::Servers::StandardServerDefinition, DataFlow::Node {
|
|
ServerDefinition() {
|
|
// `server = new Hapi.Server()`
|
|
this = DataFlow::moduleMember("hapi", "Server").getAnInstantiation()
|
|
or
|
|
// `server = Glue.compose(manifest, composeOptions)`
|
|
this = DataFlow::moduleMember("@hapi/glue", "compose").getAnInvocation()
|
|
or
|
|
// `register (server, options)`
|
|
// `module.exports.plugin = {register, pkg};`
|
|
this =
|
|
any(Module m)
|
|
.getAnExportedValue("plugin")
|
|
.getALocalSource()
|
|
.getAPropertySource("register")
|
|
.getAFunctionValue()
|
|
.getParameter(0)
|
|
or
|
|
// `const after = function (server) {...};`
|
|
// `server.dependency('name', after);`
|
|
this =
|
|
any(ServerDefinition s).ref().getAMethodCall("dependency").getABoundCallbackParameter(1, 0)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A Hapi route handler.
|
|
*/
|
|
class RouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
|
|
RouteHandler() { exists(RouteSetup setup | this = setup.getARouteHandler()) }
|
|
|
|
/**
|
|
* Gets the parameter of the route handler that contains the request object.
|
|
*/
|
|
DataFlow::ParameterNode getRequestParameter() { result = this.getParameter(0) }
|
|
|
|
/**
|
|
* Gets the parameter of the route handler that contains the "request toolkit",
|
|
* usually named `h`.
|
|
*/
|
|
DataFlow::ParameterNode getRequestToolkitParameter() { result = this.getParameter(1) }
|
|
|
|
/**
|
|
* Gets a source node referring to the request toolkit parameter, usually named `h`.
|
|
*/
|
|
DataFlow::SourceNode getRequestToolkit() { result = this.getRequestToolkitParameter() }
|
|
}
|
|
|
|
/**
|
|
* A Hapi response source, that is, an access to the `response` property
|
|
* of a request object.
|
|
*/
|
|
private class ResponseSource extends Http::Servers::ResponseSource {
|
|
RequestNode req;
|
|
|
|
ResponseSource() { this.(DataFlow::PropRead).accesses(req, "response") }
|
|
|
|
/**
|
|
* Gets the route handler that provides this response.
|
|
*/
|
|
override RouteHandler getRouteHandler() { result = req.getRouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* A Hapi request source, that is, the request parameter of a
|
|
* route handler.
|
|
*/
|
|
private class RequestSource extends Http::Servers::RequestSource {
|
|
RouteHandler rh;
|
|
|
|
RequestSource() { this = rh.getRequestParameter() }
|
|
|
|
/**
|
|
* Gets the route handler that handles this request.
|
|
*/
|
|
override RouteHandler getRouteHandler() { result = rh }
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED: Use `ResponseNode` instead.
|
|
* A Hapi response expression.
|
|
*/
|
|
deprecated class ResponseExpr extends HTTP::Servers::StandardResponseExpr {
|
|
ResponseExpr() { this.flow() instanceof ResponseNode }
|
|
}
|
|
|
|
/**
|
|
* A Hapi response node.
|
|
*/
|
|
class ResponseNode extends Http::Servers::StandardResponseNode {
|
|
override ResponseSource src;
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED: Use `RequestNode` instead.
|
|
* An Hapi request expression.
|
|
*/
|
|
deprecated class RequestExpr extends HTTP::Servers::StandardRequestExpr {
|
|
RequestExpr() { this.flow() instanceof RequestNode }
|
|
}
|
|
|
|
/**
|
|
* A Hapi request node.
|
|
*/
|
|
class RequestNode extends Http::Servers::StandardRequestNode {
|
|
override RequestSource src;
|
|
}
|
|
|
|
/**
|
|
* An access to a user-controlled Hapi request input.
|
|
*/
|
|
private class RequestInputAccess extends Http::RequestInputAccess {
|
|
RouteHandler rh;
|
|
string kind;
|
|
|
|
RequestInputAccess() {
|
|
exists(DataFlow::Node request | request = rh.getARequestNode() |
|
|
kind = "body" and
|
|
(
|
|
// `request.rawPayload`
|
|
this.(DataFlow::PropRead).accesses(request, "rawPayload")
|
|
or
|
|
exists(DataFlow::PropRead payload |
|
|
// `request.payload.name`
|
|
payload.accesses(request, "payload") and
|
|
this.(DataFlow::PropRead).accesses(payload, _)
|
|
)
|
|
)
|
|
or
|
|
kind = "parameter" and
|
|
exists(DataFlow::PropRead query |
|
|
// `request.query.name`
|
|
query.accesses(request, ["query", "params"]) and
|
|
this.(DataFlow::PropRead).accesses(query, _)
|
|
)
|
|
or
|
|
exists(DataFlow::PropRead url |
|
|
// `request.url.path`
|
|
kind = "url" and
|
|
url.accesses(request, "url") and
|
|
this.(DataFlow::PropRead).accesses(url, ["path", "origin"])
|
|
)
|
|
or
|
|
exists(DataFlow::PropRead state |
|
|
// `request.state.<name>`
|
|
kind = "cookie" and
|
|
state.accesses(request, "state") and
|
|
this.(DataFlow::PropRead).accesses(state, _)
|
|
)
|
|
)
|
|
or
|
|
exists(RequestHeaderAccess access | this = access |
|
|
rh = access.getRouteHandler() and
|
|
kind = "header"
|
|
)
|
|
}
|
|
|
|
override RouteHandler getRouteHandler() { result = rh }
|
|
|
|
override string getKind() { result = kind }
|
|
}
|
|
|
|
/**
|
|
* An access to an HTTP header on a Hapi request.
|
|
*/
|
|
private class RequestHeaderAccess extends Http::RequestHeaderAccess {
|
|
RouteHandler rh;
|
|
|
|
RequestHeaderAccess() {
|
|
exists(DataFlow::Node request | request = rh.getARequestNode() |
|
|
exists(DataFlow::PropRead headers |
|
|
// `request.headers.<name>`
|
|
headers.accesses(request, "headers") and
|
|
this.(DataFlow::PropRead).accesses(headers, _)
|
|
)
|
|
)
|
|
}
|
|
|
|
override string getAHeaderName() {
|
|
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
|
|
}
|
|
|
|
override RouteHandler getRouteHandler() { result = rh }
|
|
|
|
override string getKind() { result = "header" }
|
|
}
|
|
|
|
/**
|
|
* An HTTP header defined in a Hapi server.
|
|
*/
|
|
private class HeaderDefinition extends Http::Servers::StandardHeaderDefinition {
|
|
ResponseNode res;
|
|
|
|
HeaderDefinition() {
|
|
// request.response.header('Cache-Control', 'no-cache')
|
|
this.calls(res, "header")
|
|
}
|
|
|
|
override RouteHandler getRouteHandler() { result = res.getRouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* A call to a Hapi method that sets up a route.
|
|
*/
|
|
class RouteSetup extends DataFlow::MethodCallNode, Http::Servers::StandardRouteSetup {
|
|
ServerDefinition server;
|
|
DataFlow::Node handler;
|
|
|
|
RouteSetup() {
|
|
server.ref().getAMethodCall() = this and
|
|
(
|
|
// server.route({ handler: fun })
|
|
this.getMethodName() = "route" and
|
|
this.getOptionArgument(0, "handler") = handler
|
|
or
|
|
// server.ext('/', fun)
|
|
this.getMethodName() = "ext" and
|
|
handler = this.getArgument(1)
|
|
or
|
|
// server.route([{ handler(request){}])
|
|
this.getMethodName() = "route" and
|
|
handler =
|
|
this.getArgument(0)
|
|
.getALocalSource()
|
|
.(DataFlow::ArrayCreationNode)
|
|
.getAnElement()
|
|
.getALocalSource()
|
|
.getAPropertySource("handler")
|
|
.getAFunctionValue()
|
|
)
|
|
}
|
|
|
|
override DataFlow::SourceNode getARouteHandler() {
|
|
result = this.getARouteHandler(DataFlow::TypeBackTracker::end())
|
|
}
|
|
|
|
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
|
|
t.start() and
|
|
result = this.getRouteHandler().getALocalSource()
|
|
or
|
|
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
|
|
}
|
|
|
|
pragma[noinline]
|
|
private DataFlow::Node getRouteHandler() { result = handler }
|
|
|
|
deprecated Expr getRouteHandlerExpr() { result = handler.asExpr() }
|
|
|
|
override DataFlow::Node getServer() { result = server }
|
|
}
|
|
|
|
/**
|
|
* A function that looks like a Hapi route handler.
|
|
*
|
|
* For example, this could be the function `function(request, h){...}`.
|
|
*/
|
|
class RouteHandlerCandidate extends Http::RouteHandlerCandidate {
|
|
RouteHandlerCandidate() {
|
|
exists(string request, string responseToolkit |
|
|
(request = "request" or request = "req") and
|
|
responseToolkit = ["h", "hapi"] and
|
|
// heuristic: parameter names match the Hapi documentation
|
|
astNode.getNumParameter() = 2 and
|
|
astNode.getParameter(0).getName() = request and
|
|
astNode.getParameter(1).getName() = responseToolkit
|
|
|
|
|
// heuristic: is not invoked (Hapi invokes this at a call site we cannot reason precisely about)
|
|
not exists(DataFlow::InvokeNode cs | cs.getACallee() = astNode)
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A function that looks like a Hapi route handler and flows to a route setup.
|
|
*/
|
|
private class TrackedRouteHandlerCandidateWithSetup extends RouteHandler,
|
|
Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
|
|
TrackedRouteHandlerCandidateWithSetup() { this = any(RouteSetup s).getARouteHandler() }
|
|
}
|
|
|
|
/**
|
|
* A call to `h.view('file', { ... })` seen as a template instantiation.
|
|
*/
|
|
private class ViewCall extends Templating::TemplateInstantiation::Range, DataFlow::CallNode {
|
|
ViewCall() { this = any(RouteHandler rh).getRequestToolkit().getAMethodCall("view") }
|
|
|
|
override DataFlow::SourceNode getOutput() { none() }
|
|
|
|
override DataFlow::Node getTemplateFileNode() { result = this.getArgument(0) }
|
|
|
|
override DataFlow::Node getTemplateParamsNode() { result = this.getArgument(1) }
|
|
}
|
|
|
|
/**
|
|
* A return from a route handler.
|
|
*/
|
|
private class HandlerReturn extends Http::ResponseSendArgument {
|
|
RouteHandler handler;
|
|
|
|
HandlerReturn() { this = handler.(DataFlow::FunctionNode).getAReturn() }
|
|
|
|
override RouteHandler getRouteHandler() { result = handler }
|
|
}
|
|
}
|