Skip to content

Commit 4b56229

Browse files
authored
Merge pull request github#3527 from esbena/js/fastify
Approved by asgerf
2 parents df205b6 + b31f83a commit 4b56229

20 files changed

+594
-0
lines changed

change-notes/1.25/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Support for the following frameworks and libraries has been improved:
66
- [bluebird](http://bluebirdjs.com/)
77
- [express](https://www.npmjs.com/package/express)
8+
- [fastify](https://www.npmjs.com/package/fastify)
89
- [fstream](https://www.npmjs.com/package/fstream)
910
- [jGrowl](https://github.com/stanlemon/jGrowl)
1011
- [jQuery](https://jquery.com/)
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
/**
2+
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
3+
*/
4+
5+
import javascript
6+
import semmle.javascript.frameworks.HTTP
7+
8+
/**
9+
* Provides classes for working with [Fastify](https://www.fastify.io/) applications.
10+
*/
11+
module Fastify {
12+
/**
13+
* An expression that creates a new Fastify server.
14+
*/
15+
abstract class ServerDefinition extends HTTP::Servers::StandardServerDefinition { }
16+
17+
/**
18+
* A standard way to create a Fastify server.
19+
*/
20+
class StandardServerDefinition extends ServerDefinition {
21+
StandardServerDefinition() {
22+
this = DataFlow::moduleImport("fastify").getAnInvocation().asExpr()
23+
}
24+
}
25+
26+
/**
27+
* A function used as a Fastify route handler.
28+
*
29+
* By default, only handlers installed by a Fastify route setup are recognized,
30+
* but support for other kinds of route handlers can be added by implementing
31+
* additional subclasses of this class.
32+
*/
33+
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
34+
/**
35+
* Gets the parameter of the route handler that contains the request object.
36+
*/
37+
abstract DataFlow::ParameterNode getRequestParameter();
38+
39+
/**
40+
* Gets the parameter of the route handler that contains the reply object.
41+
*/
42+
abstract DataFlow::ParameterNode getReplyParameter();
43+
}
44+
45+
/**
46+
* A Fastify route handler installed by a route setup.
47+
*/
48+
class StandardRouteHandler extends RouteHandler, DataFlow::FunctionNode {
49+
StandardRouteHandler() { this = any(RouteSetup setup).getARouteHandler() }
50+
51+
override DataFlow::ParameterNode getRequestParameter() { result = this.getParameter(0) }
52+
53+
override DataFlow::ParameterNode getReplyParameter() { result = this.getParameter(1) }
54+
}
55+
56+
/**
57+
* A Fastify reply source, that is, the `reply` parameter of a
58+
* route handler.
59+
*/
60+
private class ReplySource extends HTTP::Servers::ResponseSource {
61+
RouteHandler rh;
62+
63+
ReplySource() { this = rh.getReplyParameter() }
64+
65+
/**
66+
* Gets the route handler that provides this response.
67+
*/
68+
override RouteHandler getRouteHandler() { result = rh }
69+
}
70+
71+
/**
72+
* A Fastify request source, that is, the request parameter of a
73+
* route handler.
74+
*/
75+
private class RequestSource extends HTTP::Servers::RequestSource {
76+
RouteHandler rh;
77+
78+
RequestSource() { this = rh.getRequestParameter() }
79+
80+
/**
81+
* Gets the route handler that handles this request.
82+
*/
83+
override RouteHandler getRouteHandler() { result = rh }
84+
}
85+
86+
/**
87+
* A call to a Fastify method that sets up a route.
88+
*/
89+
class RouteSetup extends MethodCallExpr, HTTP::Servers::StandardRouteSetup {
90+
ServerDefinition server;
91+
string methodName;
92+
93+
RouteSetup() {
94+
this.getMethodName() = methodName and
95+
methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] and
96+
server.flowsTo(this.getReceiver())
97+
}
98+
99+
override DataFlow::SourceNode getARouteHandler() {
100+
result = getARouteHandler(DataFlow::TypeBackTracker::end())
101+
}
102+
103+
private DataFlow::SourceNode getARouteHandler(DataFlow::TypeBackTracker t) {
104+
t.start() and
105+
result = this.getARouteHandlerExpr().getALocalSource()
106+
or
107+
exists(DataFlow::TypeBackTracker t2 | result = this.getARouteHandler(t2).backtrack(t2, t))
108+
}
109+
110+
override Expr getServer() { result = server }
111+
112+
/** Gets an argument that represents a route handler being registered. */
113+
private DataFlow::Node getARouteHandlerExpr() {
114+
if methodName = "route"
115+
then
116+
result =
117+
this
118+
.flow()
119+
.(DataFlow::MethodCallNode)
120+
.getOptionArgument(0,
121+
["onRequest", "preParsing", "preValidation", "preHandler", "preSerialization",
122+
"onSend", "onResponse", "handler"])
123+
else result = getLastArgument().flow()
124+
}
125+
}
126+
127+
/**
128+
* An access to a user-controlled Fastify request input.
129+
*/
130+
private class RequestInputAccess extends HTTP::RequestInputAccess {
131+
RouteHandler rh;
132+
string kind;
133+
134+
RequestInputAccess() {
135+
exists(string name | this = rh.getARequestSource().ref().getAPropertyRead(name) |
136+
kind = "parameter" and
137+
name = ["params", "query"]
138+
or
139+
kind = "body" and
140+
name = "body"
141+
)
142+
}
143+
144+
override RouteHandler getRouteHandler() { result = rh }
145+
146+
override string getKind() { result = kind }
147+
148+
override predicate isUserControlledObject() {
149+
kind = "body" and
150+
(
151+
usesFastifyPlugin(rh,
152+
DataFlow::moduleImport(["fastify-xml-body-parser", "fastify-formbody"]))
153+
or
154+
usesMiddleware(rh,
155+
any(ExpressLibraries::BodyParser bodyParser | bodyParser.producesUserControlledObjects()))
156+
)
157+
or
158+
kind = "parameter" and
159+
usesFastifyPlugin(rh, DataFlow::moduleImport("fastify-qs"))
160+
}
161+
}
162+
163+
/**
164+
* Holds if `rh` uses `plugin`.
165+
*/
166+
private predicate usesFastifyPlugin(RouteHandler rh, DataFlow::SourceNode plugin) {
167+
exists(RouteSetup setup |
168+
plugin
169+
.flowsTo(setup
170+
.getServer()
171+
.flow()
172+
.(DataFlow::SourceNode)
173+
.getAMethodCall("register")
174+
.getArgument(0)) and // only matches the plugins that apply to all routes
175+
rh = setup.getARouteHandler()
176+
)
177+
}
178+
179+
/**
180+
* Holds if `rh` uses `plugin`.
181+
*/
182+
private predicate usesMiddleware(RouteHandler rh, DataFlow::SourceNode middleware) {
183+
exists(RouteSetup setup |
184+
middleware
185+
.flowsTo(setup
186+
.getServer()
187+
.flow()
188+
.(DataFlow::SourceNode)
189+
.getAMethodCall("use")
190+
.getArgument(0)) and // only matches the middlewares that apply to all routes
191+
rh = setup.getARouteHandler()
192+
)
193+
}
194+
195+
/**
196+
* An access to a header on a Fastify request.
197+
*/
198+
private class RequestHeaderAccess extends HTTP::RequestHeaderAccess {
199+
RouteHandler rh;
200+
201+
RequestHeaderAccess() {
202+
this = rh.getARequestSource().ref().getAPropertyRead("headers").getAPropertyRead()
203+
}
204+
205+
override string getAHeaderName() {
206+
result = this.(DataFlow::PropRead).getPropertyName().toLowerCase()
207+
}
208+
209+
override RouteHandler getRouteHandler() { result = rh }
210+
211+
override string getKind() { result = "header" }
212+
}
213+
214+
/**
215+
* An argument passed to the `send` or `end` method of an HTTP response object.
216+
*/
217+
private class ResponseSendArgument extends HTTP::ResponseSendArgument {
218+
RouteHandler rh;
219+
220+
ResponseSendArgument() {
221+
this = rh.getAResponseSource().ref().getAMethodCall("send").getArgument(0).asExpr()
222+
or
223+
this = rh.(DataFlow::FunctionNode).getAReturn().asExpr()
224+
}
225+
226+
override RouteHandler getRouteHandler() { result = rh }
227+
}
228+
229+
/**
230+
* An invocation of the `redirect` method of an HTTP response object.
231+
*/
232+
private class RedirectInvocation extends HTTP::RedirectInvocation, MethodCallExpr {
233+
RouteHandler rh;
234+
235+
RedirectInvocation() {
236+
this = rh.getAResponseSource().ref().getAMethodCall("redirect").asExpr()
237+
}
238+
239+
override Expr getUrlArgument() { result = this.getLastArgument() }
240+
241+
override RouteHandler getRouteHandler() { result = rh }
242+
}
243+
244+
/**
245+
* An invocation that sets a single header of the HTTP response.
246+
*/
247+
private class SetOneHeader extends HTTP::Servers::StandardHeaderDefinition,
248+
DataFlow::MethodCallNode {
249+
RouteHandler rh;
250+
251+
SetOneHeader() {
252+
this = rh.getAResponseSource().ref().getAMethodCall("header") and
253+
this.getNumArgument() = 2
254+
}
255+
256+
override RouteHandler getRouteHandler() { result = rh }
257+
}
258+
259+
/**
260+
* An invocation that sets any number of headers of the HTTP response.
261+
*/
262+
class SetMultipleHeaders extends HTTP::ExplicitHeaderDefinition, DataFlow::MethodCallNode {
263+
RouteHandler rh;
264+
265+
SetMultipleHeaders() {
266+
this = rh.getAResponseSource().ref().getAMethodCall("headers") and
267+
this.getNumArgument() = 1
268+
}
269+
270+
/**
271+
* Gets a reference to the multiple headers object that is to be set.
272+
*/
273+
private DataFlow::SourceNode getAHeaderSource() { result.flowsTo(this.getArgument(0)) }
274+
275+
override predicate definesExplicitly(string headerName, Expr headerValue) {
276+
exists(string header |
277+
getAHeaderSource().hasPropertyWrite(header, headerValue.flow()) and
278+
headerName = header.toLowerCase()
279+
)
280+
}
281+
282+
override RouteHandler getRouteHandler() { result = rh }
283+
284+
override Expr getNameExpr() {
285+
exists(DataFlow::PropWrite write |
286+
this.getAHeaderSource().flowsTo(write.getBase()) and
287+
result = write.getPropertyNameExpr()
288+
)
289+
}
290+
}
291+
}

javascript/ql/src/semmle/javascript/frameworks/HttpFrameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import semmle.javascript.frameworks.Koa
44
import semmle.javascript.frameworks.NodeJSLib
55
import semmle.javascript.frameworks.Restify
66
import semmle.javascript.frameworks.Connect
7+
import semmle.javascript.frameworks.Fastify
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderAccess(HTTP::RequestHeaderAccess access, string res) {
4+
res = access.getAHeaderName()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition(HTTP::HeaderDefinition hd, Fastify::RouteHandler rh) {
4+
rh = hd.getRouteHandler()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition_defines(HTTP::HeaderDefinition hd, string name, string value) {
4+
hd.defines(name, value) and hd.getRouteHandler() instanceof Fastify::RouteHandler
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_HeaderDefinition_getAHeaderName(HTTP::HeaderDefinition hd, string res) {
4+
hd.getRouteHandler() instanceof Fastify::RouteHandler and res = hd.getAHeaderName()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_RedirectInvocation(HTTP::RedirectInvocation invk, Fastify::RouteHandler rh) {
4+
invk.getRouteHandler() = rh
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import javascript
2+
3+
query predicate test_RequestInputAccess(
4+
HTTP::RequestInputAccess ria, string res, Fastify::RouteHandler rh, boolean isUserControlledObject
5+
) {
6+
ria.getRouteHandler() = rh and
7+
res = ria.getKind() and
8+
if ria.isUserControlledObject()
9+
then isUserControlledObject = true
10+
else isUserControlledObject = false
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
query predicate test_ResponseSendArgument(HTTP::ResponseSendArgument arg, Fastify::RouteHandler rh) {
4+
arg.getRouteHandler() = rh
5+
}

0 commit comments

Comments
 (0)