Skip to content

Commit 1fdbbb6

Browse files
committed
support Next.js page request/response objects
1 parent a5cf024 commit 1fdbbb6

File tree

6 files changed

+133
-11
lines changed

6 files changed

+133
-11
lines changed

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

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import javascript
77
/**
88
* Provides classes and predicates modelling [Next.js](https://www.npmjs.com/package/next).
99
*/
10-
private module NextJS {
10+
module NextJS {
11+
// TODO: Private.
1112
/**
1213
* Gets a `package.json` that depends on the `Next.js` library.
1314
*/
@@ -59,6 +60,41 @@ private module NextJS {
5960
override string getSourceType() { result = "Next request parameter" }
6061
}
6162

63+
/**
64+
* Gets the `getStaticProps` function in a Next.js page.
65+
* This function is executed at build time, or when a page with a new URL is requested for the first time (if `fallback` is not false).
66+
*/
67+
DataFlow::FunctionNode getStaticPropsFunction(Module pageModule) {
68+
pageModule = getAPagesModule() and
69+
result = pageModule.getAnExportedValue("getStaticProps").getAFunctionValue()
70+
}
71+
72+
/**
73+
* Gets the `getServerSideProps` function in a Next.js page.
74+
* This function is executed on the server every time a request for the page is made.
75+
* The function receives a context parameter, which includes HTTP request/response objects.
76+
*/
77+
DataFlow::FunctionNode getServerSidePropsFunction(Module pageModule) {
78+
pageModule = getAPagesModule() and
79+
result = pageModule.getAnExportedValue("getServerSideProps").getAFunctionValue()
80+
}
81+
82+
/**
83+
* Gets the `getInitialProps` function in a Next.js page.
84+
* This function is executed on the server every time a request for the page is made.
85+
* The function receives a context parameter, which includes HTTP request/response objects.
86+
*/
87+
DataFlow::FunctionNode getInitialProps(Module pageModule) {
88+
pageModule = getAPagesModule() and
89+
result =
90+
pageModule
91+
.getAnExportedValue("default")
92+
.getAFunctionValue()
93+
.getAPropertyWrite("getInitialProps")
94+
.getRhs()
95+
.getAFunctionValue()
96+
}
97+
6298
/**
6399
* A step modelling the flow from the server-computed `getStaticProps` to the server/client rendering of the page.
64100
*/
@@ -73,17 +109,48 @@ private module NextJS {
73109
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
74110
(
75111
pred =
76-
pageModule
77-
.getAnExportedValue(["getStaticProps", "getServerSideProps"])
78-
.getAFunctionValue()
112+
[getStaticPropsFunction(pageModule), getServerSidePropsFunction(pageModule)]
79113
.getAReturn()
80114
.getALocalSource()
81115
.getAPropertyWrite("props")
82116
.getRhs()
83117
or
84-
pred = this.getAPropertyWrite("getInitialProps").getRhs().getAFunctionValue().getAReturn()
118+
pred = getInitialProps(pageModule).getAReturn()
85119
) and
86120
succ = this.getParameter(0)
87121
}
88122
}
123+
124+
/**
125+
* A Next.js function that is exected on the server for every request, seen as a routehandler.
126+
*/
127+
class NextHttpRouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::FunctionNode {
128+
Module pageModule;
129+
130+
NextHttpRouteHandler() {
131+
this = getServerSidePropsFunction(pageModule) or this = getInitialProps(pageModule)
132+
}
133+
}
134+
135+
/**
136+
* A NodeJS HTTP request object in a Next.js page.
137+
*/
138+
class NextHttpRequestSource extends NodeJSLib::RequestSource {
139+
NextHttpRouteHandler rh;
140+
141+
NextHttpRequestSource() { this = rh.getParameter(0).getAPropertyRead("req") }
142+
143+
override HTTP::RouteHandler getRouteHandler() { result = rh }
144+
}
145+
146+
/**
147+
* A NodeJS HTTP response object in a Next.js page.
148+
*/
149+
class NextHttpResponseSource extends NodeJSLib::ResponseSource {
150+
NextHttpRouteHandler rh;
151+
152+
NextHttpResponseSource() { this = rh.getParameter(0).getAPropertyRead("res") }
153+
154+
override HTTP::RouteHandler getRouteHandler() { result = rh }
155+
}
89156
}

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,18 @@ module NodeJSLib {
107107
}
108108

109109
/**
110-
* A Node.js response source, that is, the response parameter of a
110+
* A Node.js response source.
111+
*/
112+
abstract class ResponseSource extends HTTP::Servers::ResponseSource { }
113+
114+
/**
115+
* A standard Node.js response source, that is, the response parameter of a
111116
* route handler.
112117
*/
113-
private class ResponseSource extends HTTP::Servers::ResponseSource {
118+
private class StandardResponseSource extends ResponseSource {
114119
RouteHandler rh;
115120

116-
ResponseSource() { this = DataFlow::parameterNode(rh.getResponseParameter()) }
121+
StandardResponseSource() { this = DataFlow::parameterNode(rh.getResponseParameter()) }
117122

118123
/**
119124
* Gets the route handler that provides this response.
@@ -122,13 +127,18 @@ module NodeJSLib {
122127
}
123128

124129
/**
125-
* A Node.js request source, that is, the request parameter of a
130+
* A Node.js request source.
131+
*/
132+
abstract class RequestSource extends HTTP::Servers::RequestSource { }
133+
134+
/**
135+
* A standard Node.js request source, that is, the request parameter of a
126136
* route handler.
127137
*/
128-
private class RequestSource extends HTTP::Servers::RequestSource {
138+
private class StandardRequestSource extends RequestSource {
129139
RouteHandler rh;
130140

131-
RequestSource() { this = DataFlow::parameterNode(rh.getRequestParameter()) }
141+
StandardRequestSource() { this = DataFlow::parameterNode(rh.getRequestParameter()) }
132142

133143
/**
134144
* Gets the route handler that handles this request.

javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ nodes
103103
| formatting.js:7:14:7:53 | require ... , evil) |
104104
| formatting.js:7:14:7:53 | require ... , evil) |
105105
| formatting.js:7:49:7:52 | evil |
106+
| pages/Next.jsx:8:13:8:19 | req.url |
107+
| pages/Next.jsx:8:13:8:19 | req.url |
108+
| pages/Next.jsx:8:13:8:19 | req.url |
109+
| pages/Next.jsx:15:13:15:19 | req.url |
110+
| pages/Next.jsx:15:13:15:19 | req.url |
111+
| pages/Next.jsx:15:13:15:19 | req.url |
106112
| partial.js:9:25:9:25 | x |
107113
| partial.js:10:14:10:14 | x |
108114
| partial.js:10:14:10:18 | x + y |
@@ -237,6 +243,8 @@ edges
237243
| formatting.js:6:43:6:46 | evil | formatting.js:6:14:6:47 | util.fo ... , evil) |
238244
| formatting.js:7:49:7:52 | evil | formatting.js:7:14:7:53 | require ... , evil) |
239245
| formatting.js:7:49:7:52 | evil | formatting.js:7:14:7:53 | require ... , evil) |
246+
| pages/Next.jsx:8:13:8:19 | req.url | pages/Next.jsx:8:13:8:19 | req.url |
247+
| pages/Next.jsx:15:13:15:19 | req.url | pages/Next.jsx:15:13:15:19 | req.url |
240248
| partial.js:9:25:9:25 | x | partial.js:10:14:10:14 | x |
241249
| partial.js:10:14:10:14 | x | partial.js:10:14:10:18 | x + y |
242250
| partial.js:10:14:10:14 | x | partial.js:10:14:10:18 | x + y |
@@ -303,6 +311,8 @@ edges
303311
| etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value |
304312
| formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
305313
| formatting.js:7:14:7:53 | require ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
314+
| pages/Next.jsx:8:13:8:19 | req.url | pages/Next.jsx:8:13:8:19 | req.url | pages/Next.jsx:8:13:8:19 | req.url | Cross-site scripting vulnerability due to $@. | pages/Next.jsx:8:13:8:19 | req.url | user-provided value |
315+
| pages/Next.jsx:15:13:15:19 | req.url | pages/Next.jsx:15:13:15:19 | req.url | pages/Next.jsx:15:13:15:19 | req.url | Cross-site scripting vulnerability due to $@. | pages/Next.jsx:15:13:15:19 | req.url | user-provided value |
306316
| partial.js:10:14:10:18 | x + y | partial.js:13:42:13:48 | req.url | partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value |
307317
| partial.js:19:14:19:18 | x + y | partial.js:22:51:22:57 | req.url | partial.js:19:14:19:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:22:51:22:57 | req.url | user-provided value |
308318
| partial.js:28:14:28:18 | x + y | partial.js:31:47:31:53 | req.url | partial.js:28:14:28:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:31:47:31:53 | req.url | user-provided value |

javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
| ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | Cross-site scripting vulnerability due to $@. | ReflectedXssGood3.js:135:15:135:27 | req.params.id | user-provided value |
2222
| formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
2323
| formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
24+
| pages/Next.jsx:8:13:8:19 | req.url | Cross-site scripting vulnerability due to $@. | pages/Next.jsx:8:13:8:19 | req.url | user-provided value |
25+
| pages/Next.jsx:15:13:15:19 | req.url | Cross-site scripting vulnerability due to $@. | pages/Next.jsx:15:13:15:19 | req.url | user-provided value |
2426
| partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value |
2527
| partial.js:19:14:19:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:22:51:22:57 | req.url | user-provided value |
2628
| partial.js:28:14:28:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:31:47:31:53 | req.url | user-provided value |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "my-app",
3+
"version": "0.1.0",
4+
"scripts": {
5+
"dev": "next dev",
6+
"build": "next build",
7+
"start": "next start"
8+
},
9+
"dependencies": {
10+
"next": "^10.0.0",
11+
"react": "17.0.1",
12+
"react-dom": "17.0.1"
13+
}
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default function Post() {
2+
return <span />;
3+
}
4+
5+
Post.getInitialProps = async (ctx) => {
6+
const req = ctx.req;
7+
const res = ctx.res;
8+
res.end(req.url);
9+
return {}
10+
}
11+
12+
export async function getServerSideProps(ctx) {
13+
const req = ctx.req;
14+
const res = ctx.res;
15+
res.end(req.url);
16+
return {
17+
props: {}
18+
}
19+
}

0 commit comments

Comments
 (0)