forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFastify.qll
More file actions
453 lines (373 loc) · 15.1 KB
/
Fastify.qll
File metadata and controls
453 lines (373 loc) · 15.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/**
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
*/
import javascript
import semmle.javascript.frameworks.HTTP
/**
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
*/
module Fastify {
/**
* An expression that creates a new Fastify server.
*/
abstract class ServerDefinition extends Http::Servers::StandardServerDefinition { }
/**
* A standard way to create a Fastify server.
*/
class StandardServerDefinition extends ServerDefinition {
StandardServerDefinition() { this = DataFlow::moduleImport("fastify").getAnInvocation() }
}
/** Gets a data flow node referring to a fastify server. */
private DataFlow::SourceNode server(DataFlow::SourceNode creation, DataFlow::TypeTracker t) {
t.start() and
result = DataFlow::moduleImport("fastify").getAnInvocation() and
creation = result
or
// server.register((serverAlias) => ..., { options })
t.start() and
result = pluginCallback(creation).(DataFlow::FunctionNode).getParameter(0)
or
exists(DataFlow::TypeTracker t2 | result = server(creation, t2).track(t2, t))
}
/** Gets a data flow node referring to the given fastify server instance. */
DataFlow::SourceNode server(DataFlow::SourceNode creation) {
result = server(creation, DataFlow::TypeTracker::end())
}
/** Gets a data flow node referring to a fastify server. */
DataFlow::SourceNode server() { result = server(_) }
private DataFlow::SourceNode pluginCallback(
DataFlow::SourceNode creation, DataFlow::TypeBackTracker t
) {
t.start() and
result = server(creation).getAMethodCall("register").getArgument(0).getALocalSource()
or
// Track through require('fastify-plugin')
result = pluginCallback(creation, t).(FastifyPluginCall).getArgument(0).getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = pluginCallback(creation, t2).backtrack(t2, t))
}
private class FastifyPluginCall extends DataFlow::CallNode {
FastifyPluginCall() { this = DataFlow::moduleImport("fastify-plugin").getACall() }
}
/** Gets a data flow node being used as a Fastify plugin. */
private DataFlow::SourceNode pluginCallback(DataFlow::SourceNode creation) {
result = pluginCallback(creation, DataFlow::TypeBackTracker::end())
}
private class RouterDef extends Routing::Router::Range {
RouterDef() { exists(server(this)) }
override DataFlow::SourceNode getAReference() { result = server(this) }
}
/**
* A function used as a Fastify route handler.
*
* By default, only handlers installed by a Fastify route setup are recognized,
* but support for other kinds of route handlers can be added by implementing
* additional subclasses of this class.
*/
abstract class RouteHandler extends Http::Servers::StandardRouteHandler, DataFlow::ValueNode {
/**
* Gets the parameter of the route handler that contains the request object.
*/
abstract DataFlow::ParameterNode getRequestParameter();
/**
* Gets the parameter of the route handler that contains the reply object.
*/
abstract DataFlow::ParameterNode getReplyParameter();
}
/**
* A Fastify route handler installed by a route setup.
*/
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
override DataFlow::ParameterNode getRequestParameter() { result = this.getParameter(0) }
override DataFlow::ParameterNode getReplyParameter() { result = this.getParameter(1) }
}
/**
* A Fastify reply source, that is, the `reply` parameter of a
* route handler.
*/
private class ReplySource extends Http::Servers::ResponseSource {
RouteHandler rh;
ReplySource() { this = rh.getReplyParameter() }
/**
* Gets the route handler that provides this response.
*/
override RouteHandler getRouteHandler() { result = rh }
}
/**
* A Fastify 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 call to a Fastify method that sets up a route.
*/
class RouteSetup extends DataFlow::MethodCallNode, Http::Servers::StandardRouteSetup {
ServerDefinition server;
string methodName;
RouteSetup() {
this = server(server).getAMethodCall(methodName) and
methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch", "addHook"]
}
override DataFlow::SourceNode getARouteHandler() {
result = this.getARouteHandler(DataFlow::TypeBackTracker::end())
}
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
t.start() and
result = this.getARouteHandlerNode().getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
}
override DataFlow::SourceNode getServer() { result = server }
/** Gets an argument that represents a route handler being registered. */
DataFlow::Node getARouteHandlerNode() {
if methodName = "route"
then result = this.getOptionArgument(0, getNthHandlerName(_))
else result = this.getLastArgument()
}
}
private class ShorthandRoutingTreeSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup
{
ShorthandRoutingTreeSetup() { not this.getMethodName() = ["route", "addHook"] }
override string getRelativePath() { result = this.getArgument(0).getStringValue() }
override Http::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() }
}
private class AddHookRouteSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup {
AddHookRouteSetup() { this.getMethodName() = "addHook" }
override predicate isMiddlewareSetup() { any() }
}
/** Gets the name of the `n`th handler function that can be installed a route setup, in order of execution. */
private string getNthHandlerName(int n) {
result =
"onRequest,preParsing,preValidation,preHandler,handler,preSerialization,onSend,onResponse"
.splitAt(",", n)
}
private class FullRoutingTreeSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup {
FullRoutingTreeSetup() { this.getMethodName() = "route" }
override string getRelativePath() { result = this.getOptionArgument(0, "url").getStringValue() }
override Http::RequestMethodName getHttpMethod() {
result = this.getOptionArgument(0, "method").getStringValue().toUpperCase()
}
private DataFlow::Node getRawChild(int n) {
result = this.getOptionArgument(0, getNthHandlerName(n))
}
override DataFlow::Node getChildNode(int n) {
result =
rank[n + 1](DataFlow::Node child, int k | child = this.getRawChild(k) | child order by k)
}
}
private class PluginRegistration extends Routing::RouteSetup::MethodCall {
PluginRegistration() { this = server().getAMethodCall("register") }
private DataFlow::SourceNode pluginBody(DataFlow::TypeBackTracker t) {
t.start() and
result = this.getArgument(0).getALocalSource()
or
// step through calls to require('fastify-plugin')
result = this.pluginBody(t).(FastifyPluginCall).getArgument(0).getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 | result = this.pluginBody(t2).backtrack(t2, t))
}
/** Gets a functino flowing into the first argument. */
DataFlow::FunctionNode pluginBody() {
result = this.pluginBody(DataFlow::TypeBackTracker::end())
}
override Http::RequestMethodName getHttpMethod() {
result = this.getOptionArgument(1, "method").getStringValue().toUpperCase()
}
override string getRelativePath() {
result = this.getOptionArgument(1, "prefix").getStringValue()
}
override DataFlow::Node getChildNode(int n) {
n = 0 and
(
// If we can see the plugin body, use its server parameter as the child to ensure
// plugins or routes installed in the plugin are ordered
result = this.pluginBody().getParameter(0)
or
// If we can't see the plugin body, just use the plugin expression so we can
// check if something is guarded by that plugin.
not exists(this.pluginBody()) and
result = this.getArgument(0)
)
}
}
/**
* Gets the property name where user-controlled input is written to a request or response object
* in a route handler. This is used to track taint flow through request and response object properties.
*/
private string getUserControlledPropertyName() {
exists(DataFlow::PropWrite write, DataFlow::Node source, RouteHandler rh |
write.getBase*() =
[rh.getARequestSource().ref().getALocalUse(), rh.getAResponseSource().ref().getALocalUse()] and
write.getPropertyName() = result and
write.getRhs() = source and
source = any(Http::RequestInputAccess ria).getASuccessor*()
)
}
/**
* An access to a user-controlled Fastify request input.
*/
private class RequestInputAccess extends Http::RequestInputAccess {
RouteHandler rh;
string kind;
RequestInputAccess() {
exists(string name | this = rh.getARequestSource().ref().getAPropertyRead(name) |
kind = "parameter" and
name = ["params", "query"]
or
kind = "body" and
name = "body"
or
kind = "stored" and
name = getUserControlledPropertyName()
)
or
// Handle reading from reply object with user input stored on it
exists(string name |
(
this = rh.getAResponseSource().ref().getAPropertyRead(name)
or
this = rh.getAResponseSource().ref().getAPropertyRead+().getAPropertyRead(name)
) and
kind = "stored" and
name = getUserControlledPropertyName()
)
}
override RouteHandler getRouteHandler() { result = rh }
override string getKind() { result = kind }
override predicate isUserControlledObject() {
kind = "body" and
(
usesFastifyPlugin(rh,
DataFlow::moduleImport(["fastify-xml-body-parser", "fastify-formbody"]))
or
usesMiddleware(rh,
any(ExpressLibraries::BodyParser bodyParser | bodyParser.producesUserControlledObjects()))
)
or
kind = "parameter" and
usesFastifyPlugin(rh, DataFlow::moduleImport("fastify-qs"))
}
}
/**
* Holds if `rh` uses `plugin`.
*/
private predicate usesFastifyPlugin(RouteHandler rh, DataFlow::SourceNode plugin) {
exists(RouteSetup setup |
plugin.flowsTo(setup.getServer().getAMethodCall("register").getArgument(0)) and // only matches the plugins that apply to all routes
rh = setup.getARouteHandler()
)
}
/**
* Holds if `rh` uses `middleware`.
*/
private predicate usesMiddleware(RouteHandler rh, DataFlow::SourceNode middleware) {
exists(RouteSetup setup |
middleware.flowsTo(setup.getServer().getAMethodCall("use").getArgument(0)) and // only matches the middlewares that apply to all routes
rh = setup.getARouteHandler()
)
}
/**
* An access to a header on a Fastify request.
*/
private class RequestHeaderAccess extends Http::RequestHeaderAccess {
RouteHandler rh;
RequestHeaderAccess() {
this = rh.getARequestSource().ref().getAPropertyRead("headers").getAPropertyRead()
}
override string getAHeaderName() {
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
}
override RouteHandler getRouteHandler() { result = rh }
override string getKind() { result = "header" }
}
/**
* An argument passed to the `send` or `end` method of an HTTP response object.
*/
private class ResponseSendArgument extends Http::ResponseSendArgument {
RouteHandler rh;
ResponseSendArgument() {
this = rh.getAResponseSource().ref().getAMethodCall("send").getArgument(0)
or
this = rh.(DataFlow::FunctionNode).getAReturn()
}
override RouteHandler getRouteHandler() { result = rh }
}
/**
* 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() { result = this.getLastArgument() }
override RouteHandler getRouteHandler() { result = rh }
}
/**
* An invocation that sets a single header of the HTTP response.
*/
private class SetOneHeader extends Http::Servers::StandardHeaderDefinition,
DataFlow::MethodCallNode
{
RouteHandler rh;
SetOneHeader() {
this = rh.getAResponseSource().ref().getAMethodCall("header") and
this.getNumArgument() = 2
}
override RouteHandler getRouteHandler() { result = rh }
}
/**
* An invocation that sets any number of headers of the HTTP response.
*/
class SetMultipleHeaders extends Http::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
RouteHandler rh;
SetMultipleHeaders() {
this = rh.getAResponseSource().ref().getAMethodCall("headers") and
this.getNumArgument() = 1
}
/**
* Gets a reference to the multiple headers object that is to be set.
*/
private DataFlow::SourceNode getAHeaderSource() { 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 RouteHandler getRouteHandler() { result = rh }
override DataFlow::Node getNameNode() {
exists(DataFlow::PropWrite write | this.getAHeaderSource().getAPropertyWrite() = write |
result = write.getPropertyNameExpr().flow()
)
}
}
/**
* A call to `rep.view('file', { ... })`, seen as a template instantiation.
*
* Assumes the presence of a plugin that provides the `view` method, such as the `point-of-view` plugin.
*/
private class ViewCall extends Templating::TemplateInstantiation::Range, DataFlow::CallNode {
ViewCall() { this = any(ReplySource rep).ref().getAMethodCall("view") }
override DataFlow::SourceNode getOutput() { result = this.getCallback(2).getParameter(1) }
override DataFlow::Node getTemplateFileNode() { result = this.getArgument(0) }
override DataFlow::Node getTemplateParamsNode() { result = this.getArgument(1) }
}
private class FastifyCookieMiddleware extends Http::CookieMiddlewareInstance {
FastifyCookieMiddleware() {
this = DataFlow::moduleImport(["fastify-cookie", "fastify-session", "fastify-secure-session"])
}
override DataFlow::Node getASecretKey() {
exists(PluginRegistration registration |
this = registration.getArgument(0).getALocalSource() and
result = registration.getOptionArgument(1, "secret")
)
}
}
}