Skip to content

Commit 389a3c9

Browse files
committed
JS: Port CSRF query
1 parent 16fa066 commit 389a3c9

File tree

5 files changed

+73
-91
lines changed

5 files changed

+73
-91
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ module HTTP {
9696
predicate isSafe() {
9797
this = ["GET", "HEAD", "OPTIONS", "PRI", "PROPFIND", "REPORT", "SEARCH", "TRACE"]
9898
}
99+
100+
/**
101+
* Holds if this kind of HTTP request should not generally be considered free of side effects,
102+
* such as for `POST` or `PUT` requests.
103+
*/
104+
predicate isUnsafe() { not isSafe() }
99105
}
100106

101107
/**

javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql

Lines changed: 30 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -16,59 +16,27 @@ import javascript
1616
/** Gets a property name of `req` which refers to data usually derived from cookie data. */
1717
string cookieProperty() { result = "session" or result = "cookies" or result = "user" }
1818

19-
/** Gets a data flow node that flows to the base of a reference to `cookies`, `session`, or `user`. */
20-
private DataFlow::SourceNode nodeLeadingToCookieAccess(DataFlow::TypeBackTracker t) {
21-
t.start() and
19+
/**
20+
* Holds if `handler` uses cookies.
21+
*/
22+
predicate isRouteHandlerUsingCookies(Routing::RouteHandler handler) {
2223
exists(DataFlow::PropRef value |
23-
value = result.getAPropertyRead(cookieProperty()).getAPropertyReference() and
24+
value = handler.getAnInput().ref().getAPropertyRead(cookieProperty()).getAPropertyReference() and
2425
// Ignore accesses to values that are part of a CSRF or captcha check
2526
not value.getPropertyName().regexpMatch("(?i).*(csrf|xsrf|captcha).*") and
2627
// Ignore calls like `req.session.save()`
2728
not value = any(DataFlow::InvokeNode call).getCalleeNode()
2829
)
29-
or
30-
exists(DataFlow::TypeBackTracker t2 | result = nodeLeadingToCookieAccess(t2).backtrack(t2, t))
31-
}
32-
33-
/** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */
34-
DataFlow::SourceNode nodeLeadingToCookieAccess() {
35-
result = nodeLeadingToCookieAccess(DataFlow::TypeBackTracker::end())
36-
}
37-
38-
/**
39-
* Holds if `handler` uses cookies.
40-
*/
41-
predicate isRouteHandlerUsingCookies(Express::RouteHandler handler) {
42-
DataFlow::parameterNode(handler.getRequestParameter()) = nodeLeadingToCookieAccess()
43-
}
44-
45-
/** Gets a data flow node referring to a route handler that uses cookies. */
46-
private DataFlow::SourceNode getARouteUsingCookies(DataFlow::TypeTracker t) {
47-
t.start() and
48-
isRouteHandlerUsingCookies(result)
49-
or
50-
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred | pred = getARouteUsingCookies(t2) |
51-
result = pred.track(t2, t)
52-
or
53-
t = t2 and
54-
HTTP::routeHandlerStep(pred, result)
55-
)
56-
}
57-
58-
/** Gets a data flow node referring to a route handler that uses cookies. */
59-
DataFlow::SourceNode getARouteUsingCookies() {
60-
result = getARouteUsingCookies(DataFlow::TypeTracker::end())
6130
}
6231

6332
/**
64-
* Checks if `expr` is preceded by the cookie middleware `cookie`.
33+
* Checks if `route` is preceded by the cookie middleware `cookie`.
6534
*
6635
* A router handler following after cookie parsing is assumed to depend on
6736
* cookies, and thus require CSRF protection.
6837
*/
69-
predicate hasCookieMiddleware(Express::RouteHandlerExpr expr, Express::RouteHandlerExpr cookie) {
70-
any(HTTP::CookieMiddlewareInstance i).flowsToExpr(cookie) and
71-
expr.getAMatchingAncestor() = cookie
38+
predicate hasCookieMiddleware(Routing::Node route, HTTP::CookieMiddlewareInstance cookie) {
39+
route.isGuardedBy(cookie)
7240
}
7341

7442
/**
@@ -87,22 +55,26 @@ predicate hasCookieMiddleware(Express::RouteHandlerExpr expr, Express::RouteHand
8755
* })
8856
* ```
8957
*/
90-
DataFlow::CallNode csrfMiddlewareCreation() {
58+
DataFlow::SourceNode csrfMiddlewareCreation() {
9159
exists(DataFlow::SourceNode callee | result = callee.getACall() |
9260
callee = DataFlow::moduleImport("csurf")
9361
or
9462
callee = DataFlow::moduleImport("lusca") and
95-
exists(result.getOptionArgument(0, "csrf"))
63+
exists(result.(DataFlow::CallNode).getOptionArgument(0, "csrf"))
9664
or
9765
callee = DataFlow::moduleMember("lusca", "csrf")
9866
or
9967
callee = DataFlow::moduleMember("express", "csrf")
10068
)
69+
or
70+
// Note: the 'fastify-csrf' plugin enables the 'fastify.csrfProtection' middleware to be installed.
71+
// Simply having the plugin registered is not enough, so we look for the 'csrfProtection' middleware.
72+
result = Fastify::server().getAPropertyRead("csrfProtection")
10173
}
10274

10375
/**
10476
* Gets a data flow node that flows to the base of a reference to `cookies`, `session`, or `user`,
105-
* where the references property has `csrf` or `xsrf` in its name,
77+
* where the referenced property has `csrf` or `xsrf` in its name,
10678
* and a property is either written or part of a comparison.
10779
*/
10880
private DataFlow::SourceNode nodeLeadingToCsrfWriteOrCheck(DataFlow::TypeBackTracker t) {
@@ -121,10 +93,10 @@ private DataFlow::SourceNode nodeLeadingToCsrfWriteOrCheck(DataFlow::TypeBackTra
12193
/**
12294
* Gets a route handler that sets an CSRF related cookie.
12395
*/
124-
private Express::RouteHandler getAHandlerSettingCsrfCookie() {
96+
private Routing::RouteHandler getAHandlerSettingCsrfCookie() {
12597
exists(HTTP::CookieDefinition setCookie |
12698
setCookie.getNameArgument().getStringValue().regexpMatch("(?i).*(csrf|xsrf).*") and
127-
result = setCookie.getRouteHandler()
99+
result = Routing::getRouteHandler(setCookie.getRouteHandler())
128100
)
129101
}
130102

@@ -133,65 +105,44 @@ private Express::RouteHandler getAHandlerSettingCsrfCookie() {
133105
* This is indicated either by the request parameter having a CSRF related write to a session variable.
134106
* Or by the response parameter setting a CSRF related cookie.
135107
*/
136-
predicate isCsrfProtectionRouteHandler(Express::RouteHandler handler) {
137-
DataFlow::parameterNode(handler.getRequestParameter()) =
138-
nodeLeadingToCsrfWriteOrCheck(DataFlow::TypeBackTracker::end())
108+
predicate isCsrfProtectionRouteHandler(Routing::RouteHandler handler) {
109+
handler.getAnInput() = nodeLeadingToCsrfWriteOrCheck(DataFlow::TypeBackTracker::end())
139110
or
140111
handler = getAHandlerSettingCsrfCookie()
141112
}
142113

143-
/** Gets a data flow node refering to a route handler that is protecting against CSRF. */
144-
private DataFlow::SourceNode getACsrfProtectionRouteHandler(DataFlow::TypeTracker t) {
145-
t.start() and
146-
isCsrfProtectionRouteHandler(result)
147-
or
148-
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred |
149-
pred = getACsrfProtectionRouteHandler(t2)
150-
|
151-
result = pred.track(t2, t)
152-
or
153-
t = t2 and
154-
HTTP::routeHandlerStep(pred, result)
155-
)
156-
}
157-
158114
/**
159115
* Gets an express route handler expression that is either a custom CSRF protection middleware,
160116
* or a CSRF protecting library.
161117
*/
162-
Express::RouteHandlerExpr getACsrfMiddleware() {
163-
csrfMiddlewareCreation().flowsToExpr(result)
118+
Routing::Node getACsrfMiddleware() {
119+
result = Routing::getNode(csrfMiddlewareCreation())
164120
or
165-
getACsrfProtectionRouteHandler(DataFlow::TypeTracker::end()).flowsToExpr(result)
121+
isCsrfProtectionRouteHandler(result)
166122
}
167123

168124
/**
169125
* Holds if the given route handler is protected by CSRF middleware.
170126
*/
171-
predicate hasCsrfMiddleware(Express::RouteHandlerExpr handler) {
172-
getACsrfMiddleware() = handler.getAMatchingAncestor()
127+
predicate hasCsrfMiddleware(Routing::RouteHandler handler) {
128+
handler.isGuardedByNode(getACsrfMiddleware())
173129
}
174130

175-
from
176-
Express::RouterDefinition router, Express::RouteSetup setup, Express::RouteHandlerExpr handler,
177-
Express::RouteHandlerExpr cookie
131+
from Routing::RouteSetup setup, Routing::RouteHandler handler, HTTP::CookieMiddlewareInstance cookie
178132
where
179-
router = setup.getRouter() and
180-
handler = setup.getARouteHandlerExpr() and
181133
// Require that the handler uses cookies and has cookie middleware.
182134
//
183135
// In practice, handlers that use cookies always have the cookie middleware or
184136
// the handler wouldn't work. However, if we can't find the cookie middleware, it
185137
// indicates that our middleware model is too incomplete, so in that case we
186138
// don't trust it to detect the presence of CSRF middleware either.
187-
getARouteUsingCookies().flowsToExpr(handler) and
139+
setup.getAChild*() = handler and
140+
isRouteHandlerUsingCookies(handler) and
188141
hasCookieMiddleware(handler, cookie) and
189142
// Only flag the cookie parser registered first.
190-
not hasCookieMiddleware(cookie, _) and
143+
not hasCookieMiddleware(Routing::getNode(cookie), _) and
191144
not hasCsrfMiddleware(handler) and
192-
// Only warn for the last handler in a chain.
193-
handler.isLastHandler() and
194145
// Only warn for dangerous handlers, such as for POST and PUT.
195-
not setup.getRequestMethod().isSafe()
146+
setup.getOwnHttpMethod().isUnsafe()
196147
select cookie, "This cookie middleware is serving a request handler $@ without CSRF protection.",
197-
handler, "here"
148+
setup, "here"
Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
| MissingCsrfMiddlewareBad.js:7:9:7:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:10:26:12:1 | functio ... il"];\\n} | here |
2-
| MissingCsrfMiddlewareBad.js:17:13:17:26 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:25:30:27:6 | errorCa ... \\n }) | here |
3-
| MissingCsrfMiddlewareBad.js:33:13:33:26 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:41:30:43:6 | errorCa ... \\n }) | here |
4-
| 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 |
5-
| 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 |
6-
| 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-
| 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 |
8-
| 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 |
9-
| unused_cookies.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | unused_cookies.js:8:34:13:1 | (req, r ... Ok');\\n} | here |
10-
| unused_cookies.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | unused_cookies.js:29:19:32:1 | (req, r ... Ok');\\n} | here |
11-
| unused_cookies.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | unused_cookies.js:34:22:37:1 | (req, r ... Ok');\\n} | here |
1+
| MissingCsrfMiddlewareBad.js:7:9:7:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:10:1:12:2 | app.pos ... l"];\\n}) | here |
2+
| MissingCsrfMiddlewareBad.js:17:13:17:26 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:25:5:27:7 | app.pos ... })) | here |
3+
| MissingCsrfMiddlewareBad.js:33:13:33:26 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:41:5:43:7 | app.pos ... })) | here |
4+
| MissingCsrfMiddlewareBad.js:33:13:33:26 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | MissingCsrfMiddlewareBad.js:45:5:47:7 | app.pos ... })) | here |
5+
| 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:3:45:4 | router. ... ')\\n }) | here |
6+
| csurf_example.js:18:9:18:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | csurf_example.js:31:1:34:2 | app.pos ... ed')\\n}) | here |
7+
| fastify.js:5:14:5:38 | require ... ookie') | This cookie middleware is serving a request handler $@ without CSRF protection. | fastify.js:17:1:24:2 | app.rou ... \\n }\\n}) | here |
8+
| lusca_example.js:9:9:9:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | lusca_example.js:26:1:29:2 | app.pos ... ed')\\n}) | here |
9+
| lusca_example.js:9:9:9:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | lusca_example.js:31:1:34:2 | app.pos ... ed')\\n}) | here |
10+
| tst.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | tst.js:8:1:10:2 | app.pos ... s.x;\\n}) | here |
11+
| unused_cookies.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | unused_cookies.js:8:1:13:2 | app.pos ... k');\\n}) | here |
12+
| unused_cookies.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | unused_cookies.js:29:1:32:2 | app.pos ... k');\\n}) | here |
13+
| unused_cookies.js:6:9:6:22 | cookieParser() | This cookie middleware is serving a request handler $@ without CSRF protection. | unused_cookies.js:34:1:37:2 | app.pos ... k');\\n}) | here |

javascript/ql/test/query-tests/Security/CWE-352/MissingCsrfMiddlewareGood2.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,11 @@ var passport = require('passport');
9898
app.use(cookieParser())
9999
app.use(passport.authorize({ session: true }))
100100

101-
function checkToken(req) {
101+
function checkToken(req, res, next) {
102102
if (req.headers.xsrfToken !== req.session.xsrfToken) {
103103
throw new Error("Halt and catch fire!")
104104
}
105+
next();
105106
}
106107

107108
function setCsrfToken(req, response, next) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const express = require('express')
2+
const cookieParser = require('cookie-parser')
3+
const csrf = require('csurf')
4+
5+
const app = express()
6+
app.use(cookieParser())
7+
8+
app.post('/unsafe', (req, res) => { // NOT OK
9+
req.cookies.x;
10+
});
11+
12+
function middlewares() {
13+
return express.Router()
14+
.use(csrf({ cookie: true}))
15+
.use('/', express.bodyParser());
16+
}
17+
18+
app.use(middlewares());
19+
20+
app.post('/safe', (req, res) => { // OK
21+
req.cookies.x;
22+
});

0 commit comments

Comments
 (0)