Files
codeql/javascript/ql/lib/semmle/javascript/frameworks/Hapi.qll
2022-02-22 14:12:01 +01:00

276 lines
7.9 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, NewExpr {
ServerDefinition() {
// `server = new Hapi.Server()`
this = DataFlow::moduleMember("hapi", "Server").getAnInstantiation().asExpr()
}
}
/**
* A Hapi route handler.
*/
class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
Function function;
RouteHandler() {
function = astNode and
exists(RouteSetup setup | this = setup.getARouteHandler())
}
/**
* Gets the parameter of the route handler that contains the request object.
*/
Parameter getRequestParameter() { result = function.getParameter(0) }
/**
* Gets the parameter of the route handler that contains the "request toolkit",
* usually named `h`.
*/
Parameter getRequestToolkitParameter() { result = function.getParameter(1) }
/**
* Gets a source node referring to the request toolkit parameter, usually named `h`.
*/
DataFlow::SourceNode getRequestToolkit() { result = getRequestToolkitParameter().flow() }
}
/**
* A Hapi response source, that is, an access to the `response` property
* of a request object.
*/
private class ResponseSource extends HTTP::Servers::ResponseSource {
RequestExpr req;
ResponseSource() { asExpr().(PropAccess).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 = DataFlow::parameterNode(rh.getRequestParameter()) }
/**
* Gets the route handler that handles this request.
*/
override RouteHandler getRouteHandler() { result = rh }
}
/**
* A Hapi response expression.
*/
class ResponseExpr extends HTTP::Servers::StandardResponseExpr {
override ResponseSource src;
}
/**
* An Hapi request expression.
*/
class RequestExpr extends HTTP::Servers::StandardRequestExpr {
override RequestSource src;
}
/**
* An access to a user-controlled Hapi request input.
*/
private class RequestInputAccess extends HTTP::RequestInputAccess {
RouteHandler rh;
string kind;
RequestInputAccess() {
exists(Expr request | request = rh.getARequestExpr() |
kind = "body" and
(
// `request.rawPayload`
this.asExpr().(PropAccess).accesses(request, "rawPayload")
or
exists(PropAccess payload |
// `request.payload.name`
payload.accesses(request, "payload") and
this.asExpr().(PropAccess).accesses(payload, _)
)
)
or
kind = "parameter" and
exists(PropAccess query |
// `request.query.name`
query.accesses(request, "query") and
this.asExpr().(PropAccess).accesses(query, _)
)
or
exists(PropAccess url |
// `request.url.path`
kind = "url" and
url.accesses(request, "url") and
this.asExpr().(PropAccess).accesses(url, "path")
)
or
exists(PropAccess state |
// `request.state.<name>`
kind = "cookie" and
state.accesses(request, "state") and
this.asExpr().(PropAccess).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(Expr request | request = rh.getARequestExpr() |
exists(PropAccess headers |
// `request.headers.<name>`
headers.accesses(request, "headers") and
this.asExpr().(PropAccess).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 {
ResponseExpr res;
HeaderDefinition() {
// request.response.header('Cache-Control', 'no-cache')
astNode.calls(res, "header")
}
override RouteHandler getRouteHandler() { result = res.getRouteHandler() }
}
/**
* A call to a Hapi method that sets up a route.
*/
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
ServerDefinition server;
Expr handler;
RouteSetup() {
server.flowsTo(getReceiver()) and
(
// server.route({ handler: fun })
getMethodName() = "route" and
hasOptionArgument(0, "handler", handler)
or
// server.ext('/', fun)
getMethodName() = "ext" and
handler = getArgument(1)
)
}
override DataFlow::SourceNode getARouteHandler() {
result = getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = getRouteHandler().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = getARouteHandler(t2).backtrack(t2, t))
}
pragma[noinline]
private DataFlow::Node getRouteHandler() { result = handler.flow() }
Expr getRouteHandlerExpr() { result = handler }
override Expr 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" 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 = getArgument(0) }
override DataFlow::Node getTemplateParamsNode() { result = getArgument(1) }
}
/**
* A return from a route handler.
*/
private class HandlerReturn extends HTTP::ResponseSendArgument {
RouteHandler handler;
HandlerReturn() { this = handler.(DataFlow::FunctionNode).getAReturn().asExpr() }
override RouteHandler getRouteHandler() { result = handler }
}
}