原文:levelup.gitconnected.com/what-is-ses...
标题:What is Session Fixation and How to Prevent it in Node.js
作者:Poorshad Shaddel
通过会话固定Session Fixation ,攻击者可以劫持有效的用户会话,因此了解此漏洞并防范它绝对重要。
在深入研究之前,我们需要知道Session是什么以及会话身份验证Session Authentication的工作原理。如果你已经熟悉这一点,则可以跳到该部分:什么是会话固定_及_如何防止会话固定
什么是会话?
众所周知,HTTP请求是无状态的,这意味着当我们发送登录请求时,我们有一个有效的用户名和密码,没有默认机制来知道我与发送下一个请求的是同一个人。为了解决这个问题,我们需要使请求是有状态的,常见的方法,如 Cookie 、隐藏表单字段 、URL 参数 、HTML5 Web 存储 、JWT 和会话。在本文中,我们将重点介绍Session。
Session 是存储在服务器上的数据。每个客户端都有一个与服务器上的此数据关联 的唯一标识符。客户端必须在每个请求上发送此唯一标识符,以便我们知道谁在发送此请求。此标识符可以在 Cookie 或 URL 参数中携带。
在 expressjs 应用程序中显示会话和标识符 (sessionId
) 的简化示例:
js
const app = require('express')();
const session = require('express-session');
app.use(require('cookie-parser')());
app.use(require('body-parser').json());
app.use(session({
secret: 'secret',
cookie: { maxAge: 60000 },
name: 'sessionId'
}));
app.get('/', (req, res) => {
res.send('ping');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
当第一次发送请求时,express-session
中间件会创建一个新的唯一标识符,并将其设置为 cookie,同时将其存储在某个地方(在本例中为内存,但我们也可以传递给我们自定义的存储系统)。 在会话中间件的选项中,我们使用 sessionId
作为存储此唯一标识符的密钥的名称。现在,如果我们发送一个请求,我们会看到如下内容:
浏览器现在设置此 cookie 并自动存储以备进一步请求。如果我们发送一个包含有效会话的请求(该会话存在于我们的会话存储中 - 在我们的例子中是内存),我们不会在响应中返回 Set-Cookie
标头:
当用户登录时,我们可以将用户信息存储在序列化的 cookie中,也可以将其存储在数据库中并将数据与 sessionId
关联,以映射至我们的数据库:
js
const db = new Map();
app.get('/me', (req, res) => {
const user = db.get(req.sessionID);
res.json({ mySessionId: req.sessionID, me: user ? user : 'anonymous' });
});
const users = [{ name: 'bob', age: 19 }, { name: 'joe', age: 20 }];
app.post('/login', (req, res) => {
const { name } = req.body;
const user = users.find(u => u.name === name);
if (user) {
db.set(req.sessionID, user);
res.send('ok');
} else {
res.send('try again');
}
});
如果我们登录,然后使用 cookie 向 /me
发送另一个请求,我们会得到以下结果:
这是对为什么我们必须使用session以及如何做到这一点的简化总结。
攻击者能否创建有效的会话 ID?
在这种情况下,我们使用的是 express-session
。我们将一个密钥传递给了会话中间件。此密钥用于签署我们 cookie 的值。它只是意味着我们确定是我们生成了 sessionId
。因此,只要你向客户端发送签名值,就不可能。
session示例:
sessionId=s%3AL6j4T8hBwMk1ulJqGoisZbAxUOkOuQqP.x5UxPQEtKrj3sWrIy6S01CQRjAtp4biVs4H2zgqmSs
第一部分:s%3A
的意思是:s:
这是一个前缀,表示我们的 cookie 会话已签名!
第二部分:L6j4T8hBwMk1ulJqGoisZbAxUOkOuQqP
这是我们的 sessionId
,我们在数据库中使用它来关联数据。
第三部分:这是第三部分:x5UxPQEtKrj3sWrIy6S01CQRjAtp4biVs4H2zgqmSs
这是签名部分。我们使用我们的秘密生成了此文本,因此我们可以确定此cookie是由我们生成的。
我们可以简单地重新生成这个符号并检查它是否有效:
js
const crypto = require('crypto');
const secret = 'secret';
const sessionId = 'L6j4T8hBwMk1ulJqGoisZbAxUOkOuQqP';
const hmac = crypto.createHmac('sha256', secret);
hmac.update(sessionId);
const signature = hmac.digest('base64').replace(/\=+$/, '');
console.log(signature); // x5UxPQEtKrj3sWrIy6S01CQRjAtp4biVs4H2zgqmSs
这就是 express-session 检查它的方式。
什么是会话固定 Session Fixation?
在会话固定攻击中,攻击者劫持有效的用户会话。我们说我们签署cookie是为了确保没有人可以劫持其他用户的有效会话。但是,如果攻击者有自己的有效会话并尝试将其与其他用户关联,该怎么办?在这种情况下,他可以代表受害者采取行动。
当我们没有在登录等操作上生成新的 sessionIds(唯一标识符)时,就会出现问题。
攻击者如何做到这一点?
其中一种情况是攻击者对计算机具有物理访问权限。作为攻击者,我去大学,选择其中一台共享计算机,然后在 vulnerablewebsite.com
上登录我的帐户,然后不进行注销(这通常会破坏服务器存储中的会话),我在 vulnerablewebsite.com
上留下一个打开的登录页面,在此之前,我必须复制我的有效sessionId
。现在受害者正在使用这台计算机,如果受害者登录,攻击者 sessionId
将与受害者的帐户相关联。听起来很复杂?一点也不,让我们看看实际情况:
让我们使用我们的第一个用户 Bob(攻击者)登录:
现在,浏览器为本网站设置了此cookie。这意味着,如果其他人尝试发送登录请求,express-session
不会生成新的 sessionId
,而是**覆盖现有的 *sessionId
。
假设 Joe (受害者)决定使用这台共享计算机,也会发送 Bob 的 cookie 和有效会话:
我们没有收到新的会话或cookie!
奇迹发生了,现在 Bob 的 sessionId
与 Joe 的用户相关联。因此,如果攻击者 (Bob) 向 /me
发送请求,他将返回 Joe 的数据:
我们能够通过使用 Bob 的会话来获取 Joe Data。在此示例中,攻击者具有物理访问权限,但如果存在其他一些漏洞(例如 XSS),则可以在没有物理访问权限的情况下执行此操作。
某些网站在请求中将 sessionId
作为 URL 参数传递。在这种情况下,如果攻击者在 URL 参数上提供带有其 sessionId
的登录页面链接,则有可能被利用。
在此堆栈交换问题中阅读有关此方法的安全挑战的更多信息。
如何防止会话固定?
登录时生成新会话!
主要解决方案非常简单,通过这样做,始终可以确定不会发生此会话覆盖!
让我们更改代码:
js
app.post('/login', (req, res) => {
const { name } = req.body;
req.session.regenerate(err => {
if (err) {
res.send('error');
} else {
const user = users.find(u => u.name === name);
if (user) {
db.set(req.sessionID, user);
res.send('ok');
} else {
res.send('try again');
}
}
});
});
我们可以使用regenerate
函数,以便在每次有人想要登录时分配一个新会话。是否传递会话 cookie 不再重要,它将生成一个新的会话 ID 并将其发送到 Set-Cookie
标头中的客户端。
仅使用 HTTP Only 的 Cookie
当你使用 HTTP Only
时,这意味着只有服务器可以通过 Set-Cookie
标头设置 cookie,而客户端(浏览器 JavaScript)无法更改它。因此,即使你的应用存在 XSS 漏洞,攻击者也无法更改 sessionId
(cookie)。
防范 XSS
会话固定可以与 XSS 攻击结合使用以更有效,因此如果你担心会话固定,那么认真对待 XSS 攻击确实是有意义的。
合理的会话到期时间
会话过期时间应符合应用程序的特定要求,如果你更关心安全性,则应更短,反之亦然。
正确的注销实现方案
注销时,你必须正确销毁现有会话及其与任何数据的关联。否则,这些会话可以在注销后使用。(从客户端浏览器中删除cookie是不够的!
Passportjs 是否容易受到会话固定的影响?
是的,在 0.6.0 之前的版本中,问题就在那里,Passport 维护者认为会话重新生成应该在应用程序端完成,但一段时间后他们意识到问题的重要性,并在 0.6.0 版本中修复了它。如果你对此修复程序的详细信息感兴趣,可以在此处阅读所有详细信息。
结论
如果用其他用户数据覆盖现有 sessionId,则可能会发生会话固定。解决方案非常简单,每次有人登录时都会生成一个新会话,使用仅限 HTTP 的 cookie、适当的过期时间、正确的注销实现。