CSRF攻击与防御详解
一、CSRF攻击描述
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种恶意攻击方式。攻击者诱导用户在当前已登录的Web应用中执行非本意的操作。其核心原理是:利用用户在其他网站已经建立的登录状态(如Cookie),通过伪造请求,让用户在不知情的情况下执行攻击者预设的操作。
举个例子:假设你登录了网上银行A,且登录状态Cookie有效。此时你访问了恶意网站B,B的页面中包含一个隐藏的图片标签,其src指向银行A的转账接口(如<img src="https://bank-a.com/transfer?to=attacker&amount=1000">)。浏览器在加载这个图片时,会自动携带你登录银行A的Cookie向该接口发起GET请求。银行A验证Cookie有效,便执行了转账操作。这就是一次典型的CSRF攻击。
二、攻击原理与必要条件
要成功实施CSRF攻击,必须同时满足以下三个关键条件:
- 目标网站存在可被利用的接口:应用存在一个能执行敏感操作(如修改密码、转账、发帖)的接口,该接口仅通过Cookie或“浏览器自动添加的凭证”进行身份验证。
- 用户已登录目标网站:用户的浏览器中保存着目标网站的有效登录会话(Session Cookie),且该Cookie尚未过期。
- 用户被诱骗访问恶意页面:攻击者诱导用户访问一个精心构造的页面(如钓鱼邮件、恶意网站),该页面会自动向目标网站接口发起请求。
三、攻击的常见实现方式
攻击者伪造请求的方式多种多样,主要分为三类:
-
GET请求攻击
- 方式:将恶意请求参数直接放在URL中,通过
<img>、<script>、<link>、<iframe>等标签的src或href属性触发。浏览器会自动发起GET请求。 - 示例:
<!-- 用户访问包含此代码的页面,会悄悄发起转账 --> <img src="https://bank.com/transfer?to=hacker&amount=100000" width="0" height="0">
- 方式:将恶意请求参数直接放在URL中,通过
-
POST请求攻击
- 方式:在恶意页面中构建一个隐藏的
<form>,并自动提交(通过JavaScript的submit()方法)。 - 示例:
<body onload="document.forms[0].submit()"> <form action="https://bank.com/change_password" method="POST"> <input type="hidden" name="new_password" value="hacker_controlled" /> </form> </body>
- 方式:在恶意页面中构建一个隐藏的
-
其他HTTP方法攻击
- 对于PUT、DELETE等请求,也可以通过构造XMLHttpRequest或Fetch请求来实现,但通常会受到浏览器的同源策略(CORS)限制,具体取决于目标服务器的CORS配置。
四、防御策略详解
防御CSRF的核心思想是增加攻击者无法预测、无法伪造的校验信息。以下是逐层深入的防御方案:
第一层:同源检测(基础防御)
利用HTTP请求头中的Origin和Referer字段,它们会告知服务器请求的来源地址。
Origin头:指明请求来自哪个站点(协议+域名+端口)。对于同源请求或一些特殊情况(如IE11),可能不会发送。Referer头:包含完整的请求来源URL。- 防御步骤:
- 服务器在处理敏感请求(非GET)时,检查
Origin或Referer头。 - 判断其值是否与服务器的域名一致。
- 如果不一致,则拒绝请求。
- 服务器在处理敏感请求(非GET)时,检查
- 优点:简单易实现。
- 缺点:
- 隐私或安全策略可能导致浏览器不发送这些头。
- 低版本浏览器可能存在漏洞。
- 不能作为唯一防御手段,应与其他方法结合。
第二层:CSRF Token(主流且最有效的方案)
这是目前最可靠、最广泛的防御机制。
-
原理:在用户会话(Session)中生成一个随机、不可预测的令牌(Token)。在需要保护的请求(如表单、AJAX请求)中携带此Token。服务器在处理请求时,校验Token的有效性。
-
详细流程:
- 生成Token:用户访问应用时,服务器为其Session生成一个随机的CSRF Token。
- 传递Token:服务器将此Token嵌入到返回给用户的表单中(作为隐藏字段),或通过设置到全局变量(如
meta标签)供前端JavaScript获取。<!-- 表单示例 --> <form action="/transfer" method="POST"> <input type="hidden" name="csrf_token" value="randomly_generated_string"> <!-- ... 其他表单字段 --> </form> - 携带Token:用户提交表单或发起AJAX请求时,前端代码必须将此Token包含在请求中(表单字段或请求头,如
X-CSRF-TOKEN)。 - 服务器验证:服务器收到请求后,从Session中取出之前存储的Token,并与请求中携带的Token进行比对。
- 如果一致,请求合法,执行操作。
- 如果不一致、缺失或过期,请求被认定为伪造,立即拒绝并返回错误。
-
为什么能防御CSRF?
- 同源策略保护:恶意网站B无法通过JavaScript读取到用户在其他网站A的CSRF Token值(因为受同源策略限制)。
- 不可预测性:攻击者无法猜出或伪造出正确的Token值。
第三层:双重Cookie验证(替代方案)
此方案将Token放在Cookie中,但要求前端脚本将其读出并作为请求参数或头传递。
- 流程:
- 用户访问时,服务器在响应中设置一个Cookie(如
csrf_token=random_string)。 - 前端JavaScript读取此Cookie的值。
- 发起请求时,将此值作为参数(如
x-csrf-token)或自定义请求头(如X-CSRF-TOKEN)一起发送。 - 服务器收到请求后,比对Cookie中的Token值和参数/头中的Token值是否一致。
- 用户访问时,服务器在响应中设置一个Cookie(如
- 优点:实现相对简单,无需在服务器端为每个Session存储Token(但Cookie本身是Session的一部分)。
- 缺点:
- 如果网站存在XSS漏洞,攻击者可以读取Cookie,此方案即告失效。
- 需要确保子域名安全,否则子域名间的Cookie可能被篡改。
第四层:利用SameSite Cookie属性(现代浏览器强力补充)
这是浏览器提供的一种从源头遏制CSRF的机制。
- 原理:通过设置Cookie的
SameSite属性,控制Cookie在跨站请求时是否被发送。 - 三种模式:
Strict:最严格。Cookie仅在同站请求(即当前页面URL与请求目标URL的eTLD+1相同)时发送。完全阻止CSRF,但可能影响用户体验(如从外部链接点进来,登录状态会丢失)。Lax:宽松模式。在安全跨站请求(如GET请求、页面跳转)中发送Cookie,但在不安全的跨站POST请求中不发送。这是目前很多站点的默认推荐设置,能在安全性和可用性间取得平衡。None:Cookie在所有上下文中发送,但必须同时设置Secure属性(即仅通过HTTPS传输)。
- 设置示例(在HTTP响应头中):
Set-Cookie: sessionid=abc123; SameSite=Lax; Secure
总结与最佳实践
- 首选方案:CSRF Token是防御CSRF攻击最坚固的基石。确保Token随机、一次性(或短期有效),并与用户Session绑定。
- 强力补充:为所有认证相关的Cookie设置
SameSite=Lax或Strict属性。这能从根本上阻止大多数类型的CSRF攻击。 - 组合防御:对于关键操作,可以结合使用Token和
SameSite属性。同时,对敏感请求进行同源检测作为额外保障。 - 安全意识:确保应用没有XSS漏洞,因为XSS可以绕过大多数CSRF防御(如窃取Token)。
- 避免使用GET进行状态变更:严格遵守HTTP语义,GET请求只用于获取数据,不应用于执行任何有副作用的操作。这能防止简单的GET型CSRF攻击。