Session绑定后怎么还是被CSRF攻击了?我的实现有问题吗?

IT人慧娟 阅读 29

我在做用户登录时把sessionID存到cookie里,并在服务端把session和用户ID做了绑定。但测试时发现,攻击页面通过已登录的浏览器发起请求,服务端居然能拿到正确的用户信息。我试过设置cookie的SameSite=Strict和HttpOnly,但好像没用…

服务端中间件代码是这样校验的:if (session.userId === req.user.id),但好像没检查Referer。是不是应该同时验证Referer和Origin头?或者Session绑定的时机不对?


// 服务端中间件片段
app.use((req, res, next) => {
  const sessionUserId = req.session.userId;
  const currentUserId = req.user.id;
  if (sessionUserId !== currentUserId) {
    return res.status(401).send('Session invalid');
  }
  next();
});

现在遇到的情况是,当用户访问恶意网站时,攻击页面发送的POST请求居然能通过验证,服务端没有检测到Referer异常。是不是需要在绑定session时强制关联Referer信息?或者我的验证逻辑完全错了?

我来解答 赞 6 收藏
二维码
手机扫码查看
2 条解答
新艳 Dev
你遇到的问题其实挺常见的,CSRF攻击的核心是利用用户的已登录状态发送恶意请求,而你的代码目前只校验了session和用户ID的绑定关系,缺少对请求来源的验证。这里有几个点可以优化:

首先,SameSite=Strict确实能防一部分CSRF,但它并不是所有浏览器都完全支持,尤其是老版本浏览器可能直接忽略这个属性。所以不能完全依赖它。

其次,Referer和Origin的校验是有必要的,但要注意它们并不是万能的。有些情况下,比如HTTPS跳HTTP,Referer可能会被浏览器丢弃。你可以试试在中间件里加个逻辑,判断请求头里的Referer或Origin是否匹配你的站点域名。代码可以这样写:

app.use((req, res, next) => {
const sessionUserId = req.session.userId;
const currentUserId = req.user.id;

// 校验session和用户ID绑定
if (sessionUserId !== currentUserId) {
return res.status(401).send('Session invalid');
}

// 校验Referer或Origin
const referer = req.get('Referer');
const origin = req.get('Origin');
const allowedHost = 'yourdomain.com'; // 替换为你的实际域名

if (
(!referer || !referer.includes(allowedHost)) &&
(!origin || !origin.includes(allowedHost))
) {
return res.status(403).send('Invalid request source');
}

next();
});


另外,更推荐的做法是使用CSRF Token机制。简单来说,服务端生成一个随机的CSRF Token,存到用户的session里,并在页面渲染时把这个Token嵌入到表单或者HTTP头中。每次收到POST请求时,服务端再校验这个Token是否匹配。这样即使攻击者伪造了请求,他也拿不到正确的Token。以下是一个简单的实现思路:

1. 用户访问页面时,服务端生成一个CSRF Token,存到session里,同时把它渲染到前端页面。
2. 前端发起POST请求时,把Token放在请求头或者表单参数里。
3. 服务端收到请求后,对比请求里的Token和session里的Token是否一致。

示例代码如下:

// 生成CSRF Token并存入session
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = generateRandomToken(); // 自定义函数生成随机Token
}
next();
});

// 校验CSRF Token
app.post('/some-endpoint', (req, res) => {
const csrfTokenFromRequest = req.body.csrfToken || req.get('X-CSRF-Token');
if (csrfTokenFromRequest !== req.session.csrfToken) {
return res.status(403).send('CSRF token validation failed');
}
// 正常处理请求
});


总结一下,Referer和Origin的校验可以加,但不要完全依赖它们。最好的办法是引入CSRF Token机制,这才是防御CSRF攻击的标准做法。希望这些建议能帮你解决问题!
点赞 1
2026-02-17 20:11
宇硕 Dev
你这个问题其实很常见,很多人以为Session绑定用户ID就万事大吉了,但其实CSRF攻击并不关心你有没有绑定Session,它只是借已登录的浏览器发请求而已。

你现在的校验逻辑是看session.userId和req.user.id是否一致,这没问题,但它并不能防御CSRF。因为你并没有验证请求的来源是否可信。

### 为什么SameSite=Strict没生效?
你设置的是Cookie的SameSite=Strict,这个属性确实能防CSRF,但它的限制是:**只在用户主动跳转时才允许发送Cookie**,例如点击链接或直接在浏览器地址栏输入。但如果你的攻击页面是通过JavaScript发起POST请求,只要不是导航行为,SameSite=Strict不会阻止请求带上Cookie(尤其是当你用的是fetchXMLHttpRequest)。

如果你希望限制AJAX请求来源,那就不能只靠SameSite。

---

### 为什么没验证Origin和Referer就会出问题?

你已经意识到这个问题了,没错。**CSRF防御的核心就在于验证请求来源**,也就是检查:

- 请求头中有没有 Origin
- 请求头中有没有 Referer
- 它们的值是否在你信任的白名单中

你当前中间件没有做这部分校验,所以攻击页面发来的请求,只要用户已登录,就能成功拿到用户信息。

---

### 建议改进方式

1. **中间件里加来源检查**

你可以这样改:

app.use((req, res, next) => {
const allowedOrigin = req.get('Origin') || req.get('Referer');
if (!allowedOrigin || !allowedOrigin.startsWith('https://yourdomain.com')) {
return res.status(403).send('Forbidden');
}

const sessionUserId = req.session.userId;
const currentUserId = req.user.id;
if (sessionUserId !== currentUserId) {
return res.status(401).send('Session invalid');
}

next();
});


2. **或者使用CSRF Token机制**

更标准的做法是服务端生成一个随机Token,前端每次请求都要带这个Token(比如放在Header里),服务端验证它的有效性。这比单纯校验Origin更安全。

你可以用像 [csurf](https://www.npmjs.com/package/csurf) 这样的中间件:

const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.use(csrfProtection);


然后前端需要从Cookie或响应头里拿到这个Token,并在请求Header中带上它。

---

### 小结一下

- Session绑定用户ID是基本操作,但不能防CSRF
- SameSite=Strict有局限,AJAX请求仍可能带Cookie
- 要防御CSRF,必须验证来源(Origin/Referer),或者使用CSRF Token
- 现在推荐用CSRF Token机制,比手动验证来源更标准、更安全

建议你改成使用CSRF Token,这样能覆盖各种情况,也符合主流框架的做法。
点赞 13
2026-02-03 21:17