攻击_CSRF
前言
Github:https://github.com/HealerJean
一、CSRF 是什么
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用用户身份发起非自愿请求的攻击方式。攻击者通过诱导用户访问恶意网站或点击链接,以用户的名义向目标网站发送请求,从而完成攻击者期望的操作(如转账、修改密码、添加管理员等)。
1、攻击原理
- 用户在浏览器中登录了受信任的网站
A(如银行网站); - 登录成功后,浏览器保存了网站 A 的
Cookie; - 用户在未退出网站
A的前提下,访问了攻击者控制的网站B; - 网站
B中嵌入了指向网站A的请求(如<img src="http://bank.example/transfer?to=hacker&amount=1000000">); - 浏览器自动携带网站
A的Cookie发送请求; - 网站
A认为请求合法,执行了转账操作。
2、攻击特点
- 利用用户身份发起请求;
- 请求看似合法,难以追踪;
- 不需要窃取用户凭证,仅需诱导用户访问恶意页面;
- 通常通过
GET请求实现,但也可用于POST请求。
二、CSRF 攻击示例
假设银行网站存在
CSRF漏洞,攻击者构造如下 HTML 页面:
- 当用户访问该页面时,浏览器会向银行网站发起请求,自动携带用户 Cookie,完成转账操作。
<img src="http://bank.example/transfer?to=hacker&amount=1000000" style="display:none;" />
1、受害者 A 在银行有一笔存款,通过对银行的网站发送请求 http://bank.example/withdraw?account=A&amount=1000000&for=B 可以使A 把 1000000 的存款转到 B的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 A已经成功登陆。
2、黑客 C 自己在该银行也有账户,他知道某个 URL 可以把钱进行转帐操作。A 可以自己发送一个请求给银行:http://bank.example/withdraw?account=A&amount=1000000&for=C。但是这个请求来自 C而非 A,他不能通过安全认证,因此该请求不会起作用。
3、这时,C 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码: src=”http://bank.example/withdraw?account=A&amount=1000000&for=C ”,并且通过广告等诱使 Bob 来访问他的网站。
4、当 A 访问该网站时,上述 url 就会从 A的浏览器发向银行,而这个请求会附带 A 浏览器中的 cookie 一起发向银行服务器。大多数情况下,该请求会失败,因为他要求 A 的认证信息。但是,如果 A 当时恰巧刚访问他的银行后不久,他的浏览器与银行网站之间的 session 尚未过期,浏览器的 cookie 之中含有 A 的认证信息。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 A 的账号转移到C 的账号,而 A 当时毫不知情。等以后 A 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 C 则可以拿到钱后逍遥法外。
三、防御 CSRF 的方法
| 方法 | 安全性 | 实现难度 | 适用场景 | 是否推荐 | 描述 | 优点 | 缺点 |
|---|---|---|---|---|---|---|---|
| Referer 验证 | 中 | 低 | GET 请求、非敏感操作 | ❌(仅辅助) | 检查请求来源是否为可信域名 | 实现简单,对现有系统影响小 | Referer 可伪造、可为空、HTTPS 跳转不带 Referer |
| Token 验证(参数) | 高 | 中 | 通用场景 | ✅ 推荐 | 在请求中加入随机 Token,服务器校验 |
安全性高,广泛使用 | 需要前后端配合,Token 管理复杂 |
| Token 验证(Header) | 高 | 中 | 前后端分离项目 | ✅ 推荐 | 将 Token 放入自定义 Header(如 X-CSRF-Token) |
适用于前后端分离架构 | 仅适用于 Ajax 请求,兼容性要求高 |
| 验证码 | 高 | 高 | 高风险操作 | ✅ 辅助推荐 | 强制用户交互,防止自动请求 | 安全性高 | 降低用户体验,不适合频繁操作 |
1、验证 Referer 头
1)适用场景:
- 简单页面请求(如
GET请求); - 已有系统快速接入;
- 非敏感操作。
2)问题与限制:
Referer可伪造(某些浏览器支持伪造);HTTPS→HTTP跳转不带 Referer;- 用户隐私设置可禁用 Referer;
- 直接输入地址访问时
Referer为空。
3)示例
@Slf4j
public class RefererInterceptor extends HandlerInterceptorAdapter {
private Set<String> whiteList = new HashSet<>(Arrays.asList("example.com", "trusted.com"));
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String method = request.getMethod();
String referer = request.getHeader("Referer");
// 仅对 POST 请求强制校验 Referer
if ("POST".equalsIgnoreCase(method)) {
if (StringUtils.isBlank(referer)) {
log.warn("Missing Referer header in POST request");
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Missing Referer");
return false;
}
try {
URL refererUrl = new URL(referer);
String currentHost = request.getServerName();
if (!currentHost.equals(refererUrl.getHost()) && !whiteList.contains(refererUrl.getHost())) {
log.warn("Invalid Referer: {}", referer);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid Referer");
return false;
}
} catch (MalformedURLException e) {
log.warn("Invalid Referer URL: {}", referer);
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid Referer");
return false;
}
}
return true;
}
}
2、Token 验证(推荐)
1)实现方式:
- 服务端生成
Token(随机字符串); - 将
Token存入Session或Cookie; - 前端请求时携带
Token(参数或Header); - 服务端校验
Token是否匹配。
2)示例:
前端示例
<form action="/transfer" method="POST">
<input type="hidden" name="csrfToken" th:value="${csrfToken}" />
<input type="text" name="to" />
<input type="submit" value="转账" />
</form>
后端 Token 生成(Java):
String csrfToken = UUID.randomUUID().toString();
session.setAttribute("csrfToken", csrfToken);
String token = request.getParameter("csrfToken");
String sessionToken = (String) request.getSession().getAttribute("csrfToken");
if (!token.equals(sessionToken)) {
throw new ForbiddenException("Invalid CSRF token");
}
3、自定义 Header Token
1)实现方式:
- 前端在每次请求中加入
X-CSRF-Token: <token>; - 后端拦截请求并验证
Token; Token可通过接口获取或从Cookie中读取。
2)示例:
前端设置
axios.defaults.headers.common['X-CSRF-Token'] = localStorage.getItem('csrfToken');
后端校验逻辑(Spring Boot)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if ("POST".equalsIgnoreCase(request.getMethod())) {
String token = request.getHeader("X-CSRF-Token");
String sessionToken = (String) request.getSession().getAttribute("csrfToken");
if (token == null || !token.equals(sessionToken)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid CSRF Token");
return false;
}
}
return true;
}
4、验证码(辅助手段)
1)使用场景:
- 高风险操作(如修改密码、转账);
- 防止自动化攻击;
- 用户交互确认。
2) 缺点:
- 降低用户体验;
- 不适用于频繁操作;
- 无法完全替代
Token验证。
四、实践建议
- 优先使用
Token验证机制,后端存储到redis中 - 对敏感操作强制
Token校验; - 避免使用
GET请求进行状态变更操作; - 前后端分离项目使用自定义
Header + Token; - 高风险操作结合验证码;
- 禁用不必要的跨域请求;
- 定期更新
Token,防止Token被泄露; - 使用
SameSite Cookie属性(推荐SameSite=Strict或Lax); - 启用
CSRF防护中间件(如SpringSecurityCSRFProtection)
1、Spring Security 中的 CSRF 防护
Spring Security默认启用了CSRF防护机制,适用于基于Session的认证。
启用方式(Spring Boot):
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
}
前端使用方式:
// 从 Cookie 中读取 XSRF-TOKEN
const csrfToken = getCookie('XSRF-TOKEN');
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
axios.defaults.headers.common['X-XSRF-Token'] = csrfToken;


