Skip to content

Commit 140a70f

Browse files
authored
Merge pull request #7029 from erik-krogh/cwe384
JS: add js/session-fixation query
2 parents 0bf055f + ab5d945 commit 140a70f

File tree

8 files changed

+193
-0
lines changed

8 files changed

+193
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* The `js/session-fixation` query has been added. It highlights servers that reuse a session after a user has logged in.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
Reusing a session could allow an attacker to gain unauthorized access to another account. Always
8+
ensure that, when a user logs in or out, the current session is abandoned so that a new
9+
session may be started.
10+
</p>
11+
</overview>
12+
13+
<recommendation>
14+
15+
<p>
16+
Always use <code>req.session.regenerate(...);</code> to start a new session when
17+
a user logs in or out.
18+
</p>
19+
20+
</recommendation>
21+
<example>
22+
23+
<p>
24+
The following example shows the previous session being used after authentication.
25+
This would allow a previous user to use the new user's account.
26+
</p>
27+
28+
<sample src="examples/SessionFixation.js" />
29+
30+
<p>
31+
This code example solves the problem by not reusing the session, and instead calling <code>req.session.regenerate()</code>
32+
to ensure that the session is not reused.
33+
</p>
34+
<sample src="examples/SessionFixationFixed.js" />
35+
36+
</example>
37+
<references>
38+
<li>
39+
OWASP: <a href="https://www.owasp.org/index.php/Session_fixation">Session fixation</a>
40+
</li>
41+
<li>
42+
Stack Overflow: <a href="https://stackoverflow.com/questions/22209354/creating-a-new-session-after-authentication-with-passport/30468384#30468384">Creating a new session after authentication with Passport</a>
43+
</li>
44+
<li>
45+
jscrambler.com: <a href="https://blog.jscrambler.com/best-practices-for-secure-session-management-in-node">Best practices for secure session management in Node</a>
46+
</li>
47+
</references>
48+
</qhelp>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @name Failure to abandon session
3+
* @description Reusing an existing session as a different user could allow
4+
* an attacker to access someone else's account by using
5+
* their session.
6+
* @kind problem
7+
* @problem.severity warning
8+
* @security-severity 5
9+
* @precision medium
10+
* @id js/session-fixation
11+
* @tags security
12+
* external/cwe/cwe-384
13+
*/
14+
15+
import javascript
16+
17+
/**
18+
* Holds if `setup` uses express-session (or similar) to log in a user.
19+
*/
20+
pragma[inline]
21+
predicate isLoginSetup(Express::RouteSetup setup) {
22+
// either some path that contains "login" with a write to `req.session`
23+
setup.getPath().matches("%login%") and
24+
exists(
25+
setup
26+
.getARouteHandler()
27+
.(Express::RouteHandler)
28+
.getARequestSource()
29+
.ref()
30+
.getAPropertyRead("session")
31+
.getAPropertyWrite()
32+
)
33+
or
34+
// or an authentication method is used (e.g. `passport.authenticate`)
35+
setup.getARouteHandler().(DataFlow::CallNode).getCalleeName() = "authenticate"
36+
}
37+
38+
/**
39+
* Holds if `handler` regenerates its session using `req.session.regenerate`.
40+
*/
41+
pragma[inline]
42+
predicate regeneratesSession(Express::RouteSetup setup) {
43+
exists(
44+
setup
45+
.getARouteHandler()
46+
.(Express::RouteHandler)
47+
.getARequestSource()
48+
.ref()
49+
.getAPropertyRead("session")
50+
.getAPropertyRead("regenerate")
51+
)
52+
}
53+
54+
from Express::RouteSetup setup
55+
where
56+
isLoginSetup(setup) and
57+
not regeneratesSession(setup)
58+
select setup, "Route handler does not invalidate session following login"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const express = require('express');
2+
const session = require('express-session');
3+
var bodyParser = require('body-parser')
4+
const app = express();
5+
app.use(bodyParser.urlencoded({ extended: false }))
6+
app.use(session({
7+
secret: 'keyboard cat'
8+
}));
9+
10+
app.post('/login', function (req, res) {
11+
// Check that username password matches
12+
if (req.body.username === 'admin' && req.body.password === 'admin') {
13+
req.session.authenticated = true;
14+
res.redirect('/');
15+
} else {
16+
res.redirect('/login');
17+
}
18+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const express = require('express');
2+
const session = require('express-session');
3+
var bodyParser = require('body-parser')
4+
const app = express();
5+
app.use(bodyParser.urlencoded({ extended: false }))
6+
app.use(session({
7+
secret: 'keyboard cat'
8+
}));
9+
10+
app.post('/login', function (req, res) {
11+
// Check that username password matches
12+
if (req.body.username === 'admin' && req.body.password === 'admin') {
13+
req.session.regenerate(function (err) {
14+
if (err) {
15+
res.send('Error');
16+
} else {
17+
req.session.authenticated = true;
18+
res.redirect('/');
19+
}
20+
});
21+
} else {
22+
res.redirect('/login');
23+
}
24+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
| tst.js:9:1:14:2 | app.get ... n');\\n}) | Route handler does not invalidate session following login |
2+
| tst.js:27:1:29:2 | app.get ... n');\\n}) | Route handler does not invalidate session following login |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE-384/SessionFixation.ql
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const express = require('express');
2+
const session = require('express-session');
3+
const passport = require('passport');
4+
const app = express();
5+
app.use(session({
6+
secret: 'keyboard cat'
7+
}));
8+
// handle login
9+
app.get('/login', function (req, res) { // NOT OK - no regenerate
10+
req.session.user = {
11+
userId: something
12+
};
13+
res.send('logged in');
14+
});
15+
16+
// with regenerate
17+
app.get('/login2', function (req, res) { // OK
18+
req.session.regenerate(function (err) {
19+
req.session.user = {
20+
userId: something
21+
};
22+
res.send('logged in');
23+
});
24+
});
25+
26+
// using passport
27+
app.get('/passport', passport.authenticate('local'), function (req, res) { // NOT OK - no regenerate
28+
res.send('logged in');
29+
});
30+
31+
// with regenerate, still using passport
32+
app.get('/passport2', passport.authenticate('local'), function (req, res) { // OK
33+
var passport = req._passport.instance;
34+
req.session.regenerate(function(err, done, user) {
35+
req.session[passport._key] = {};
36+
req._passport.instance = passport;
37+
req._passport.session = req.session[passport._key];
38+
res.send('logged in');
39+
});
40+
});

0 commit comments

Comments
 (0)