Skip to content

Commit 615b2ec

Browse files
committed
JS: Fix handling of fastify-plugin
1 parent b226f76 commit 615b2ec

File tree

3 files changed

+72
-14
lines changed

3 files changed

+72
-14
lines changed

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

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,16 @@ module Fastify {
5050
t.start() and
5151
result = server(creation).getAMethodCall("register").getArgument(0).getALocalSource()
5252
or
53+
// Track through require('fastify-plugin')
54+
result = pluginCallback(creation, t).(FastifyPluginCall).getArgument(0).getALocalSource()
55+
or
5356
exists(DataFlow::TypeBackTracker t2 | result = pluginCallback(creation, t2).backtrack(t2, t))
5457
}
5558

59+
private class FastifyPluginCall extends DataFlow::CallNode {
60+
FastifyPluginCall() { this = DataFlow::moduleImport("fastify-plugin").getACall() }
61+
}
62+
5663
/** Gets a data flow node being used as a Fastify plugin. */
5764
private DataFlow::SourceNode pluginCallback(DataFlow::SourceNode creation) {
5865
result = pluginCallback(creation, DataFlow::TypeBackTracker::end())
@@ -198,18 +205,40 @@ module Fastify {
198205
}
199206

200207
private class PluginRegistration extends Routing::RouteSetup::MethodCall {
201-
ServerDefinition server;
208+
PluginRegistration() { this = server().getAMethodCall("register") }
202209

203-
PluginRegistration() {
204-
server.flowsTo(this.getReceiver().asExpr()) and
205-
getMethodName() = "register"
210+
private DataFlow::SourceNode pluginBody(DataFlow::TypeBackTracker t) {
211+
t.start() and
212+
result = getArgument(0).getALocalSource()
213+
or
214+
// step through calls to require('fastify-plugin')
215+
result = pluginBody(t).(FastifyPluginCall).getArgument(0).getALocalSource()
216+
or
217+
exists(DataFlow::TypeBackTracker t2 | result = pluginBody(t2).backtrack(t2, t))
206218
}
207219

220+
/** Gets a functino flowing into the first argument. */
221+
DataFlow::FunctionNode pluginBody() { result = pluginBody(DataFlow::TypeBackTracker::end()) }
222+
208223
override HTTP::RequestMethodName getHttpMethod() {
209224
result = getOptionArgument(1, "method").getStringValue().toUpperCase()
210225
}
211226

212227
override string getRelativePath() { result = getOptionArgument(1, "prefix").getStringValue() }
228+
229+
override DataFlow::Node getChildNode(int n) {
230+
n = 0 and
231+
(
232+
// If we can see the plugin body, use its server parameter as the child to ensure
233+
// plugins or routes installed in the plugin are ordered
234+
result = pluginBody().getParameter(0)
235+
or
236+
// If we can't see the plugin body, just use the plugin expression so we can
237+
// check if something is guarded by that plugin.
238+
not exists(pluginBody()) and
239+
result = getArgument(0)
240+
)
241+
}
213242
}
214243

215244
/**
@@ -403,14 +432,4 @@ module Fastify {
403432
)
404433
}
405434
}
406-
407-
private class RouteHandlerTracking extends Routing::RouteHandlerTrackingStep {
408-
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
409-
exists(DataFlow::CallNode call |
410-
call = DataFlow::moduleImport("fastify-plugin") and
411-
pred = call.getArgument(0) and
412-
succ = call
413-
)
414-
}
415-
}
416435
}

javascript/ql/test/query-tests/Security/CWE-352/MissingCsrfMiddleware.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
| MissingCsrfMiddlewareBad.js:33:13:33:26 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:45:31:47:6 | errorCa ... \\n }) | here |
55
| csurf_api_example.js:42:37:42:50 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | csurf_api_example.js:42:53:45:3 | functio ... e')\\n } | here |
66
| csurf_example.js:18:9:18:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | csurf_example.js:31:40:34:1 | functio ... sed')\\n} | here |
7+
| fastify2.js:7:16:7:40 | require ... ookie') | This cookie middleware is serving a request handler $@ without CSRF protection. | fastify2.js:24:12:27:3 | async ( ... ody\\n } | here |
78
| fastify.js:5:14:5:38 | require ... ookie') | This cookie middleware is serving a request handler $@ without CSRF protection. | fastify.js:20:12:23:3 | async ( ... ody\\n } | here |
89
| lusca_example.js:9:9:9:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | lusca_example.js:26:42:29:1 | functio ... sed')\\n} | here |
910
| lusca_example.js:9:9:9:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | lusca_example.js:31:40:34:1 | functio ... sed')\\n} | here |
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const fastify = require('fastify')
2+
const fp = require('fastify-plugin');
3+
4+
const app = fastify();
5+
6+
function plugin(app) {
7+
app.register(require('fastify-cookie'));
8+
app.register(require('fastify-csrf'));
9+
}
10+
app.register(fp(plugin));
11+
12+
app.route({
13+
method: 'GET',
14+
path: '/getter',
15+
handler: async (req, reply) => { // OK
16+
return 'hello';
17+
}
18+
})
19+
20+
// unprotected route
21+
app.route({
22+
method: 'POST',
23+
path: '/',
24+
handler: async (req, reply) => { // NOT OK - lacks CSRF protection
25+
req.session.blah;
26+
return req.body
27+
}
28+
})
29+
30+
31+
app.route({
32+
method: 'POST',
33+
path: '/',
34+
onRequest: app.csrfProtection,
35+
handler: async (req, reply) => { // OK - has CSRF protection
36+
return req.body
37+
}
38+
})

0 commit comments

Comments
 (0)