CSRF攻击:陌生链接不要随便点|浏览器篇
# CSRF攻击:陌生链接不要随便点
30 秒速记
CSRF利用用户已经建立的登录状态,诱导浏览器向受信任站点发起并非用户真实意图的操作请求。- 攻击链成立的关键是浏览器中仍保存目标站点的
Cookie、Session等认证状态,恶意页面借此让请求以受害者身份被处理。 - 案例中的恶意页面调用 Gmail 的
HTTP设置接口创建邮件转发过滤器,随后攻击者利用获得的邮件完成域名账户密码重置和转移。 CSRF的直接目标是借用用户身份执行操作;XSS则是在页面中注入恶意脚本并窃取或操纵页面数据,两者攻击入口不同。- 点击陌生链接只是触发方式之一;真正的风险来自目标接口接受了携带既有登录状态、但违背用户意图的请求。
CSRF 是攻击者借用用户已有的登录状态,诱导浏览器向受信任站点提交违背用户真实意图的操作请求。 攻击者通常不需要看到登录 Cookie,只要浏览器按规则自动携带它,而服务端又没有验证请求意图,敏感操作就可能被当成用户本人发起。案例中恶意页面创建了 Gmail 邮件转发规则,攻击者再利用邮件完成密码重置和域名转移。陌生链接只是触发方式之一,防护重点仍是 CSRF Token、请求来源校验和合理的 SameSite 配置。
中我们讲到了 XSS 攻击,XSS 的攻击方式是黑客往用户的页面中注入恶意脚本,然后再通过恶意脚本将用户页面的数据上传到黑客的服务器上,最后黑客再利用这些数据进行一些恶意操作。XSS 攻击能够带来很大的破坏性,不过另外一种类型的攻击也不容忽视,它就是我们今天要聊的 CSRF 攻击。
相信你经常能听到的一句话:“别点那个链接,小心有病毒!”点击一个链接怎么就能染上病毒了呢?
我们结合一个真实的关于 CSRF 攻击的典型案例来分析下,在 2007 年的某一天,David 无意间打开了 Gmail 邮箱中的一份邮件,并点击了该邮件中的一个链接。过了几天,David 就发现他的域名被盗了。不过几经周折,David 还是要回了他的域名,也弄清楚了他的域名之所以被盗,就是因为无意间点击的那个链接。
那 David 的域名是怎么被盗的呢?
我们结合下图来分析下 David 域名的被盗流程:

- 首先 David 发起登录 Gmail 邮箱请求,然后 Gmail 服务器返回一些登录状态给 David 的浏览器,这些信息包括了 Cookie、Session 等,这样在 David 的浏览器中,Gmail 邮箱就处于登录状态了。
- 接着黑客通过各种手段引诱 David 去打开他的链接,比如 hacker.com,然后在 hacker.com 页面中,黑客编写好了一个邮件过滤器,并通过 Gmail 提供的 HTTP 设置接口设置好了新的邮件过滤功能,该过滤器会将 David 所有的邮件都转发到黑客的邮箱中。
- 最后的事情就很简单了,因为有了 David 的邮件内容,所以黑客就可以去域名服务商那边重置 David 域名账户的密码,重置好密码之后,就可以将其转出到黑客的账户了。
以上就是 David 的域名被盗的完整过程,其中前两步就是我们今天要聊的 CSRF 攻击。David 在要回了他的域名之后,也将整个攻击过程分享到他的站点上了,如果你感兴趣的话,可以参考
原理拆解: 攻击者通常看不到受害者的登录 Cookie,但这并不妨碍浏览器在符合 Cookie 发送规则时自动携带它。恶意页面只负责构造对受信任站点的请求,目标服务器看到有效会话后便把请求识别成受害者;若敏感接口没有额外验证请求意图,修改邮箱转发规则、变更绑定信息等操作就会成功。后续的密码重置和域名转移属于业务后果,并不是 CSRF 请求本身。
最小验证: 输入是一台本地服务和浏览器对 /transfer 的请求,要验证仅有会话 Cookie 时会被拒绝,同时具备会话与 CSRF Token 才能修改状态。将代码保存为 csrf-demo.js,用 node csrf-demo.js 启动后访问 http://localhost:3000/。
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Set-Cookie': 'sid=alice; HttpOnly; SameSite=Lax'
});
return res.end(`<form method="POST" action="/transfer">
<input name="csrf" value="token-123">
<button>提交转账</button>
</form>`);
}
if (req.method === 'POST' && req.url === '/transfer') {
let body = '';
req.on('data', chunk => body += chunk);
return req.on('end', () => {
const loggedIn = (req.headers.cookie || '').includes('sid=alice');
const validToken = new URLSearchParams(body).get('csrf') === 'token-123';
res.writeHead(loggedIn && validToken ? 200 : 403);
res.end(loggedIn && validToken ? 'transfer accepted' : 'forbidden');
});
}
res.writeHead(404).end();
}).listen(3000, () => console.log('http://localhost:3000/'));
表单正常提交时预期得到 transfer accepted;删除隐藏值或用不含令牌的表单提交时得到 forbidden。示例令牌为固定值,仅用于展示校验链路,生产系统应生成不可预测且绑定会话的令牌。
边界与排查: SameSite 能降低跨站携带 Cookie 的概率,但旧客户端、错误属性和特定跳转链路可能削弱保护,不能代替服务端校验。XSS 若已能读取页面或代替用户调用接口,也可能绕过页面中的令牌防线。排查时应核对敏感接口是否拒绝无令牌请求,并检查 Origin、Referer、Cookie 属性、审计日志及状态变更记录。
面试官追问
追问 1用户已登录邮箱后只打开了 hacker.com,既没输入密码也没把 Cookie 发给攻击者,邮件转发规则为什么仍可能被改?
攻击者不必读取 Cookie,只需诱导浏览器向邮箱设置接口构造请求,浏览器可能按规则自动携带现有登录状态。若服务端只认会话而不验证请求意图,就会把恶意操作当成用户本人提交。
追问 2代码评审发现 /transfer 只检查 sid 登录 Cookie,前端表单没有 CSRF Token,你会要求服务端怎样改?
服务端应让状态变更请求同时通过会话检查和不可预测、与会话绑定的 CSRF Token 校验,无令牌或令牌错误时返回拒绝。不能只在页面上隐藏按钮,因为攻击页面仍可直接构造请求。
追问 3产品把登录 Cookie 设置成 SameSite=Lax 后准备删除全部服务端令牌校验,理由是现代浏览器不会跨站携带,你会同意吗?
不应把 SameSite 当作服务端校验的完全替代,它只能降低部分跨站携带 Cookie 的概率。旧客户端、属性配置错误或特定跳转链路可能削弱保护,因此敏感操作仍应保留令牌及来源检查。
追问 4线上出现一批异常邮箱转发规则,但访问日志都带有效会话,安全值班会从哪些证据判断是否为 CSRF?
应检查敏感接口是否接受无 CSRF Token 请求,并核对 Origin、Referer、Cookie 属性、审计日志和状态变更时间线。有效会话不能排除 CSRF,因为攻击本就借用受害者已有的登录状态。
追问 5安全团队在“只校验 Origin”与“会话绑定令牌加来源检查”之间争论,涉及账户绑定变更时你会选哪种?
应以会话绑定的不可预测令牌作为核心校验,并把 Origin、Referer 作为额外排查或防护信号。单一来源头可能受客户端行为和部署配置影响;组合方案实现成本更高,但对敏感状态变更更稳妥。
追问 6一次事故里脚本读取页面数据后外传,另一次则借已登录状态修改 /transfer,面试时你会怎样区分 XSS 与 CSRF?
读取页面数据并外传符合 `XSS的利用路径,而借浏览器现有身份触发跨站状态变更符合CSRF。两者可能相互影响:若 XSS`` 已能读取页面或代替用户请求,它也可能绕过页面中的令牌防线。
# 什么是 CSRF 攻击
30 秒速记
CSRF是攻击者借用用户已有的登录状态,向目标服务器伪造非用户本意的跨站请求。- 攻击成立的关键是用户仍处于登录状态,并访问了攻击者控制或布置的第三方页面。
- 恶意页面可以触发自动
GET、自动POST,也可以诱导用户主动点击请求链接。 - 服务器若只根据登录凭证确认身份,却不判断请求是否代表用户真实意愿,就可能执行转账等敏感操作。
CSRF不要求把恶意代码注入目标页面;它利用的是目标接口的校验缺口和用户现存的登录状态。
CSRF 是攻击者借用用户已有的登录状态,通过第三方页面伪造非用户本意的跨站请求。 本质上是浏览器自动带上目标站点认可的身份凭证,而服务器只确认了“你是谁”,没有确认“这是不是你的真实意愿”。比如用户登录网银后打开恶意页面,页面可能自动发起 GET、提交 POST,或者诱导用户点击转账链接。它不需要向目标页面注入代码;CORS 也不能单独防住,因为它主要限制读取跨源响应,而不是阻止请求发出。
