TT Lab
开始
学习 学习路径 课程

会话与令牌 — 从浏览器到服务网格

仅凭 Cookie 无法知道请求来自哪里

在 TT Lab 中继续学习

一句话总结

CSRF 是借助浏览器会自动附带 Cookie 的特性,让其他站点在用户不知情的情况下发出改变状态的请求的攻击。仅凭 Cookie,无法判断该请求是否从我们自己的页面发起,所以服务器必须另外要求一种攻击页面无法伪造的信号。

为什么需要它

用户在 bank.example.com 保持登录的同时,在另一个标签页打开了攻击者的页面。该页面里藏着一个指向 bank.example.com/transfer 的表单,页面一打开就会提交。目的地是银行,所以浏览器会附带银行的 Cookie。到达服务器的请求,与用户亲手点击的转账请求一个字都不差。

攻击者读不到响应。跨源响应会被同源策略遮住。所以 CSRF 不是窃取而是写入的攻击,防御也朝着确认“这次写入是否真的来自我们的页面”的方向发展。

工作原理

OWASP CSRF 防护备忘单 整理的防御有四个方向。

同步令牌(synchronizer token)。服务器为每个会话生成不可预测的值,存入会话并下发到页面。改变状态的请求必须通过请求头或隐藏字段把这个值送回来。攻击页面由于同源策略读不到该值。OWASP 建议不要通过 Cookie 发送令牌,并且相比表单字段更推荐自定义请求头。这里常被漏掉的是与会话的绑定。如果只检查“是不是我们签发的令牌”,攻击者即使把用自己账号拿到的真实令牌放进受害者的请求,也能通过。比较要用 hmac.compare_digest 这样的恒定时间函数,避免泄露时间差。

签名的双重提交 Cookie。不想在服务器上保存状态时使用。把随机值和会话 ID 绑在一起用 HMAC 签名,得到的值同时放进 Cookie 和请求头。OWASP 不建议不带签名、只检查 Cookie 与请求头是否相同的做法,因为能从子域名或网络位置注入 Cookie 的攻击者,可以让两个值恰好相同。

Fetch Metadata 与 Origin。浏览器会在每个请求上附带 Sec-Fetch-Site。值是 same-origin、same-site、cross-site、none,并且它是以 Sec- 开头的禁止修改的请求头,页面脚本无法更改。OWASP 建议拒绝 cross-site 的 POST、PUT、PATCH、DELETE,same-site 只在信任同级子域名时才放行。对于没有该请求头的旧客户端,要么拦截(对敏感端点推荐),要么转交 Origin 检查和令牌来判断。Origin 头要与白名单比较整个字符串是否相同。如果只比较开头,http://127.0.0.1:8303.evil.example 这样的名称就会通过。

自定义请求头。要在跨源请求上附带自定义请求头,必须通过 CORS 预检请求。服务器不允许,请求就发不出去。所以如果把 CORS 放得很宽松,这种防御也会一起崩溃。

SameSite 只是辅助。在 RFC 6265bis 草案 中,Lax Cookie 会随同站请求发送,即使是跨站请求,只要是安全方法的顶级导航也会带上。有三个局限。

same-site ≠ same-origin   blog.example.com 과 bank.example.com 은 같은 사이트다
GET 으로 상태 변경        링크 하나로 최상위 GET 이동이 되고 Lax 쿠키가 실린다
기본값의 예외             속성이 없으면 Lax 와 같은 기본 모드지만 브라우저가
                          Lax-allowing-unsafe 를 쓸 수 있다

关于最后一行,草案要求只对此类浏览器最近创建的 Cookie 设置例外,并把 2 分钟以内作为合理上限。据 web.dev 介绍,Chrome 从 80 起把没有属性的 Cookie 当作 Lax 处理,但对顶级 POST 设置了约 2 分钟内允许携带 Cookie 的临时缓解。显式的 SameSite=Lax 没有这个例外。OWASP 的结论也一样——SameSite 只是纵深防御,不能代替 CSRF 防护。

登录表单也是目标。登录 CSRF 会让受害者登录到攻击者的账号,使受害者输入的内容积累在攻击者的账号里。登录前没有会话,无处绑定令牌,所以 OWASP 建议先创建登录前会话并放入令牌,登录后再重新创建会话。

在现场相遇的样子

如果还留着 GET /logout、GET /approve?id=42 这类用 GET 改变状态的端点,SameSite=Lax 什么也拦不住。一个链接就会带上 Cookie。这就是 OWASP 另外写明“不要用 GET 执行改变状态的操作”的原因。

公司的服务分散在同一个可注册域名下的多个子域名中,也很常见。只要某个活动页面出现 XSS,该页面就会向银行子域名发送 same-site 请求。SameSite 会放行,只有不信任同级子域名的 Fetch Metadata 策略和绑定到会话的令牌才能拦住。

下一项实验要做什么

在 127.0.0.1:8303 上搭建可登录的转账服务器,并签发按会话区分的 CSRF 令牌。评分器会用 curl 亲自构造伪造请求。发送没有令牌的请求、别人会话的令牌、Sec-Fetch-Site: cross-site、白名单之外的 Origin,确认全部返回 403,并且还要确认转账没有被记录。最后填写 SameSite=Lax Cookie 会随哪些跨站请求发送的判定表。