mirror of
https://github.com/github/codeql.git
synced 2026-04-29 10:45:15 +02:00
Add Restify/Spife support
This commit is contained in:
committed by
Alvaro Muñoz
parent
42a97b26bb
commit
6ab62da015
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
|
||||
- Improved support for [Restify](http://restify.com/) framework, leading to more results when scanning applications developed with this framework.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: feature
|
||||
---
|
||||
|
||||
- Added support for the [Spife](https://github.com/npm/spife) framework.
|
||||
@@ -7,3 +7,4 @@ import semmle.javascript.frameworks.Micro
|
||||
import semmle.javascript.frameworks.Restify
|
||||
import semmle.javascript.frameworks.Connect
|
||||
import semmle.javascript.frameworks.Fastify
|
||||
import semmle.javascript.frameworks.Spife
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.frameworks.HTTP
|
||||
import semmle.javascript.security.dataflow.RequestForgeryCustomizations as RFC
|
||||
|
||||
/**
|
||||
* Provides classes for working with [Restify](https://restify.com/) servers.
|
||||
*/
|
||||
module Restify {
|
||||
/**
|
||||
* An expression that creates a new Restify server.
|
||||
@@ -19,23 +23,23 @@ module Restify {
|
||||
/**
|
||||
* A Restify route handler.
|
||||
*/
|
||||
class RouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::ValueNode {
|
||||
Function function;
|
||||
|
||||
RouteHandler() {
|
||||
function = astNode and
|
||||
any(RouteSetup setup).getARouteHandler() = this
|
||||
}
|
||||
|
||||
abstract class RouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the request object.
|
||||
*/
|
||||
Parameter getRequestParameter() { result = function.getParameter(0) }
|
||||
DataFlow::ParameterNode getRequestParameter() { result = this.getParameter(0) }
|
||||
|
||||
/**
|
||||
* Gets the parameter of the route handler that contains the response object.
|
||||
*/
|
||||
Parameter getResponseParameter() { result = function.getParameter(1) }
|
||||
DataFlow::ParameterNode getResponseParameter() { result = this.getParameter(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard Restify route handler.
|
||||
*/
|
||||
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
|
||||
StandardRouteHandler() { any(RouteSetup setup).getARouteHandler() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,7 +49,7 @@ module Restify {
|
||||
private class ResponseSource extends Http::Servers::ResponseSource {
|
||||
RouteHandler rh;
|
||||
|
||||
ResponseSource() { this = DataFlow::parameterNode(rh.getResponseParameter()) }
|
||||
ResponseSource() { this = rh.getResponseParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that provides this response.
|
||||
@@ -60,7 +64,7 @@ module Restify {
|
||||
private class RequestSource extends Http::Servers::RequestSource {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestSource() { this = DataFlow::parameterNode(rh.getRequestParameter()) }
|
||||
RequestSource() { this = rh.getRequestParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that handles this request.
|
||||
@@ -80,7 +84,7 @@ module Restify {
|
||||
* A Node.js HTTP response provided by Restify.
|
||||
*/
|
||||
class ResponseNode extends NodeJSLib::ResponseNode {
|
||||
ResponseNode() { src instanceof ResponseSource }
|
||||
ResponseNode() { src instanceof ResponseSource or src instanceof FormatterResponseSource }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,14 +99,14 @@ module Restify {
|
||||
* A Node.js HTTP request provided by Restify.
|
||||
*/
|
||||
class RequestNode extends NodeJSLib::RequestNode {
|
||||
RequestNode() { src instanceof RequestSource }
|
||||
RequestNode() { src instanceof RequestSource or src instanceof FormatterRequestSource }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a user-controlled Restify request input.
|
||||
*/
|
||||
private class RequestInputAccess extends Http::RequestInputAccess {
|
||||
RequestNode request;
|
||||
Http::RequestNode request;
|
||||
string kind;
|
||||
|
||||
RequestInputAccess() {
|
||||
@@ -113,23 +117,17 @@ module Restify {
|
||||
this.(DataFlow::PropRead).accesses(query, _)
|
||||
)
|
||||
or
|
||||
exists(string methodName |
|
||||
// `request.href()` or `request.getPath()`
|
||||
kind = "url" and
|
||||
this.(DataFlow::MethodCallNode).calls(request, methodName)
|
||||
|
|
||||
methodName = "href" or
|
||||
methodName = "getPath"
|
||||
exists(DataFlow::PropRead prop |
|
||||
// `request.params.<name>`
|
||||
// `request.query.<name>`
|
||||
kind = "parameter" and
|
||||
prop.accesses(request, ["params", "query"]) and
|
||||
this.(DataFlow::PropRead).accesses(prop, _)
|
||||
)
|
||||
or
|
||||
// `request.getContentType()`, `request.userAgent()`, `request.trailer(...)`, `request.header(...)`
|
||||
kind = "header" and
|
||||
this.(DataFlow::MethodCallNode)
|
||||
.calls(request, ["getContentType", "userAgent", "trailer", "header"])
|
||||
or
|
||||
// `req.cookies
|
||||
kind = "cookie" and
|
||||
this.(DataFlow::PropRead).accesses(request, "cookies")
|
||||
// `request.href()` or `request.getPath()`
|
||||
kind = "url" and
|
||||
this.(DataFlow::MethodCallNode).calls(request, ["href", "getPath"])
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = request.getRouteHandler() }
|
||||
@@ -137,12 +135,35 @@ module Restify {
|
||||
override string getKind() { result = kind }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a header on a Restify request.
|
||||
*/
|
||||
private class RequestHeaderAccess extends Http::RequestHeaderAccess {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestHeaderAccess() {
|
||||
// `request.getContentType()`, `request.userAgent()`, `request.trailer(...)`, `request.header(...)`
|
||||
this =
|
||||
rh.getARequestSource()
|
||||
.ref()
|
||||
.getAMethodCall(["header", "trailer", "userAgent", "getContentType"])
|
||||
}
|
||||
|
||||
override string getAHeaderName() {
|
||||
result = this.(DataFlow::MethodCallNode).getArgument(0).getStringValue().toLowerCase()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = "header" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An HTTP header defined in a Restify server.
|
||||
*/
|
||||
private class HeaderDefinition extends Http::Servers::StandardHeaderDefinition {
|
||||
HeaderDefinition() {
|
||||
// response.header('Cache-Control', 'no-cache')
|
||||
// res.header('Cache-Control', 'no-cache')
|
||||
this.getReceiver() instanceof ResponseNode and
|
||||
this.getMethodName() = "header"
|
||||
}
|
||||
@@ -150,6 +171,41 @@ module Restify {
|
||||
override RouteHandler getRouteHandler() { this.getReceiver() = result.getAResponseNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that sets any number of headers of the HTTP response.
|
||||
*/
|
||||
private class MultipleHeaderDefinitions extends Http::ExplicitHeaderDefinition,
|
||||
DataFlow::MethodCallNode {
|
||||
MultipleHeaderDefinitions() {
|
||||
// res.set({'Cache-Control': 'no-cache'})
|
||||
this.getReceiver() instanceof ResponseNode and
|
||||
this.getMethodName() = "set"
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the multiple headers object that is to be set.
|
||||
*/
|
||||
private DataFlow::SourceNode getAHeaderSource() {
|
||||
this.getArgument(0).getALocalSource() instanceof DataFlow::ObjectLiteralNode and
|
||||
result.flowsTo(this.getArgument(0))
|
||||
}
|
||||
|
||||
override predicate definesHeaderValue(string headerName, DataFlow::Node headerValue) {
|
||||
exists(string header |
|
||||
this.getAHeaderSource().hasPropertyWrite(header, headerValue) and
|
||||
headerName = header.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameNode() {
|
||||
exists(DataFlow::PropWrite write | this.getAHeaderSource().getAPropertyWrite() = write |
|
||||
result = write.getPropertyNameExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { this.getReceiver() = result.getAResponseNode() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a Restify method that sets up a route.
|
||||
*/
|
||||
@@ -157,13 +213,263 @@ module Restify {
|
||||
ServerDefinition server;
|
||||
|
||||
RouteSetup() {
|
||||
// server.get('/', fun)
|
||||
// server.head('/', fun)
|
||||
server.ref().getAMethodCall(any(Http::RequestMethodName m).toLowerCase()) = this
|
||||
server
|
||||
.ref()
|
||||
.getAMethodCall([
|
||||
"del", "get", "head", "opts", "post", "put", "patch", "param", "pre", "use", "on"
|
||||
]) = this
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandler() { result.flowsTo(this.getArgument(1)) }
|
||||
override DataFlow::SourceNode getARouteHandler() {
|
||||
exists(DataFlow::Node arg |
|
||||
// server.get('/', fun)
|
||||
// server.get('/', fun1, fun2)
|
||||
// server.get('/', [fun1, fun2])
|
||||
// server.param('name', fun)
|
||||
// server.on('event', fun)
|
||||
this.getMethodName() = ["del", "get", "head", "opts", "post", "put", "patch", "param", "on"] and
|
||||
arg = this.getAnArgument() and
|
||||
not arg = this.getArgument(0)
|
||||
or
|
||||
// server.use(fun)
|
||||
// server.use(fun1, fun2)
|
||||
// server.use([fun1, fun2])
|
||||
this.getMethodName() = ["use", "pre"] and
|
||||
arg = this.getAnArgument()
|
||||
|
|
||||
(
|
||||
// server.use(fun1, fun2)
|
||||
result.flowsTo(arg) and
|
||||
not arg.getALocalSource() instanceof DataFlow::ArrayCreationNode
|
||||
or
|
||||
result.flowsTo(arg.getALocalSource().(DataFlow::ArrayCreationNode).getAnElement())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getServer() { result = server }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a Restify's createServer method that sets up a formatter.
|
||||
*/
|
||||
class FormatterSetup extends DataFlow::CallNode {
|
||||
DataFlow::ObjectLiteralNode formatters;
|
||||
|
||||
FormatterSetup() {
|
||||
// `server = restify.createServer({ formatters: { ... } })`
|
||||
this = DataFlow::moduleMember("restify", "createServer").getACall() and
|
||||
this.getArgument(0)
|
||||
.getALocalSource()
|
||||
.(DataFlow::ObjectLiteralNode)
|
||||
.hasPropertyWrite("formatters", formatters)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode getAFormatterHandler() { formatters.hasPropertyWrite(_, result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Restify route handler.
|
||||
*/
|
||||
class FormatterHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
|
||||
Function function;
|
||||
|
||||
FormatterHandler() {
|
||||
function = astNode and
|
||||
any(FormatterSetup setup).getAFormatterHandler() = this
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parameter of the formatter handler that contains the request object.
|
||||
*/
|
||||
Parameter getRequestParameter() { result = function.getParameter(0) }
|
||||
|
||||
/**
|
||||
* Gets the parameter of the formatter handler that contains the response object.
|
||||
*/
|
||||
Parameter getResponseParameter() { result = function.getParameter(1) }
|
||||
|
||||
/**
|
||||
* Gets the parameter of the formatter handler that contains the body object.
|
||||
*/
|
||||
Parameter getBodyParameter() { result = function.getParameter(2) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Restify request source, that is, the request parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class FormatterRequestSource extends Http::Servers::RequestSource {
|
||||
FormatterHandler fh;
|
||||
|
||||
FormatterRequestSource() { this = DataFlow::parameterNode(fh.getRequestParameter()) }
|
||||
|
||||
/**
|
||||
* Gets the formatter handler that handles this request.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = fh }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Restify response source, that is, the response parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class FormatterResponseSource extends Http::Servers::ResponseSource {
|
||||
FormatterHandler fh;
|
||||
|
||||
FormatterResponseSource() { this = DataFlow::parameterNode(fh.getResponseParameter()) }
|
||||
|
||||
/**
|
||||
* Gets the route handler that provides this response.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = fh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An argument passed to the `send` method of an HTTP response object.
|
||||
*/
|
||||
private class ResponseSendArgument extends Http::ResponseSendArgument {
|
||||
RouteHandler rh;
|
||||
|
||||
ResponseSendArgument() {
|
||||
this = rh.getAResponseSource().ref().getAMethodCall(["send", "sendRaw"]).getArgument(0)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression returned by a formatter
|
||||
*/
|
||||
private class FormatterOutput extends Http::ResponseSendArgument {
|
||||
FormatterHandler fh;
|
||||
|
||||
FormatterOutput() { this = fh.getAReturn() }
|
||||
|
||||
override Http::RouteHandler getRouteHandler() { result = fh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of the `redirect` method of an HTTP response object.
|
||||
*/
|
||||
private class RedirectInvocation extends Http::RedirectInvocation, DataFlow::MethodCallNode {
|
||||
RouteHandler rh;
|
||||
|
||||
RedirectInvocation() { this = rh.getAResponseSource().ref().getAMethodCall("redirect") }
|
||||
|
||||
override DataFlow::Node getUrlArgument() {
|
||||
this.getNumArgument() = 3 and
|
||||
result = this.getArgument(1)
|
||||
or
|
||||
this.getNumArgument() = 2 and
|
||||
this.getArgument(0)
|
||||
.getALocalSource()
|
||||
.(DataFlow::ObjectLiteralNode)
|
||||
.hasPropertyWrite("hostname", result)
|
||||
or
|
||||
this.getNumArgument() = 2 and
|
||||
result = this.getArgument(0)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that looks like a Restify route handler.
|
||||
*
|
||||
* For example, this could be the function `function(req, res, next){...}`.
|
||||
*/
|
||||
class RouteHandlerCandidate extends Http::RouteHandlerCandidate {
|
||||
RouteHandlerCandidate() {
|
||||
// heuristic: parameter names match the Restify documentation
|
||||
astNode.getNumParameter() = [2, 3] and
|
||||
astNode.getParameter(0).getName() = ["request", "req"] and
|
||||
astNode.getParameter(1).getName() = ["response", "res"] and
|
||||
not astNode.getParameter(2).getName() != "next" and
|
||||
// heuristic: is not invoked (Restify invokes this at a call site we cannot reason precisely about)
|
||||
not exists(DataFlow::InvokeNode cs | cs.getACallee() = astNode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of a REstify client, viewed as a sink for request forgery.
|
||||
*/
|
||||
class RequestForgerySink extends RFC::RequestForgery::Sink {
|
||||
RequestForgerySink() {
|
||||
exists(DataFlow::Node arg |
|
||||
DataFlow::moduleMember("restify-clients",
|
||||
["createClient", "createJsonClient", "createStringClient"]).getACall().getArgument(0) =
|
||||
arg and
|
||||
(
|
||||
arg.getALocalSource().(DataFlow::ObjectLiteralNode).hasPropertyWrite("url", this)
|
||||
or
|
||||
not arg.getALocalSource() instanceof DataFlow::ObjectLiteralNode and
|
||||
this = arg
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getARequest() {
|
||||
// returning the createClient argument itself since there is no request associated to the client yet.
|
||||
// `getARequest()` is only used for display purposes
|
||||
result = this
|
||||
}
|
||||
|
||||
override string getKind() { result = "host" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A header produced by a formatter
|
||||
*/
|
||||
private class FormatterContentTypeHeader extends Http::ImplicitHeaderDefinition,
|
||||
DataFlow::FunctionNode instanceof FormatterHandler {
|
||||
string contentType;
|
||||
|
||||
FormatterContentTypeHeader() {
|
||||
exists(DataFlow::PropWrite write |
|
||||
write.getRhs() = this and
|
||||
write.getPropertyName() = contentType
|
||||
)
|
||||
}
|
||||
|
||||
override predicate defines(string headerName, string headerValue) {
|
||||
headerName = "content-type" and headerValue = contentType
|
||||
}
|
||||
|
||||
override Http::RouteHandler getRouteHandler() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A header produced by a route handler with no explicit declaration of a Content-Type.
|
||||
*/
|
||||
private class ContentTypeRouteHandlerHeader extends Http::ImplicitHeaderDefinition,
|
||||
DataFlow::FunctionNode {
|
||||
ContentTypeRouteHandlerHeader() { this instanceof RouteHandler }
|
||||
|
||||
override predicate defines(string headerName, string headerValue) {
|
||||
headerName = "content-type" and headerValue = "application/json"
|
||||
}
|
||||
|
||||
override Http::RouteHandler getRouteHandler() { result = this }
|
||||
}
|
||||
|
||||
/** A Restify router */
|
||||
private class RouterRange extends Routing::Router::Range {
|
||||
ServerDefinition def;
|
||||
|
||||
RouterRange() { this = def }
|
||||
|
||||
override DataFlow::SourceNode getAReference() { result = def.ref() }
|
||||
}
|
||||
|
||||
private class RoutingTreeSetup extends Routing::RouteSetup::MethodCall {
|
||||
RoutingTreeSetup() { this instanceof RouteSetup }
|
||||
|
||||
override string getRelativePath() {
|
||||
not this.getMethodName() = ["use", "pre", "param", "on"] and // do not treat parameter name as a path
|
||||
result = this.getArgument(0).getStringValue()
|
||||
}
|
||||
|
||||
override Http::RequestMethodName getHttpMethod() { result.toLowerCase() = this.getMethodName() }
|
||||
}
|
||||
}
|
||||
|
||||
440
javascript/ql/lib/semmle/javascript/frameworks/Spife.qll
Normal file
440
javascript/ql/lib/semmle/javascript/frameworks/Spife.qll
Normal file
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* Provides classes for working with [Spife](https://github.com/npm/spife) applications.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.frameworks.HTTP
|
||||
|
||||
/**
|
||||
* Provides classes for working with [Spife](https://github.com/npm/spife) applications.
|
||||
*/
|
||||
module Spife {
|
||||
/**
|
||||
* An API graph entry point ensuring all tagged template exprs are part of the API graph
|
||||
*/
|
||||
private class TaggedTemplateEntryPoint extends API::EntryPoint {
|
||||
TaggedTemplateEntryPoint() { this = "TaggedTemplateEntryPoint" }
|
||||
|
||||
override DataFlow::SourceNode getASource() { result.asExpr() instanceof TaggedTemplateExpr }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a Spife method that sets up a route.
|
||||
*/
|
||||
private class RouteSetup extends API::CallNode, Http::Servers::StandardRouteSetup {
|
||||
TaggedTemplateExpr template;
|
||||
|
||||
RouteSetup() {
|
||||
exists(CallExpr templateCall |
|
||||
this.getCalleeNode().asExpr() = template and
|
||||
API::moduleImport(["@npm/spife/routing", "spife/routing"])
|
||||
.asSource()
|
||||
.flowsToExpr(template.getTag()) and
|
||||
templateCall.getAChild() = template
|
||||
)
|
||||
}
|
||||
|
||||
private string getRoutePattern() {
|
||||
// Concatenate the constant parts of the expression
|
||||
result =
|
||||
concat(Expr e, int i |
|
||||
e = template.getTemplate().getElement(i) and exists(e.getStringValue())
|
||||
|
|
||||
e.getStringValue() order by i
|
||||
)
|
||||
}
|
||||
|
||||
private string getARouteLine() {
|
||||
result = this.getRoutePattern().splitAt("\n").regexpReplaceAll(" +", " ").trim()
|
||||
}
|
||||
|
||||
private predicate hasLine(string method, string path, string handlerName) {
|
||||
exists(string line | line = this.getARouteLine() |
|
||||
line.splitAt(" ", 0) = method and
|
||||
line.splitAt(" ", 1) = path and
|
||||
line.splitAt(" ", 2) = handlerName
|
||||
)
|
||||
}
|
||||
|
||||
API::Node getHandlerByName(string name) { result = this.getParameter(0).getMember(name) }
|
||||
|
||||
API::Node getHandlerByRoute(string method, string path) {
|
||||
exists(string handlerName |
|
||||
this.hasLine(method, path, handlerName) and
|
||||
result = this.getHandlerByName(handlerName)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getARouteHandler() {
|
||||
result = this.getHandlerByRoute(_, _).getAValueReachingSink().(DataFlow::FunctionNode)
|
||||
or
|
||||
exists(DataFlow::MethodCallNode validation |
|
||||
validation = this.getHandlerByRoute(_, _).getAValueReachingSink() and
|
||||
result = validation.getArgument(1).getAFunctionValue()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getServer() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spife route handler.
|
||||
*/
|
||||
abstract class RouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::FunctionNode {
|
||||
/**
|
||||
* 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 context object.
|
||||
*/
|
||||
DataFlow::ParameterNode getContextParameter() { result = this.getParameter(1) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A standard Spife route handler.
|
||||
*/
|
||||
private class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
|
||||
StandardRouteHandler() { any(RouteSetup setup).getARouteHandler() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that looks like a Spife route handler.
|
||||
*
|
||||
* For example, this could be the function `function(req, res, next){...}`.
|
||||
*/
|
||||
class RouteHandlerCandidate extends Http::RouteHandlerCandidate {
|
||||
RouteHandlerCandidate() {
|
||||
// heuristic: parameter names match the Restify documentation
|
||||
astNode.getNumParameter() = 2 and
|
||||
astNode.getParameter(0).getName() = ["request", "req"] and
|
||||
astNode.getParameter(1).getName() = ["context", "ctx"]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spife 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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spife context source, that is, the context the parameter of a
|
||||
* route handler.
|
||||
*/
|
||||
private class ContextSource extends Http::Servers::RequestSource {
|
||||
RouteHandler rh;
|
||||
|
||||
ContextSource() { this = rh.getContextParameter() }
|
||||
|
||||
/**
|
||||
* Gets the route handler that handles this request.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a user-controlled Spife request input.
|
||||
*/
|
||||
private class RequestInputAccess extends Http::RequestInputAccess {
|
||||
RouteHandler rh;
|
||||
string kind;
|
||||
|
||||
RequestInputAccess() {
|
||||
this = rh.getARequestSource().ref().getAPropertyRead("body") and
|
||||
kind = "body"
|
||||
or
|
||||
this = rh.getARequestSource().ref().getAPropertyRead("query").getAPropertyRead() and
|
||||
kind = "parameter"
|
||||
or
|
||||
this = rh.getARequestSource().ref().getAPropertyRead("raw") and
|
||||
kind = "raw"
|
||||
or
|
||||
this = rh.getARequestSource().ref().getAPropertyRead(["url", "urlObject"]) and
|
||||
kind = "url"
|
||||
or
|
||||
this = rh.getARequestSource().ref().getAMethodCall() and
|
||||
this.(DataFlow::MethodCallNode).getMethodName() = ["cookie", "cookies"] and
|
||||
kind = "cookie"
|
||||
or
|
||||
exists(DataFlow::PropRead validated, DataFlow::MethodCallNode get |
|
||||
rh.getARequestSource().ref().getAPropertyRead() = validated and
|
||||
validated.getPropertyName().matches("validated%") and
|
||||
get.getReceiver() = validated and
|
||||
this = get and
|
||||
kind = "body"
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = kind }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a user-controlled Spife context input.
|
||||
*/
|
||||
private class ContextInputAccess extends Http::RequestInputAccess {
|
||||
ContextSource request;
|
||||
string kind;
|
||||
|
||||
ContextInputAccess() {
|
||||
request.ref().flowsTo(this.(DataFlow::MethodCallNode).getReceiver()) and
|
||||
this.(DataFlow::MethodCallNode).getMethodName() = "get" and
|
||||
kind = "path"
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = request.getRouteHandler() }
|
||||
|
||||
override string getKind() { result = kind }
|
||||
}
|
||||
|
||||
/**
|
||||
* An access to a header on a Spife request.
|
||||
*/
|
||||
private class RequestHeaderAccess extends Http::RequestHeaderAccess {
|
||||
RouteHandler rh;
|
||||
|
||||
RequestHeaderAccess() {
|
||||
this =
|
||||
rh.getARequestSource().ref().getAPropertyRead(["headers", "rawHeaders"]).getAPropertyRead()
|
||||
}
|
||||
|
||||
override string getAHeaderName() {
|
||||
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
|
||||
override string getKind() { result = "header" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Spife response source, that is, the response variable used by a
|
||||
* route handler.
|
||||
*/
|
||||
private class ReplySource extends Http::Servers::ResponseSource {
|
||||
ReplySource() {
|
||||
// const reply = require("@npm/spife/reply")
|
||||
// reply(resp)
|
||||
// reply.header(resp, 'foo', 'bar')
|
||||
this = API::moduleImport(["@npm/spife/reply", "spife/reply"]).getACall() or
|
||||
this = API::moduleImport(["@npm/spife/reply", "spife/reply"]).getAMember().getACall()
|
||||
}
|
||||
|
||||
private DataFlow::SourceNode reachesHandlerReturn(
|
||||
DataFlow::CallNode headerCall, DataFlow::TypeTracker t
|
||||
) {
|
||||
result = headerCall and
|
||||
t.start()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = this.reachesHandlerReturn(headerCall, t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route handler that provides this response.
|
||||
*/
|
||||
override RouteHandler getRouteHandler() {
|
||||
exists(RouteHandler handler |
|
||||
handler.(DataFlow::FunctionNode).getAReturn().getALocalSource() =
|
||||
this.reachesHandlerReturn(this, DataFlow::TypeTracker::end()) and
|
||||
result = handler
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An HTTP header defined in a Spife response.
|
||||
*/
|
||||
private class HeaderDefinition extends Http::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
|
||||
ReplySource reply;
|
||||
|
||||
HeaderDefinition() {
|
||||
// reply.header(RESPONSE, 'Cache-Control', 'no-cache')
|
||||
reply.ref().(DataFlow::MethodCallNode).getMethodName() = "header" and
|
||||
reply.ref().(DataFlow::MethodCallNode).getNumArgument() = 3 and
|
||||
this = reply
|
||||
}
|
||||
|
||||
override predicate definesHeaderValue(string headerName, DataFlow::Node headerValue) {
|
||||
// reply.header(RESPONSE, 'Cache-Control', 'no-cache')
|
||||
headerName = this.getNameNode().getStringValue() and
|
||||
headerValue = this.getArgument(2)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameNode() { result = this.getArgument(1) }
|
||||
|
||||
override RouteHandler getRouteHandler() { result = reply.getRouteHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation that sets any number of headers of the HTTP response.
|
||||
*/
|
||||
private class MultipleHeaderDefinitions extends Http::ExplicitHeaderDefinition, DataFlow::CallNode {
|
||||
ReplySource reply;
|
||||
|
||||
MultipleHeaderDefinitions() {
|
||||
// reply.header(RESPONSE, {'Cache-Control': 'no-cache'})
|
||||
// reply(RESPONSE, {'Cache-Control': 'no-cache'})
|
||||
reply.ref().(DataFlow::CallNode).getCalleeName() = ["header", "reply"] and
|
||||
reply.ref().(DataFlow::CallNode).getAnArgument().getALocalSource() instanceof
|
||||
DataFlow::ObjectLiteralNode and
|
||||
this = reply
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the multiple headers object that is to be set.
|
||||
*/
|
||||
private DataFlow::SourceNode getAHeaderSource() {
|
||||
exists(int i |
|
||||
this.getArgument(i).getALocalSource() instanceof DataFlow::ObjectLiteralNode and
|
||||
result.flowsTo(this.getArgument(i))
|
||||
)
|
||||
}
|
||||
|
||||
override predicate definesHeaderValue(string headerName, DataFlow::Node headerValue) {
|
||||
exists(string header |
|
||||
this.getAHeaderSource().hasPropertyWrite(header, headerValue) and
|
||||
headerName = header.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameNode() {
|
||||
exists(DataFlow::PropWrite write | this.getAHeaderSource().getAPropertyWrite() = write |
|
||||
result = write.getPropertyNameExpr().flow()
|
||||
)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = reply.getRouteHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A header produced by a route handler with no explicit declaration of a Content-Type.
|
||||
*/
|
||||
private class ContentTypeRouteHandlerHeader extends Http::ImplicitHeaderDefinition,
|
||||
DataFlow::FunctionNode {
|
||||
ContentTypeRouteHandlerHeader() { this instanceof RouteHandler }
|
||||
|
||||
override predicate defines(string headerName, string headerValue) {
|
||||
headerName = "content-type" and headerValue = "application/json"
|
||||
}
|
||||
|
||||
override Http::RouteHandler getRouteHandler() { result = this }
|
||||
}
|
||||
|
||||
/**
|
||||
* An HTTP cookie defined in a Spife HTTP response.
|
||||
*/
|
||||
private class CookieDefinition extends Http::CookieDefinition, DataFlow::MethodCallNode {
|
||||
ReplySource reply;
|
||||
|
||||
CookieDefinition() {
|
||||
// reply.cookie(RESPONSE, 'TEST', 'FOO', {"maxAge": 1000, "httpOnly": true, "secure": true})
|
||||
this = reply.ref().(DataFlow::MethodCallNode) and
|
||||
this.getMethodName() = "cookie"
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArgument() { result = this.getArgument(1) }
|
||||
|
||||
override DataFlow::Node getValueArgument() { result = this.getArgument(2) }
|
||||
|
||||
override RouteHandler getRouteHandler() { result = reply.getRouteHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A response argument passed to the `reply` method.
|
||||
*/
|
||||
private class ReplyArgument extends Http::ResponseSendArgument, DataFlow::Node {
|
||||
RouteHandler rh;
|
||||
|
||||
ReplyArgument() {
|
||||
exists(ReplySource reply |
|
||||
reply.ref().(DataFlow::CallNode).getCalleeName() =
|
||||
["reply", "cookie", "link", "header", "headers", "raw", "status", "toStream", "vary"] and
|
||||
this = reply.ref().(DataFlow::CallNode).getArgument(0) and
|
||||
rh = reply.getRouteHandler()
|
||||
)
|
||||
or
|
||||
this = rh.(DataFlow::FunctionNode).getAReturn()
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = rh }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression passed to the `template` method of the reply object
|
||||
* as the value of a template variable.
|
||||
*/
|
||||
private class TemplateInput extends Http::ResponseBody {
|
||||
TemplateObjectInput obj;
|
||||
|
||||
TemplateInput() {
|
||||
obj.getALocalSource().(DataFlow::ObjectLiteralNode).hasPropertyWrite(_, this)
|
||||
}
|
||||
|
||||
override RouteHandler getRouteHandler() { result = obj.getRouteHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An object passed to the `template` method of the reply object.
|
||||
*/
|
||||
private class TemplateObjectInput extends DataFlow::Node {
|
||||
ReplySource reply;
|
||||
|
||||
TemplateObjectInput() {
|
||||
reply.ref().(DataFlow::MethodCallNode).getMethodName() = "template" and
|
||||
this = reply.ref().(DataFlow::MethodCallNode).getArgument(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the route handler that uses this object.
|
||||
*/
|
||||
RouteHandler getRouteHandler() { result = reply.getRouteHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* An invocation of the `redirect` method of an HTTP response object.
|
||||
*/
|
||||
private class RedirectInvocation extends Http::RedirectInvocation, DataFlow::MethodCallNode {
|
||||
ReplySource reply;
|
||||
|
||||
RedirectInvocation() {
|
||||
this = reply.ref().(DataFlow::MethodCallNode) and
|
||||
this.getMethodName() = "redirect"
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlArgument() { result = this.getAnArgument() }
|
||||
|
||||
override RouteHandler getRouteHandler() { result = reply.getRouteHandler() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to `reply.template('template', { ... })`, seen as a template instantiation.
|
||||
*/
|
||||
private class TemplateCall extends Templating::TemplateInstantiation::Range, DataFlow::CallNode {
|
||||
TemplateCall() {
|
||||
exists(ReplySource reply |
|
||||
reply.ref().(DataFlow::MethodCallNode).getMethodName() = "template" and
|
||||
this = reply.ref()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::SourceNode getOutput() { result = this }
|
||||
|
||||
override DataFlow::Node getTemplateFileNode() { result = this.getArgument(0) }
|
||||
|
||||
override DataFlow::Node getTemplateParamsNode() { result = this.getArgument(1) }
|
||||
}
|
||||
}
|
||||
@@ -45,3 +45,19 @@ private class PromotedConnectCandidate extends Connect::RouteHandler,
|
||||
result = ConnectExpressShared::getRouteHandlerParameter(this, kind)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add `Restify::RouteHandlerCandidate` to the extent of `Restify::RouteHandler`.
|
||||
*/
|
||||
private class PromotedRestifyCandidate extends Restify::RouteHandler,
|
||||
Http::Servers::StandardRouteHandler {
|
||||
PromotedRestifyCandidate() { this instanceof Restify::RouteHandlerCandidate }
|
||||
}
|
||||
|
||||
/**
|
||||
* Add `Spife::RouteHandlerCandidate` to the extent of `Spife::RouteHandler`.
|
||||
*/
|
||||
private class PromotedSpifeCandidate extends Spife::RouteHandler,
|
||||
Http::Servers::StandardRouteHandler {
|
||||
PromotedSpifeCandidate() { this instanceof Spife::RouteHandlerCandidate }
|
||||
}
|
||||
|
||||
@@ -35,4 +35,3 @@
|
||||
| restify.js:9:5:9:17 | req.trailer() | header |
|
||||
| restify.js:10:5:10:16 | req.header() | header |
|
||||
| restify.js:11:5:11:11 | req.url | url |
|
||||
| restify.js:12:5:12:15 | req.cookies | cookie |
|
||||
|
||||
@@ -299,11 +299,13 @@ test_RemoteFlowSources
|
||||
| src/http.js:30:28:30:32 | chunk |
|
||||
| src/http.js:40:23:40:30 | authInfo |
|
||||
| src/http.js:45:23:45:27 | error |
|
||||
| src/http.js:63:17:63:33 | req.query.myParam |
|
||||
| src/http.js:73:18:73:22 | chunk |
|
||||
| src/http.js:82:18:82:22 | chunk |
|
||||
| src/https.js:6:26:6:32 | req.url |
|
||||
| src/https.js:8:3:8:20 | req.headers.cookie |
|
||||
| src/https.js:9:3:9:17 | req.headers.foo |
|
||||
| src/indirect2.js:10:12:10:25 | req.params.key |
|
||||
| src/indirect.js:17:28:17:34 | req.url |
|
||||
test_RouteHandler
|
||||
| createServer.js:2:20:2:41 | functio ... res) {} | createServer.js:2:1:2:42 | https.c ... es) {}) |
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
var restify = require('restify');
|
||||
const restifyPlugins = require('restify-plugins');
|
||||
var clients = require('restify-clients');
|
||||
|
||||
const opts = {
|
||||
formatters: {
|
||||
'text/plain': function(req, res, body) { // test: formatter
|
||||
if (body instanceof Error) {
|
||||
return '<html><body>' + body.message + '</body></html>'; // test: stackTraceExposureSink
|
||||
} else {
|
||||
return '<html><body>' + body + req.params.name + '</body></html>'; // test: source, stackTraceExposureSink, !xssSink, !xss
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const _server = restify.createServer(opts)
|
||||
|
||||
const server = restify.createServer({
|
||||
formatters: {
|
||||
'text/html': function(req, res, body) { // test: formatter
|
||||
if (body instanceof Error) {
|
||||
return '<html><body>' + body.message + '</body></html>'; // test: stackTraceExposureSink, xssSink
|
||||
} else {
|
||||
return '<html><body>' + body + req.params.name + '</body></html>'; // test: source, stackTraceExposureSink, xssSink, xss
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The pre handler chain is executed before routing. That means these handlers will execute for an incoming request even if it’s for a route that you did not register.
|
||||
server.pre(restify.plugins.pre.dedupeSlashes());
|
||||
server.pre(function(req, res, next) { // test: handler
|
||||
return next();
|
||||
});
|
||||
|
||||
// The use handler chains is executed after a route has been chosen to service the request.
|
||||
server.use(restifyPlugins.jsonBodyParser({ mapParams: true })); // TODO: prototype pollution?
|
||||
server.use(restifyPlugins.acceptParser(server.acceptable));
|
||||
server.use(restifyPlugins.queryParser({ mapParams: true })); // TODO: prototype pollution?
|
||||
server.use(restifyPlugins.fullResponse());
|
||||
server.use(function(req, res, next) { // test: handler
|
||||
return next();
|
||||
});
|
||||
function filter(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
function filter1(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
function filter2(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
function filter3(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
function filter4(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
function filter5(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
function filter6(req, res, next) { // test: handler
|
||||
return next();
|
||||
}
|
||||
const handlers = [filter5, filter6];
|
||||
server.use(filter); // test: setup
|
||||
server.use(filter1, filter2); // test: setup
|
||||
server.use([filter3, filter4]); // test: setup
|
||||
server.use(handlers); // setup
|
||||
|
||||
function respond(req, res, next) { // test: handler
|
||||
res.send('hello ' + req.params.name); // test: source, stackTraceExposureSink
|
||||
res.send('hello ' + req.params["name"]); // test: source, stackTraceExposureSink
|
||||
res.send('hello ' + req.query.name); // test: source, stackTraceExposureSink
|
||||
res.send('hello ' + req.params[0]); // test: source, stackTraceExposureSink
|
||||
|
||||
res.redirect({
|
||||
hostname: req.params.name, // test: source, redirectSink
|
||||
pathname: '/bar',
|
||||
port: 80,
|
||||
secure: true,
|
||||
permanent: true,
|
||||
query: {
|
||||
a: 1
|
||||
}
|
||||
}, next);
|
||||
res.redirect(301, req.params.name, next); // test: source, redirectSink
|
||||
res.redirect(req.params.name, next); // test: source, redirectSink
|
||||
next();
|
||||
}
|
||||
server.get('/hello/:name', respond); // test: setup
|
||||
server.head('/hello/:name', respond); // test: setup
|
||||
server.get('/', function(req, res, next) { // test: setup, handler
|
||||
res.send('home')
|
||||
return next();
|
||||
});
|
||||
|
||||
server.get('/foo', // test: setup
|
||||
function(req, res, next) { // test: handler
|
||||
req.someData = req.params.name; // test: source
|
||||
return next();
|
||||
},
|
||||
function(req, res, next) { // test: handler
|
||||
res.header("Content-Type", "text/html");
|
||||
res.send(req.someData); // test: stackTraceExposureSink, xssSink, xss
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
server.get('/foo2', // test: setup
|
||||
[function(req, res, next) { // test: handler
|
||||
req.someData = 'foo';
|
||||
return next();
|
||||
},
|
||||
function(req, res, next) { // test: handler
|
||||
res.send(req.someData); // test: stackTraceExposureSink
|
||||
return next();
|
||||
}]
|
||||
);
|
||||
|
||||
function xss(req, res, next) { // test: handler
|
||||
res.header("Content-Type", "text/html");
|
||||
res.send('hello ' + req.query.name); // test: source, stackTraceExposureSink, xssSink, xss
|
||||
next();
|
||||
}
|
||||
server["get"]('/xss', xss); // test: setup
|
||||
|
||||
function xss2(req, res, next) { // test: handler
|
||||
var body = req.params.name; // test: source
|
||||
res.writeHead(200, {
|
||||
'Content-Length': Buffer.byteLength(body),
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
res.write(body); // test: stackTraceExposureSink, xssSink, xss
|
||||
res.end();
|
||||
next();
|
||||
}
|
||||
["get", "head"].forEach(method => {
|
||||
server[method]('/xss2', xss2);
|
||||
});
|
||||
|
||||
function xss3(req, res, next) { // test: handler
|
||||
res.header("Content-Type", "text/html");
|
||||
res.send('hello ' + req.header("foo")); // test: source, stackTraceExposureSink, xssSink, !xss
|
||||
next();
|
||||
}
|
||||
server["get"]('/xss3', xss3); // test: setup
|
||||
|
||||
|
||||
function sendV2(req, res, next) { // test: candidateHandler
|
||||
res.set({
|
||||
"Content-Type": "text/html",
|
||||
"access-control-allow-origin": "*", // test: corsMiconfigurationSink
|
||||
"access-control-allow-headers": "Content-Type, Authorization, Content-Length, X-Requested-With",
|
||||
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"access-control-allow-credentials": "true"
|
||||
})
|
||||
res.send('hello ' + req.params.name); // test: source, stackTraceExposureSink, xssSink, xss
|
||||
clients.createJsonClient({
|
||||
url: req.params.uri, // test: source, ssrfSink
|
||||
});
|
||||
clients.createJsonClient(req.params.uri); // test: source, ssrfSink
|
||||
|
||||
next();
|
||||
}
|
||||
server.get('/hello2/:name', restify.plugins.conditionalHandler([ // test: setup
|
||||
{ version: ['2.0.0', '2.1.0', '2.2.0'], handler: sendV2 }
|
||||
]));
|
||||
|
||||
server.get('/version/test', restify.plugins.conditionalHandler([ //test: setup
|
||||
{
|
||||
version: ['2.0.0', '2.1.0', '2.2.0'],
|
||||
handler: function(req, res, next) { // test: candidateHandler
|
||||
res.send(200, {
|
||||
requestedVersion: req.version(),
|
||||
matchedVersion: req.matchedVersion()
|
||||
});
|
||||
return next();
|
||||
}
|
||||
}
|
||||
]));
|
||||
|
||||
server.on('InternalServer', function(req, res, err, callback) { // test: setup, handler
|
||||
return callback();
|
||||
});
|
||||
|
||||
server.on('restifyError', function(req, res, err, callback) { // test: setup, handler
|
||||
return callback();
|
||||
});
|
||||
server.on('after', function(req, res, route, error) { // test: setup, handler
|
||||
});
|
||||
server.on('pre', function(req, res) { // test: setup, handler
|
||||
});
|
||||
server.on('routed', function(req, res, route) { // test: setup, handler
|
||||
res.header("Content-Type", "text/plain")
|
||||
res.send(req.params.foo) // test: source, !xssSink, !xss
|
||||
});
|
||||
server.on('uncaughtException', function(req, res, route, err) { // test: setup, handler
|
||||
res.header("Content-Type", "text/html")
|
||||
res.send(req.params.foo) // test: source, xssSink, xss
|
||||
});
|
||||
|
||||
|
||||
server.listen(8080, function() {
|
||||
console.log('%s listening at %s', server.name, server.url);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
passingPositiveTests
|
||||
| PASSED | candidateHandler | src/index.js:150:35:150:59 | // test ... Handler |
|
||||
| PASSED | candidateHandler | src/index.js:173:42:173:66 | // test ... Handler |
|
||||
| PASSED | corsMiconfigurationSink | src/index.js:153:41:153:72 | // test ... ionSink |
|
||||
| PASSED | handler | src/index.js:32:39:32:54 | // test: handler |
|
||||
| PASSED | handler | src/index.js:41:39:41:54 | // test: handler |
|
||||
| PASSED | handler | src/index.js:44:35:44:50 | // test: handler |
|
||||
| PASSED | handler | src/index.js:47:36:47:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:50:36:50:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:53:36:53:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:56:36:56:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:59:36:59:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:62:36:62:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:71:36:71:51 | // test: handler |
|
||||
| PASSED | handler | src/index.js:93:44:93:66 | // test ... handler |
|
||||
| PASSED | handler | src/index.js:99:30:99:45 | // test: handler |
|
||||
| PASSED | handler | src/index.js:103:30:103:45 | // test: handler |
|
||||
| PASSED | handler | src/index.js:111:31:111:46 | // test: handler |
|
||||
| PASSED | handler | src/index.js:115:30:115:45 | // test: handler |
|
||||
| PASSED | handler | src/index.js:121:32:121:47 | // test: handler |
|
||||
| PASSED | handler | src/index.js:128:33:128:48 | // test: handler |
|
||||
| PASSED | handler | src/index.js:142:33:142:48 | // test: handler |
|
||||
| PASSED | handler | src/index.js:183:65:183:87 | // test ... handler |
|
||||
| PASSED | handler | src/index.js:187:63:187:85 | // test ... handler |
|
||||
| PASSED | handler | src/index.js:190:55:190:77 | // test ... handler |
|
||||
| PASSED | handler | src/index.js:192:39:192:61 | // test ... handler |
|
||||
| PASSED | handler | src/index.js:194:49:194:71 | // test ... handler |
|
||||
| PASSED | handler | src/index.js:198:65:198:87 | // test ... handler |
|
||||
| PASSED | redirectSink | src/index.js:78:32:78:60 | // test ... ectSink |
|
||||
| PASSED | redirectSink | src/index.js:87:45:87:73 | // test ... ectSink |
|
||||
| PASSED | redirectSink | src/index.js:88:40:88:68 | // test ... ectSink |
|
||||
| PASSED | setup | src/index.js:66:21:66:34 | // test: setup |
|
||||
| PASSED | setup | src/index.js:67:31:67:44 | // test: setup |
|
||||
| PASSED | setup | src/index.js:68:33:68:46 | // test: setup |
|
||||
| PASSED | setup | src/index.js:91:38:91:51 | // test: setup |
|
||||
| PASSED | setup | src/index.js:92:39:92:52 | // test: setup |
|
||||
| PASSED | setup | src/index.js:93:44:93:66 | // test ... handler |
|
||||
| PASSED | setup | src/index.js:98:20:98:33 | // test: setup |
|
||||
| PASSED | setup | src/index.js:110:21:110:34 | // test: setup |
|
||||
| PASSED | setup | src/index.js:126:29:126:42 | // test: setup |
|
||||
| PASSED | setup | src/index.js:147:31:147:44 | // test: setup |
|
||||
| PASSED | setup | src/index.js:166:66:166:79 | // test: setup |
|
||||
| PASSED | setup | src/index.js:170:66:170:78 | //test: setup |
|
||||
| PASSED | setup | src/index.js:183:65:183:87 | // test ... handler |
|
||||
| PASSED | setup | src/index.js:187:63:187:85 | // test ... handler |
|
||||
| PASSED | setup | src/index.js:190:55:190:77 | // test ... handler |
|
||||
| PASSED | setup | src/index.js:192:39:192:61 | // test ... handler |
|
||||
| PASSED | setup | src/index.js:194:49:194:71 | // test ... handler |
|
||||
| PASSED | setup | src/index.js:198:65:198:87 | // test ... handler |
|
||||
| PASSED | source | src/index.js:11:76:11:130 | // test ... k, !xss |
|
||||
| PASSED | source | src/index.js:24:76:24:128 | // test ... nk, xss |
|
||||
| PASSED | source | src/index.js:72:41:72:80 | // test ... reSink |
|
||||
| PASSED | source | src/index.js:73:44:73:82 | // test ... ureSink |
|
||||
| PASSED | source | src/index.js:74:40:74:78 | // test ... ureSink |
|
||||
| PASSED | source | src/index.js:75:39:75:77 | // test ... ureSink |
|
||||
| PASSED | source | src/index.js:78:32:78:60 | // test ... ectSink |
|
||||
| PASSED | source | src/index.js:87:45:87:73 | // test ... ectSink |
|
||||
| PASSED | source | src/index.js:88:40:88:68 | // test ... ectSink |
|
||||
| PASSED | source | src/index.js:100:37:100:51 | // test: source |
|
||||
| PASSED | source | src/index.js:123:40:123:92 | // test ... nk, xss |
|
||||
| PASSED | source | src/index.js:129:31:129:45 | // test: source |
|
||||
| PASSED | source | src/index.js:144:43:144:96 | // test ... k, !xss |
|
||||
| PASSED | source | src/index.js:158:41:158:93 | // test ... nk, xss |
|
||||
| PASSED | source | src/index.js:160:26:160:50 | // test ... srfSink |
|
||||
| PASSED | source | src/index.js:162:45:162:69 | // test ... srfSink |
|
||||
| PASSED | source | src/index.js:196:28:196:58 | // test ... k, !xss |
|
||||
| PASSED | source | src/index.js:200:28:200:56 | // test ... nk, xss |
|
||||
| PASSED | ssrfSink | src/index.js:160:26:160:50 | // test ... srfSink |
|
||||
| PASSED | ssrfSink | src/index.js:162:45:162:69 | // test ... srfSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:9:66:9:96 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:11:76:11:130 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:22:66:22:105 | // test ... xssSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:24:76:24:128 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:72:41:72:80 | // test ... reSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:73:44:73:82 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:74:40:74:78 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:75:39:75:77 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:105:29:105:73 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:116:29:116:59 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:123:40:123:92 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:134:20:134:64 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:144:43:144:96 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | src/index.js:158:41:158:93 | // test ... nk, xss |
|
||||
| PASSED | xss | src/index.js:24:76:24:128 | // test ... nk, xss |
|
||||
| PASSED | xss | src/index.js:105:29:105:73 | // test ... nk, xss |
|
||||
| PASSED | xss | src/index.js:123:40:123:92 | // test ... nk, xss |
|
||||
| PASSED | xss | src/index.js:134:20:134:64 | // test ... nk, xss |
|
||||
| PASSED | xss | src/index.js:158:41:158:93 | // test ... nk, xss |
|
||||
| PASSED | xss | src/index.js:200:28:200:56 | // test ... nk, xss |
|
||||
| PASSED | xssSink | src/index.js:22:66:22:105 | // test ... xssSink |
|
||||
| PASSED | xssSink | src/index.js:24:76:24:128 | // test ... nk, xss |
|
||||
| PASSED | xssSink | src/index.js:105:29:105:73 | // test ... nk, xss |
|
||||
| PASSED | xssSink | src/index.js:123:40:123:92 | // test ... nk, xss |
|
||||
| PASSED | xssSink | src/index.js:134:20:134:64 | // test ... nk, xss |
|
||||
| PASSED | xssSink | src/index.js:144:43:144:96 | // test ... k, !xss |
|
||||
| PASSED | xssSink | src/index.js:158:41:158:93 | // test ... nk, xss |
|
||||
| PASSED | xssSink | src/index.js:200:28:200:56 | // test ... nk, xss |
|
||||
failingPositiveTests
|
||||
passingNegativeTests
|
||||
| PASSED | !xss | src/index.js:11:76:11:130 | // test ... k, !xss |
|
||||
| PASSED | !xss | src/index.js:144:43:144:96 | // test ... k, !xss |
|
||||
| PASSED | !xss | src/index.js:196:28:196:58 | // test ... k, !xss |
|
||||
| PASSED | !xssSink | src/index.js:11:76:11:130 | // test ... k, !xss |
|
||||
| PASSED | !xssSink | src/index.js:196:28:196:58 | // test ... k, !xss |
|
||||
failingNegativeTests
|
||||
194
javascript/ql/test/library-tests/frameworks/Restify2/tests.ql
Normal file
194
javascript/ql/test/library-tests/frameworks/Restify2/tests.ql
Normal file
@@ -0,0 +1,194 @@
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CleartextStorageCustomizations
|
||||
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations
|
||||
import semmle.javascript.security.dataflow.StackTraceExposureCustomizations
|
||||
import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
|
||||
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
|
||||
import semmle.javascript.security.dataflow.ReflectedXssCustomizations
|
||||
import semmle.javascript.security.dataflow.ReflectedXssQuery as XssConfig
|
||||
import semmle.javascript.heuristics.AdditionalRouteHandlers
|
||||
|
||||
class InlineTest extends LineComment {
|
||||
string tests;
|
||||
|
||||
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
|
||||
|
||||
string getPositiveTest() {
|
||||
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
|
||||
}
|
||||
|
||||
string getNegativeTest() { result = tests.trim().splitAt(",").trim() and result.matches("!%") }
|
||||
|
||||
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
|
||||
|
||||
predicate hasNegativeTest(string test) { test = this.getNegativeTest() }
|
||||
|
||||
predicate inNode(DataFlow::Node n) {
|
||||
this.getLocation().getFile() = n.getFile() and
|
||||
this.getLocation().getStartLine() = n.getStartLine()
|
||||
}
|
||||
}
|
||||
|
||||
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
(
|
||||
expectation = "source" and
|
||||
exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "setup" and
|
||||
exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "handler" and
|
||||
exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "candidateHandler" and
|
||||
exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "xssSink" and
|
||||
exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "xss" and
|
||||
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "cleartextStorageSink" and
|
||||
exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "corsMiconfigurationSink" and
|
||||
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "stackTraceExposureSink" and
|
||||
exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "redirectSink" and
|
||||
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "ssrfSink" and
|
||||
exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
(
|
||||
expectation = "source" and
|
||||
not exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "setup" and
|
||||
not exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "handler" and
|
||||
not exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "candidateHandler" and
|
||||
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "xssSink" and
|
||||
not exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "xss" and
|
||||
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "cleartextStorageSink" and
|
||||
not exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "corsMiconfigurationSink" and
|
||||
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "stackTraceExposureSink" and
|
||||
not exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "redirectSink" and
|
||||
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "ssrfSink" and
|
||||
not exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate passingNegativeTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasNegativeTest(expectation) and
|
||||
(
|
||||
expectation = "!source" and
|
||||
not exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "!setup" and
|
||||
not exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "!handler" and
|
||||
not exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "!candidateHandler" and
|
||||
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xssSink" and
|
||||
not exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xss" and
|
||||
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "!cleartextStorageSink" and
|
||||
not exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!corsMiconfigurationSink" and
|
||||
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!stackTraceExposureSink" and
|
||||
not exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!redirectSink" and
|
||||
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!ssrfSink" and
|
||||
not exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate failingNegativeTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasNegativeTest(expectation) and
|
||||
(
|
||||
expectation = "!source" and
|
||||
exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "!setup" and
|
||||
exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "!handler" and
|
||||
exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "!candidateHandler" and
|
||||
exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xssSink" and
|
||||
exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xss" and
|
||||
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "!cleartextStorageSink" and
|
||||
exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!corsMiconfigurationSink" and
|
||||
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!stackTraceExposureSink" and
|
||||
exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!redirectSink" and
|
||||
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!ssrfSink" and
|
||||
exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
'use strict'
|
||||
|
||||
const routes = require('@npm/spife/routing')
|
||||
|
||||
module.exports = routes`
|
||||
GET / homepage
|
||||
GET /test1 test1
|
||||
GET /test1 test2
|
||||
GET /test4 test4
|
||||
GET /test5 test5
|
||||
GET /test6 test6
|
||||
GET /raw1 raw1
|
||||
GET /raw2 raw2
|
||||
POST /body parseBody
|
||||
GET /redirect/:redirect_url redirect
|
||||
POST /packages/new createPackage
|
||||
`(require('../views'))
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict'
|
||||
|
||||
const { Environment, FileSystemLoader } = require('nunjucks')
|
||||
const Loader = require('@npm/spife/templates/loader')
|
||||
const path = require('path')
|
||||
|
||||
const isDev = !new Set(['prod', 'production', 'stag', 'staging']).has(
|
||||
process.env.NODE_ENV
|
||||
)
|
||||
|
||||
const templateDirs = [path.join(__dirname, '..', 'templates')]
|
||||
const nunjucksEnv = new Environment(new FileSystemLoader(templateDirs))
|
||||
const nunjucksLoader = new Loader({
|
||||
dirs: templateDirs,
|
||||
load (resolved) {
|
||||
const template = nunjucksEnv.getTemplate(resolved.path, true)
|
||||
return context => {
|
||||
return template.render(context)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
DEBUG: process.env.DEBUG,
|
||||
ENABLE_FORM_PARSING: false,
|
||||
METRICS: process.env.METRICS,
|
||||
MIDDLEWARE: [
|
||||
'@npm/spife/middleware/debug',
|
||||
['@npm/spife/middleware/template', [
|
||||
nunjucksLoader
|
||||
], [
|
||||
// template context processors go here
|
||||
]],
|
||||
'@npm/spife/middleware/common',
|
||||
'@npm/spife/middleware/logging',
|
||||
'@npm/spife/middleware/metrics',
|
||||
'@npm/spife/middleware/monitor',
|
||||
'@npm/spife/middleware/hot-reload',
|
||||
['@npm/spife/middleware/csrf', { secureCookie: !isDev }]
|
||||
],
|
||||
NAME: 'nunjucks-example',
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
PORT: 8124,
|
||||
ROUTER: './routes/index.js',
|
||||
HOT: true
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
'use strict'
|
||||
|
||||
const reply = require('@npm/spife/reply')
|
||||
const validate = require('@npm/spife/decorators/validate')
|
||||
const joi = require('@npm/spife/joi')
|
||||
const concat = require('concat-stream')
|
||||
|
||||
const createPackageSchema = joi.object().keys({
|
||||
contents: joi.string().max(200).required(),
|
||||
destination: joi.any().valid([
|
||||
joi.object({
|
||||
name: joi.string().max(200).required(),
|
||||
address: joi.string().max(200).required()
|
||||
}),
|
||||
joi.string().min(1)
|
||||
])
|
||||
})
|
||||
|
||||
module.exports = { homepage, parseBody, raw1, raw2, test1, test2, test3, test4, test5, test6, redirect, createPackage: validate.body(createPackageSchema, createPackage), }
|
||||
|
||||
function sink(obj) { console.log(obj) }
|
||||
|
||||
function createPackage(req, context) { // test: handler
|
||||
const tainted = req.validatedBody.get('destination') // test: source
|
||||
sink(taitned)
|
||||
}
|
||||
|
||||
function homepage(req, context) { // test: handler
|
||||
sink(req.cookie("test")) // test: source
|
||||
sink(req.cookies().test) // test: source
|
||||
sink(req.headers.test) // test: source
|
||||
sink(req.rawHeaders[0]) // test: source
|
||||
sink(req.raw.headers) // test: source
|
||||
sink(req.url) // test: source
|
||||
sink(req.urlObject.pathname) // test: source
|
||||
sink(context.get('package')) // test: source
|
||||
sink(context)
|
||||
return reply.template('home', { target: req.query.name }) // test: source, templateInstantiation, stackTraceExposureSink
|
||||
}
|
||||
|
||||
function raw1(req, context) { // test: handler
|
||||
sink(req.query.name) // test: source
|
||||
return reply(req.query.name, 200, { // test: source, xssSink, stackTraceExposureSink, xss
|
||||
"content-type": "text/html",
|
||||
"access-control-allow-origin": "*", // test: corsMiconfigurationSink
|
||||
"access-control-allow-headers": "Content-Type, Authorization, Content-Length, X-Requested-With",
|
||||
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"access-control-allow-credentials": "true"
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
function redirect(req, context) { // test: handler
|
||||
return reply.redirect(context.get('redirect_url')) // test: redirectSink, source, stackTraceExposureSink
|
||||
}
|
||||
function raw2(req, context) { // test: handler
|
||||
return reply.cookie({ "test": req.query.name }, "test", req.query.name, { "httpOnly": false, "secure": false }) // test: source, cleartextStorageSink, stackTraceExposureSink
|
||||
}
|
||||
|
||||
function test1(req, context) { // test: handler
|
||||
switch (req.accept.type(['json', 'html', 'plain'])) {
|
||||
case 'json':
|
||||
return { "some": req.query.name } // test: source, stackTraceExposureSink
|
||||
case 'html':
|
||||
return reply.header('<p>' + req.query.name + '</p>', 'content-type', 'text/html') // test: source, xssSink, stackTraceExposureSink, xss
|
||||
case 'plain':
|
||||
return reply.header('<p>' + req.query.name + '</p>', { 'content-type': 'text/plain' }) // test: source, stackTraceExposureSink, !xssSink, !xss
|
||||
}
|
||||
return 'well, I guess you just want plaintext.'
|
||||
}
|
||||
|
||||
function test2(req, context) { // test: handler
|
||||
switch (req.accept.type(['json', 'html'])) {
|
||||
case 'json':
|
||||
return { "some": req.query.name } // test: source, stackTraceExposureSink
|
||||
case 'html':
|
||||
return reply.header('<p>' + req.query.name + '</p>', { 'content-type': 'text/plain' }) // test: source, stackTraceExposureSink, !xssSink, !xss
|
||||
}
|
||||
return 'well, I guess you just want plaintext.'
|
||||
}
|
||||
|
||||
function test3(req, context) { // test: candidateHandler
|
||||
return reply('<p>' + req.query.name + '</p>') // test: source, stackTraceExposureSink, !xssSink, !xss
|
||||
}
|
||||
|
||||
function test4(req, context) { // test: handler
|
||||
const body = req.body // test: source
|
||||
const newPackument = body['package-json']
|
||||
const message = `INFO: User invited to package ${newPackument._id} successfully.`
|
||||
return reply(message, 200, { 'npm-notice': message }) // test: stackTraceExposureSink, !xssSink, !xss
|
||||
}
|
||||
|
||||
function test5(req, context) { // test: handler
|
||||
const body = req.body // test: source
|
||||
const newPackument = body['package-json']
|
||||
const message = `INFO: User invited to package ${newPackument._id} successfully.`
|
||||
return reply(message, 200) // test: stackTraceExposureSink, !xssSink, !xss
|
||||
}
|
||||
|
||||
function test6(req, context) { // test: handler
|
||||
const body = req.body // test: source
|
||||
const newPackument = body['package-json']
|
||||
const message = `INFO: User invited to package ${newPackument._id} successfully.`
|
||||
if (message.contains('foo')) {
|
||||
return reply(message, 200, { 'npm-notice': message }) // test: stackTraceExposureSink, !xssSink, !xss
|
||||
} else {
|
||||
return reply(message, 200, { 'npm-notice': message, 'content-type': 'text/html' }) // test: stackTraceExposureSink, xssSink, xss
|
||||
}
|
||||
}
|
||||
|
||||
function parseBody(req, context) {
|
||||
return req.body.then(data => { // test: source, stackTraceExposureSink
|
||||
sink(data.name)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
passingPositiveTests
|
||||
| PASSED | candidateHandler | lib/views/index.js:82:32:82:56 | // test ... Handler |
|
||||
| PASSED | cleartextStorageSink | lib/views/index.js:57:115:57:175 | // test ... ureSink |
|
||||
| PASSED | corsMiconfigurationSink | lib/views/index.js:45:41:45:72 | // test ... ionSink |
|
||||
| PASSED | handler | lib/views/index.js:23:40:23:55 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:28:35:28:50 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:41:31:41:46 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:53:35:53:50 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:56:31:56:46 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:60:32:60:47 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:72:32:72:47 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:86:32:86:47 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:93:32:93:47 | // test: handler |
|
||||
| PASSED | handler | lib/views/index.js:100:32:100:47 | // test: handler |
|
||||
| PASSED | redirectSink | lib/views/index.js:54:54:54:106 | // test ... ureSink |
|
||||
| PASSED | source | lib/views/index.js:24:56:24:70 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:29:28:29:42 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:30:28:30:42 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:31:26:31:40 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:32:27:32:41 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:33:25:33:39 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:34:17:34:31 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:35:32:35:46 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:36:32:36:46 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:38:61:38:122 | // test ... ureSink |
|
||||
| PASSED | source | lib/views/index.js:42:24:42:38 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
|
||||
| PASSED | source | lib/views/index.js:54:54:54:106 | // test ... ureSink |
|
||||
| PASSED | source | lib/views/index.js:57:115:57:175 | // test ... ureSink |
|
||||
| PASSED | source | lib/views/index.js:63:41:63:79 | // test ... ureSink |
|
||||
| PASSED | source | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
|
||||
| PASSED | source | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
|
||||
| PASSED | source | lib/views/index.js:75:41:75:79 | // test ... ureSink |
|
||||
| PASSED | source | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
|
||||
| PASSED | source | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
|
||||
| PASSED | source | lib/views/index.js:87:25:87:39 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:94:25:94:39 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:101:25:101:39 | // test: source |
|
||||
| PASSED | source | lib/views/index.js:112:34:112:72 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:38:61:38:122 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:54:54:54:106 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:57:115:57:175 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:63:41:63:79 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:75:41:75:79 | // test ... ureSink |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:90:57:90:103 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:97:30:97:76 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:105:59:105:105 | // test ... k, !xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:107:88:107:132 | // test ... nk, xss |
|
||||
| PASSED | stackTraceExposureSink | lib/views/index.js:112:34:112:72 | // test ... ureSink |
|
||||
| PASSED | xss | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
|
||||
| PASSED | xss | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
|
||||
| PASSED | xss | lib/views/index.js:107:88:107:132 | // test ... nk, xss |
|
||||
| PASSED | xssSink | lib/views/index.js:43:39:43:91 | // test ... nk, xss |
|
||||
| PASSED | xssSink | lib/views/index.js:65:89:65:141 | // test ... nk, xss |
|
||||
| PASSED | xssSink | lib/views/index.js:107:88:107:132 | // test ... nk, xss |
|
||||
failingPositiveTests
|
||||
passingNegativeTests
|
||||
| PASSED | !xss | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
|
||||
| PASSED | !xss | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
|
||||
| PASSED | !xss | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
|
||||
| PASSED | !xss | lib/views/index.js:97:30:97:76 | // test ... k, !xss |
|
||||
| PASSED | !xssSink | lib/views/index.js:67:94:67:148 | // test ... k, !xss |
|
||||
| PASSED | !xssSink | lib/views/index.js:77:94:77:148 | // test ... k, !xss |
|
||||
| PASSED | !xssSink | lib/views/index.js:83:49:83:103 | // test ... k, !xss |
|
||||
| PASSED | !xssSink | lib/views/index.js:97:30:97:76 | // test ... k, !xss |
|
||||
failingNegativeTests
|
||||
| FAILED | !xss | lib/views/index.js:90:57:90:103 | // test ... k, !xss |
|
||||
| FAILED | !xss | lib/views/index.js:105:59:105:105 | // test ... k, !xss |
|
||||
| FAILED | !xssSink | lib/views/index.js:90:57:90:103 | // test ... k, !xss |
|
||||
| FAILED | !xssSink | lib/views/index.js:105:59:105:105 | // test ... k, !xss |
|
||||
194
javascript/ql/test/library-tests/frameworks/Spife/tests.ql
Normal file
194
javascript/ql/test/library-tests/frameworks/Spife/tests.ql
Normal file
@@ -0,0 +1,194 @@
|
||||
import javascript
|
||||
import semmle.javascript.security.dataflow.CleartextStorageCustomizations
|
||||
import semmle.javascript.security.dataflow.CorsMisconfigurationForCredentialsCustomizations
|
||||
import semmle.javascript.security.dataflow.StackTraceExposureCustomizations
|
||||
import semmle.javascript.security.dataflow.ServerSideUrlRedirectCustomizations
|
||||
import semmle.javascript.security.dataflow.RequestForgeryCustomizations
|
||||
import semmle.javascript.security.dataflow.ReflectedXssCustomizations
|
||||
import semmle.javascript.security.dataflow.ReflectedXssQuery as XssConfig
|
||||
import semmle.javascript.heuristics.AdditionalRouteHandlers
|
||||
|
||||
class InlineTest extends LineComment {
|
||||
string tests;
|
||||
|
||||
InlineTest() { tests = this.getText().regexpCapture("\\s*test:(.*)", 1) }
|
||||
|
||||
string getPositiveTest() {
|
||||
result = tests.trim().splitAt(",").trim() and not result.matches("!%")
|
||||
}
|
||||
|
||||
string getNegativeTest() { result = tests.trim().splitAt(",").trim() and result.matches("!%") }
|
||||
|
||||
predicate hasPositiveTest(string test) { test = this.getPositiveTest() }
|
||||
|
||||
predicate hasNegativeTest(string test) { test = this.getNegativeTest() }
|
||||
|
||||
predicate inNode(DataFlow::Node n) {
|
||||
this.getLocation().getFile() = n.getFile() and
|
||||
this.getLocation().getStartLine() = n.getStartLine()
|
||||
}
|
||||
}
|
||||
|
||||
query predicate passingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
(
|
||||
expectation = "source" and
|
||||
exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "setup" and
|
||||
exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "handler" and
|
||||
exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "candidateHandler" and
|
||||
exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "xssSink" and
|
||||
exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "xss" and
|
||||
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "cleartextStorageSink" and
|
||||
exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "corsMiconfigurationSink" and
|
||||
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "stackTraceExposureSink" and
|
||||
exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "redirectSink" and
|
||||
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "ssrfSink" and
|
||||
exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate failingPositiveTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasPositiveTest(expectation) and
|
||||
(
|
||||
expectation = "source" and
|
||||
not exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "setup" and
|
||||
not exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "handler" and
|
||||
not exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "candidateHandler" and
|
||||
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "xssSink" and
|
||||
not exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "xss" and
|
||||
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "cleartextStorageSink" and
|
||||
not exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "corsMiconfigurationSink" and
|
||||
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "stackTraceExposureSink" and
|
||||
not exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "redirectSink" and
|
||||
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "ssrfSink" and
|
||||
not exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate passingNegativeTests(string res, string expectation, InlineTest t) {
|
||||
res = "PASSED" and
|
||||
t.hasNegativeTest(expectation) and
|
||||
(
|
||||
expectation = "!source" and
|
||||
not exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "!setup" and
|
||||
not exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "!handler" and
|
||||
not exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "!candidateHandler" and
|
||||
not exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xssSink" and
|
||||
not exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xss" and
|
||||
not exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "!cleartextStorageSink" and
|
||||
not exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!corsMiconfigurationSink" and
|
||||
not exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!stackTraceExposureSink" and
|
||||
not exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!redirectSink" and
|
||||
not exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!ssrfSink" and
|
||||
not exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
|
||||
query predicate failingNegativeTests(string res, string expectation, InlineTest t) {
|
||||
res = "FAILED" and
|
||||
t.hasNegativeTest(expectation) and
|
||||
(
|
||||
expectation = "!source" and
|
||||
exists(RemoteFlowSource n | t.inNode(n))
|
||||
or
|
||||
expectation = "!setup" and
|
||||
exists(Http::RouteSetup n | t.inNode(n))
|
||||
or
|
||||
expectation = "!handler" and
|
||||
exists(Http::RouteHandler n | t.inNode(n))
|
||||
or
|
||||
expectation = "!candidateHandler" and
|
||||
exists(Http::RouteHandlerCandidate n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xssSink" and
|
||||
exists(ReflectedXss::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!xss" and
|
||||
exists(XssConfig::Configuration cfg, DataFlow::Node sink |
|
||||
cfg.hasFlow(_, sink) and t.inNode(sink)
|
||||
)
|
||||
or
|
||||
expectation = "!cleartextStorageSink" and
|
||||
exists(CleartextStorage::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!corsMiconfigurationSink" and
|
||||
exists(CorsMisconfigurationForCredentials::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!stackTraceExposureSink" and
|
||||
exists(StackTraceExposure::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!redirectSink" and
|
||||
exists(ServerSideUrlRedirect::Sink n | t.inNode(n))
|
||||
or
|
||||
expectation = "!ssrfSink" and
|
||||
exists(RequestForgery::Sink n | t.inNode(n))
|
||||
)
|
||||
}
|
||||
@@ -11,10 +11,16 @@ test_RequestInputAccess
|
||||
| src/test.js:19:5:19:26 | request ... ('bar') | header | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
| src/test.js:20:5:20:25 | request ... ('baz') | header | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
test_RouteHandler_getAResponseHeader
|
||||
| src/test.js:6:1:6:21 | functio ... er1(){} | content-type | src/test.js:6:1:6:21 | functio ... er1(){} |
|
||||
| src/test.js:9:19:11:1 | functio ... ition\\n} | content-type | src/test.js:9:19:11:1 | functio ... ition\\n} |
|
||||
| src/test.js:9:19:11:1 | functio ... ition\\n} | header1 | src/test.js:10:5:10:34 | respons ... 1', '') |
|
||||
| src/test.js:12:19:22:1 | functio ... okie;\\n} | content-type | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
| src/test.js:12:19:22:1 | functio ... okie;\\n} | header2 | src/test.js:13:5:13:37 | respons ... 2', '') |
|
||||
test_HeaderDefinition_defines
|
||||
| src/test.js:6:1:6:21 | functio ... er1(){} | content-type | application/json |
|
||||
| src/test.js:9:19:11:1 | functio ... ition\\n} | content-type | application/json |
|
||||
| src/test.js:10:5:10:34 | respons ... 1', '') | header1 | |
|
||||
| src/test.js:12:19:22:1 | functio ... okie;\\n} | content-type | application/json |
|
||||
| src/test.js:13:5:13:37 | respons ... 2', '') | header2 | |
|
||||
test_ResponseExpr
|
||||
| src/test.js:9:46:9:53 | response | src/test.js:9:19:11:1 | functio ... ition\\n} |
|
||||
@@ -24,14 +30,20 @@ test_ResponseExpr
|
||||
| src/test.js:12:46:12:53 | response | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
| src/test.js:13:5:13:12 | response | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
test_HeaderDefinition
|
||||
| src/test.js:6:1:6:21 | functio ... er1(){} | src/test.js:6:1:6:21 | functio ... er1(){} |
|
||||
| src/test.js:9:19:11:1 | functio ... ition\\n} | src/test.js:9:19:11:1 | functio ... ition\\n} |
|
||||
| src/test.js:10:5:10:34 | respons ... 1', '') | src/test.js:9:19:11:1 | functio ... ition\\n} |
|
||||
| src/test.js:12:19:22:1 | functio ... okie;\\n} | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
| src/test.js:13:5:13:37 | respons ... 2', '') | src/test.js:12:19:22:1 | functio ... okie;\\n} |
|
||||
test_RouteSetup_getServer
|
||||
| src/test.js:7:1:7:26 | server2 ... ndler1) | src/test.js:4:15:4:36 | restify ... erver() |
|
||||
| src/test.js:9:1:11:2 | server2 ... tion\\n}) | src/test.js:4:15:4:36 | restify ... erver() |
|
||||
| src/test.js:12:1:22:2 | server2 ... kie;\\n}) | src/test.js:4:15:4:36 | restify ... erver() |
|
||||
test_HeaderDefinition_getAHeaderName
|
||||
| src/test.js:6:1:6:21 | functio ... er1(){} | content-type |
|
||||
| src/test.js:9:19:11:1 | functio ... ition\\n} | content-type |
|
||||
| src/test.js:10:5:10:34 | respons ... 1', '') | header1 |
|
||||
| src/test.js:12:19:22:1 | functio ... okie;\\n} | content-type |
|
||||
| src/test.js:13:5:13:37 | respons ... 2', '') | header2 |
|
||||
test_ServerDefinition
|
||||
| src/test.js:1:15:1:47 | require ... erver() |
|
||||
|
||||
Reference in New Issue
Block a user