「译」 什么是会话固定「Session Fixation」以及如何在 Node.js 程序中进行预防

原文: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 时,这意味着只有服务器可以通过 Set-Cookie 标头设置 cookie,而客户端(浏览器 JavaScript)无法更改它。因此,即使你的应用存在 XSS 漏洞,攻击者也无法更改 sessionId (cookie)。

防范 XSS

会话固定可以与 XSS 攻击结合使用以更有效,因此如果你担心会话固定,那么认真对待 XSS 攻击确实是有意义的。

合理的会话到期时间

会话过期时间应符合应用程序的特定要求,如果你更关心安全性,则应更短,反之亦然。

正确的注销实现方案

注销时,你必须正确销毁现有会话及其与任何数据的关联。否则,这些会话可以在注销后使用。(从客户端浏览器中删除cookie是不够的!

Passportjs 是否容易受到会话固定的影响?

是的,在 0.6.0 之前的版本中,问题就在那里,Passport 维护者认为会话重新生成应该在应用程序端完成,但一段时间后他们意识到问题的重要性,并在 0.6.0 版本中修复了它。如果你对此修复程序的详细信息感兴趣,可以在此处阅读所有详细信息。

结论

如果用其他用户数据覆盖现有 sessionId,则可能会发生会话固定。解决方案非常简单,每次有人登录时都会生成一个新会话,使用仅限 HTTP 的 cookie、适当的过期时间、正确的注销实现。

References

owasp.org/www-communi...

developer.mozilla.org/en-US/docs/...

相关推荐
sunny_3 小时前
面试踩大坑!同一段 Node.js 代码,CJS 和 ESM 的执行顺序居然是反的?!99% 的人都答错了
前端·面试·node.js
叶落阁主7 小时前
Tailscale 完全指南:从入门到私有 DERP 部署
运维·安全·远程工作
Qinana14 小时前
150行代码搞定私有知识库!Node.js + LangChain 打造最小化 RAG 系统全流程
人工智能·程序员·node.js
一次旅行15 小时前
npm-error code 128问题解决方法
node.js
前端付豪1 天前
Nest 项目小实践之图书展示和搜索
前端·node.js·nestjs
无责任此方_修行中2 天前
如何利用 pnpm 的安全控制功能防御 npm 供应链攻击
javascript·npm·node.js
允许部分打工人先富起来2 天前
在node项目中执行python脚本
前端·python·node.js
用户962377954482 天前
DVWA 靶场实验报告 (High Level)
安全
None3212 天前
【NestJs】基于Redlock装饰器分布式锁设计与实现
后端·node.js
数据智能老司机2 天前
用于进攻性网络安全的智能体 AI——在 n8n 中构建你的第一个 AI 工作流
人工智能·安全·agent