一、什么是CSRF攻击?
CSRF(跨站请求伪造)是一种攻击方式,攻击者诱骗受害者的浏览器向一个已登录的Web应用程序发送非本意的请求,从而在用户不知情的情况下,以用户的名义执行某些操作。
攻击者通过诱导用户访问恶意网站,使用户浏览器自动携带 Cookie 等身份凭证,向目标网站发起请求,执行非用户本意的操作如:转账、修改密码、发消息等
简单来说,就是攻击者利用了用户已经通过身份验证的会话,伪造请求来执行操作。
CSRF攻击的核心原理:
- 用户已登录目标网站(如银行、社交平台、邮件系统),并保持了有效的会话(例如,Cookie未过期)。
- 用户访问了攻击者控制的恶意网站,或者点击了恶意链接。
- 恶意网站中包含一个指向目标网站的请求(如<img src="https://bank.com/transfer?to=attacker\&amount=1000">)。
- 浏览器会自动携带用户在bank.com的Cookie(包括身份验证信息)发送该请求。
- 目标服务器收到请求,认为是用户本人发起的合法操作,于是执行转账等操作。
案例1:
假设你登录了 mybank.com,然后在另一个标签页打开了一个恶意网站evil.com。
evil.com 的页面上有一个隐藏的图片或自动向 mybank.com网站提交的表单:
或通过 JavaScript 发起的请求,其目标是 mybank.com 的敏感操作接口。
html
--假设这是一个银行处理转账的请求链接
<img src="https://mybank.com/transfer?to=hacker&amount=5000" width="0" height="0">
你的浏览器会尝试加载这个"图片",并自动发送请求到mybank.com,同时带上你的登录Cookie。
如果银行服务器看到这个来自已登录用户的 GET 请求处理转账,就执行了转账操作,而你完全不知情。攻击就可能成功。
案例2: .NET MVC CSRF 攻击案例:
假设你正在开发一个银行应用,其中有一个功能允许用户更改自己的邮箱地址。
csharp
public class AccountController : Controller
{
// 显示修改邮箱页面
public ActionResult ChangeEmail()
{
return View();
}
// 处理邮箱修改
[HttpPost]
public ActionResult ChangeEmail(string newEmail)
{
if (ModelState.IsValid)
{
// 更新当前用户的邮箱
UserService.UpdateUserEmail(User.Identity.Name, newEmail);
ViewBag.Message = "邮箱已成功修改。";
}
return View();
}
}
对应的视图 ChangeEmail.cshtml
html
@using (Html.BeginForm("ChangeEmail", "Account"))
{
<label>新邮箱:</label>
<input type="text" name="newEmail" />
<input type="submit" value="修改邮箱" />
}
CSRF 攻击过程
攻击者知道该网站存在一个 /Account/ChangeEmail 的 POST 接口用于修改邮箱,
并且没有验证 AntiForgeryToken。于是:
- 用户登录了银行网站
https://bank.example.com。 - 用户在另一个浏览器标签中访问了攻击者的恶意网站:
https://evil-site.com。
恶意网站https://evil-site.com包含如下 HTML 代码:
html
<html>
<body>
<h1>免费领取礼品!</h1>
<form action="https://bank.example.com/Account/ChangeEmail" method="POST">
<input type="hidden" name="newEmail" value="attacker@evil.com" />
<input type="submit" value="点击领取" />
</form>
<script>
document.forms[0].submit(); // 自动提交表单
</script>
</body>
</html>
- 当用户加载此页面时,浏览器会自动向银行网站发送一个 POST 请求。
- 由于用户仍处于登录状态,请求
携带了有效的身份认证 Cookie(如 .ASPXAUTH)。 - 银行服务器收到请求后,无法判断这是用户主动操作还是被诱导的,于是执行了邮箱修改操作。
- 用户的邮箱被悄悄更改为 attacker@evil.com,攻击者可借此重置密码,完全控制账户。
二、如何防范CSRF攻击?
- 使用CSRF Token(最常用):
- 服务器在返回表单或页面时,嵌入一个随机生成的、一次性的Token。
- 当用户提交表单时,必须包含这个Token。
- 服务器验证Token的有效性,无效则拒绝请求。
- 由于攻击者无法获取这个Token,因此无法伪造有效的请求。
- 检查请求头中的 Referer 或 Origin 字段:
- 服务器可以检查请求的来源是否合法。
- 但此方法可能被绕过,且某些隐私设置会禁用Referer。
- SameSite Cookie 属性:
- 将Cookie的SameSite属性设置为 Strict 或 Lax。
- SameSite=Lax:允许同站请求和部分安全的跨站GET请求。
- SameSite=Strict:完全禁止跨站Cookie发送。
这是现代浏览器推荐的防御手段之一。
- 双重提交Cookie:
- 将CSRF Token同时放在请求参数和Cookie中,服务器验证两者是否匹配。
- 利用同源策略,攻击者无法读取Cookie中的Token。
- 要求用户重新验证:
- 对于敏感操作(如修改密码、大额转账),要求用户重新输入密码或进行二次验证。
三、防伪令牌(Anti-Forgery Token)使用案例
在 .NET Core 中,使用防伪令牌(Anti-Forgery Token)是防止跨站请求伪造(CSRF)攻击的标准做法
-
1、启用 Anti-Forgery 支持(默认已启用)
csharpvar builder = WebApplication.CreateBuilder(args); // 添加 MVC 服务(包含防伪令牌支持) builder.Services.AddControllersWithViews(); var app = builder.Build(); app.UseRouting(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); -
2、 在 Razor 视图中生成防伪令牌
在需要提交表单的 .cshtml 视图中,使用 @Html.AntiForgeryToken() 或 :
html<form asp-action="Update" asp-controller="Home" method="post"> @Html.AntiForgeryToken() <!-- 或者使用标签助手 --> <!-- <input asp-antiforgery="true" /> --> <input type="text" name="name" /> <button type="submit">提交</button> </form>这会在表单中生成一个隐藏输入字段,如:
html<input name="__RequestVerificationToken" type="hidden" value="CfDJ8s..." /> -
3、在控制器中验证防伪令牌
在处理 POST、PUT、DELETE 等敏感操作的 Action 上,使用 [ValidateAntiForgeryToken] 特性:
csharp[HttpPost] [ValidateAntiForgeryToken] public IActionResult Update(string name) { // 验证通过后处理业务逻辑 // ... return View(); }或者,你也可以在类级别应用,以保护整个控制器的所有 POST 方法:
csharp//该特性仅对非 GET 请求生效。 [ValidateAntiForgeryToken] public class HomeController : Controller { [HttpPost] public IActionResult Update(string name) { ... } } -
4、 AJAX 请求中使用防伪令牌
对于 AJAX 请求(如 jQuery 或 Fetch API),你需要手动提取令牌并附加到请求头或数据中。
方法一:从页面提取令牌
html<!-- 在页面中预留令牌 --> <form id="antiForgeryForm" asp-antiforgery="true" style="display:none;"></form>javascriptfunction getAntiForgeryToken() { return $('#antiForgeryForm input[name="__RequestVerificationToken"]').val(); } // 使用 jQuery AJAX $.ajax({ url: '/Home/Update', type: 'POST', data: { __RequestVerificationToken: getAntiForgeryToken(), name: 'test' }, success: function() { } });方法二:使用 RequestVerificationToken 请求头(推荐)
将令牌放在请求头中更安全:
javascript$.ajax({ url: '/Home/Update', type: 'POST', headers: { 'RequestVerificationToken': getAntiForgeryToken() }, data: { name: 'test' }, success: function() { } });然后在控制器中启用从请求头读取:
csharp// 可在 Program.cs 中配置 builder.Services.AddAntiforgery(options => { options.HeaderName = "RequestVerificationToken"; });