Skip to content

Commit 7182056

Browse files
committed
JS: Instantiate for Fastify
1 parent cfb9265 commit 7182056

File tree

3 files changed

+151
-10
lines changed

3 files changed

+151
-10
lines changed

javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll

Lines changed: 111 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,47 @@ module Fastify {
2323
}
2424
}
2525

26+
/** Gets a data flow node referring to a fastify server. */
27+
private DataFlow::SourceNode server(DataFlow::SourceNode creation, DataFlow::TypeTracker t) {
28+
t.start() and
29+
result = DataFlow::moduleImport("fastify").getAnInvocation() and
30+
creation = result
31+
or
32+
// server.register((serverAlias) => ..., { options })
33+
t.start() and
34+
result = pluginCallback(creation).(DataFlow::FunctionNode).getParameter(0)
35+
or
36+
exists(DataFlow::TypeTracker t2 | result = server(creation, t2).track(t2, t))
37+
}
38+
39+
/** Gets a data flow node referring to the given fastify server instance. */
40+
DataFlow::SourceNode server(DataFlow::SourceNode creation) {
41+
result = server(creation, DataFlow::TypeTracker::end())
42+
}
43+
44+
/** Gets a data flow node referring to a fastify server. */
45+
DataFlow::SourceNode server() { result = server(_) }
46+
47+
private DataFlow::SourceNode pluginCallback(
48+
DataFlow::SourceNode creation, DataFlow::TypeBackTracker t
49+
) {
50+
t.start() and
51+
result = server(creation).getAMethodCall("register").getArgument(0).getALocalSource()
52+
or
53+
exists(DataFlow::TypeBackTracker t2 | result = pluginCallback(creation, t2).backtrack(t2, t))
54+
}
55+
56+
/** Gets a data flow node being used as a Fastify plugin. */
57+
private DataFlow::SourceNode pluginCallback(DataFlow::SourceNode creation) {
58+
result = pluginCallback(creation, DataFlow::TypeBackTracker::end())
59+
}
60+
61+
private class RouterDef extends Routing::Router::Range {
62+
RouterDef() { exists(server(this)) }
63+
64+
override DataFlow::SourceNode getAReference() { result = server(this) }
65+
}
66+
2667
/**
2768
* A function used as a Fastify route handler.
2869
*
@@ -110,19 +151,66 @@ module Fastify {
110151
override Expr getServer() { result = server }
111152

112153
/** Gets an argument that represents a route handler being registered. */
113-
private DataFlow::Node getARouteHandlerExpr() {
154+
DataFlow::Node getARouteHandlerExpr() {
114155
if methodName = "route"
115156
then
116-
result =
117-
this.flow()
118-
.(DataFlow::MethodCallNode)
119-
.getOptionArgument(0,
120-
[
121-
"onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
122-
"onSend", "onResponse", "handler"
123-
])
124-
else result = this.getLastArgument().flow()
157+
result = this.flow().(DataFlow::MethodCallNode).getOptionArgument(0, getNthHandlerName(_))
158+
else result = getLastArgument().flow()
159+
}
160+
}
161+
162+
private class ShorthandRoutingTreeSetup extends Routing::RouteSetup::MethodCall {
163+
ShorthandRoutingTreeSetup() {
164+
this.asExpr() instanceof RouteSetup and
165+
not this.getMethodName() = "route"
125166
}
167+
168+
override string getRelativePath() { result = this.getArgument(0).getStringValue() }
169+
170+
override HTTP::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() }
171+
}
172+
173+
/** Gets the name of the `n`th handler function that can be installed a route setup, in order of execution. */
174+
private string getNthHandlerName(int n) {
175+
result =
176+
"onRequest,preParsing,preValidation,preHandler,handler,preSerialization,onSend,onResponse"
177+
.splitAt(",", n)
178+
}
179+
180+
private class FullRoutingTreeSetup extends Routing::RouteSetup::MethodCall {
181+
FullRoutingTreeSetup() {
182+
asExpr() instanceof RouteSetup and
183+
getMethodName() = "route"
184+
}
185+
186+
override string getRelativePath() { result = getOptionArgument(0, "url").getStringValue() }
187+
188+
override HTTP::RequestMethodName getHttpMethod() {
189+
result = getOptionArgument(0, "method").getStringValue().toUpperCase()
190+
}
191+
192+
private DataFlow::Node getRawChild(int n) {
193+
result = getOptionArgument(0, getNthHandlerName(n))
194+
}
195+
196+
override DataFlow::Node getChildNode(int n) {
197+
result = getRawChild(rank[n + 1](int k | exists(getRawChild(k))))
198+
}
199+
}
200+
201+
private class PluginRegistration extends Routing::RouteSetup::MethodCall {
202+
ServerDefinition server;
203+
204+
PluginRegistration() {
205+
server.flowsTo(this.getReceiver().asExpr()) and
206+
getMethodName() = "register"
207+
}
208+
209+
override HTTP::RequestMethodName getHttpMethod() {
210+
result = getOptionArgument(1, "method").getStringValue().toUpperCase()
211+
}
212+
213+
override string getRelativePath() { result = getOptionArgument(1, "prefix").getStringValue() }
126214
}
127215

128216
/**
@@ -303,4 +391,17 @@ module Fastify {
303391

304392
override DataFlow::Node getTemplateParamsNode() { result = this.getArgument(1) }
305393
}
394+
395+
private class FastifyCookieMiddleware extends HTTP::CookieMiddlewareInstance {
396+
FastifyCookieMiddleware() {
397+
this = DataFlow::moduleImport(["fastify-cookie", "fastify-session", "fastify-secure-session"])
398+
}
399+
400+
override DataFlow::Node getASecretKey() {
401+
exists(PluginRegistration registration |
402+
this = registration.getArgument(0).getALocalSource() and
403+
result = registration.getOptionArgument(1, "secret")
404+
)
405+
}
406+
}
306407
}

javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ module HTTP {
234234
or
235235
// references to class methods
236236
succ = CallGraph::callgraphStep(pred, DataFlow::TypeTracker::end())
237+
or
238+
exists(DataFlow::CallNode call |
239+
call = DataFlow::moduleImport("fastify-plugin") and
240+
pred = call.getArgument(0) and
241+
succ = call
242+
)
237243
}
238244

239245
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const fastify = require('fastify')
2+
3+
const app = fastify();
4+
5+
app.register(require('fastify-cookie'));
6+
app.register(require('fastify-csrf'));
7+
8+
app.route({
9+
method: 'GET',
10+
path: '/getter',
11+
handler: async (req, reply) => { // OK
12+
return 'hello';
13+
}
14+
})
15+
16+
// unprotected route
17+
app.route({
18+
method: 'POST',
19+
path: '/',
20+
handler: async (req, reply) => { // NOT OK - lacks CSRF protection
21+
req.session.blah;
22+
return req.body
23+
}
24+
})
25+
26+
27+
app.route({
28+
method: 'POST',
29+
path: '/',
30+
onRequest: app.csrfProtection,
31+
handler: async (req, reply) => { // OK - has CSRF protection
32+
return req.body
33+
}
34+
})

0 commit comments

Comments
 (0)