Skip to content

Commit 4c5ecb4

Browse files
authored
Merge pull request #4478 from erik-krogh/homegrownCsrf
Approved by asgerf
2 parents 502faa7 + ce95676 commit 4c5ecb4

File tree

4 files changed

+160
-3
lines changed

4 files changed

+160
-3
lines changed

change-notes/1.26/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
| Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | More results | This query now detects more unsafe uses of nested option properties. |
5353
| Client-side URL redirect (`js/client-side-unvalidated-url-redirection`) | More results | This query now recognizes some unsafe uses of `importScripts()` inside WebWorkers. |
5454
| Missing CSRF middleware (`js/missing-token-validation`) | More results | This query now recognizes writes to cookie and session variables as potentially vulnerable to CSRF attacks. |
55+
| Missing CSRF middleware (`js/missing-token-validation`) | Fewer results | This query now recognizes more ways of protecting against CSRF attacks. |
5556

5657

5758
## Changes to libraries

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

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

18-
/** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */
18+
/** Gets a data flow node that flows to the base of a reference to `cookies`, `session`, or `user`. */
1919
private DataFlow::SourceNode nodeLeadingToCookieAccess(DataFlow::TypeBackTracker t) {
2020
t.start() and
2121
exists(DataFlow::PropRef value |
@@ -94,14 +94,78 @@ DataFlow::CallNode csrfMiddlewareCreation() {
9494
exists(result.getOptionArgument(0, "csrf"))
9595
or
9696
callee = DataFlow::moduleMember("lusca", "csrf")
97+
or
98+
callee = DataFlow::moduleMember("express", "csrf")
99+
)
100+
}
101+
102+
/**
103+
* Gets a data flow node that flows to the base of a write to `cookies`, `session`, or `user`,
104+
* where the written property has `csrf` or `xsrf` in its name.
105+
*/
106+
private DataFlow::SourceNode nodeLeadingToCsrfWrite(DataFlow::TypeBackTracker t) {
107+
t.start() and
108+
result
109+
.getAPropertyRead(cookieProperty())
110+
.getAPropertyWrite()
111+
.getPropertyName()
112+
.regexpMatch("(?i).*(csrf|xsrf).*")
113+
or
114+
exists(DataFlow::TypeBackTracker t2 | result = nodeLeadingToCsrfWrite(t2).backtrack(t2, t))
115+
}
116+
117+
/**
118+
* Gets a route handler that sets an CSRF related cookie.
119+
*/
120+
private Express::RouteHandler getAHandlerSettingCsrfCookie() {
121+
exists(HTTP::CookieDefinition setCookie |
122+
setCookie.getNameArgument().getStringValue().regexpMatch("(?i).*(csrf|xsrf).*") and
123+
result = setCookie.getRouteHandler()
124+
)
125+
}
126+
127+
/**
128+
* Holds if `handler` is protecting from CSRF.
129+
* This is indicated either by the request parameter having a CSRF related write to a session variable.
130+
* Or by the response parameter setting a CSRF related cookie.
131+
*/
132+
predicate isCsrfProtectionRouteHandler(Express::RouteHandler handler) {
133+
DataFlow::parameterNode(handler.getRequestParameter()) =
134+
nodeLeadingToCsrfWrite(DataFlow::TypeBackTracker::end())
135+
or
136+
handler = getAHandlerSettingCsrfCookie()
137+
}
138+
139+
/** Gets a data flow node refering to a route handler that is protecting against CSRF. */
140+
private DataFlow::SourceNode getACsrfProtectionRouteHandler(DataFlow::TypeTracker t) {
141+
t.start() and
142+
isCsrfProtectionRouteHandler(result)
143+
or
144+
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred |
145+
pred = getACsrfProtectionRouteHandler(t2)
146+
|
147+
result = pred.track(t2, t)
148+
or
149+
t = t2 and
150+
HTTP::routeHandlerStep(pred, result)
97151
)
98152
}
99153

154+
/**
155+
* Gets an express route handler expression that is either a custom CSRF protection middleware,
156+
* or a CSRF protecting library.
157+
*/
158+
Express::RouteHandlerExpr getACsrfMiddleware() {
159+
csrfMiddlewareCreation().flowsToExpr(result)
160+
or
161+
getACsrfProtectionRouteHandler(DataFlow::TypeTracker::end()).flowsToExpr(result)
162+
}
163+
100164
/**
101165
* Holds if the given route handler is protected by CSRF middleware.
102166
*/
103167
predicate hasCsrfMiddleware(Express::RouteHandlerExpr handler) {
104-
csrfMiddlewareCreation().flowsToExpr(handler.getAMatchingAncestor())
168+
getACsrfMiddleware() = handler.getAMatchingAncestor()
105169
}
106170

107171
from

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,7 @@ module Express {
718718
/**
719719
* An invocation of the `cookie` method on an HTTP response object.
720720
*/
721-
private class SetCookie extends HTTP::CookieDefinition, MethodCallExpr {
721+
class SetCookie extends HTTP::CookieDefinition, MethodCallExpr {
722722
RouteHandler rh;
723723

724724
SetCookie() { calls(rh.getAResponseExpr(), "cookie") }
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
var express = require('express');
2+
var cookieParser = require('cookie-parser');
3+
var passport = require('passport');
4+
5+
(function () {
6+
7+
var app = express()
8+
9+
app.use(cookieParser())
10+
app.use(passport.authorize({ session: true }))
11+
12+
function getCsrfToken(request) {
13+
return request.headers['x-xsrf-token'];
14+
}
15+
16+
function setCsrfToken(request, response, next) {
17+
response.cookie('XSRF-TOKEN', request.csrfToken());
18+
next();
19+
}
20+
21+
const csrf = {
22+
getCsrfToken: getCsrfToken,
23+
setCsrfToken: setCsrfToken
24+
};
25+
26+
app.use(express.csrf({ value: csrf.getCsrfToken }));
27+
app.use(csrf.setCsrfToken);
28+
29+
app.post('/changeEmail', function (req, res) {
30+
let newEmail = req.cookies["newEmail"];
31+
})
32+
});
33+
34+
35+
36+
(function () {
37+
var app = express()
38+
39+
app.use(cookieParser())
40+
app.use(passport.authorize({ session: true }))
41+
42+
var crypto = require('crypto');
43+
44+
var generateToken = function (len) {
45+
return crypto.randomBytes(Math.ceil(len * 3 / 4))
46+
.toString('base64')
47+
.slice(0, len);
48+
};
49+
function defaultValue(req) {
50+
return (req.body && req.body._csrf)
51+
|| (req.query && req.query._csrf)
52+
|| (req.headers['x-csrf-token']);
53+
}
54+
var checkToken = function (req, res, next) {
55+
var token = req.session._csrf || (req.session._csrf = generateToken(24));
56+
if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next();
57+
var val = defaultValue(req);
58+
if (val != token) return next(function () {
59+
res.send({ auth: false });
60+
});
61+
next();
62+
}
63+
const csrf = {
64+
check: checkToken
65+
};
66+
67+
app.use(express.cookieParser());
68+
app.use(express.session({ secret: 'thomasdavislovessalmon' }));
69+
app.use(express.bodyParser());
70+
app.use(csrf.check);
71+
72+
app.post('/changeEmail', function (req, res) {
73+
let newEmail = req.cookies["newEmail"];
74+
})
75+
});
76+
77+
(function () {
78+
79+
var app = express()
80+
81+
app.use(cookieParser())
82+
app.use(passport.authorize({ session: true }))
83+
84+
// Assume token is being set somewhere
85+
app.use(express.csrf({ value: function (request) {
86+
return request.headers['x-xsrf-token'];
87+
}}));
88+
89+
app.post('/changeEmail', function (req, res) {
90+
let newEmail = req.cookies["newEmail"];
91+
})
92+
});

0 commit comments

Comments
 (0)