CSRF(Cross-site request forgery跨站请求伪造,也被称成为“one click attack”或者session riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。需要注意的是,CSRF与XSS的区别,CSRF是其他网站进行对你的网站的攻击。
关于CSRF的详细信息请看:https://baike.baidu.com/item/CSRF/2735433
对CSRF进行简单了解后,我们先来看看CSRF攻击受害者需要几步。
受害者必须依次完成两个步骤:
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B。
此时危险网站B拥有受害者在信任网站A的登录验证cookie,假设Cookie没有失效或者过期,那么危险网站B就可以发起假冒的请求,来获取受害者在信任网站A的信息或者在受害者不知情的情况下,进行资金转移等。
MVC框架主要通过在form内添加@Html.AntiForgeryToken()和在action上添加 [ValidateAntiForgeryToken]进行防止。
具体代码如下:
1. 在cshtml页面加上 @Html.AntiForgeryToken()
<section id="loginForm">
@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>使用本地帐户登录。</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="登录" class="btn btn-default" />
</div>
</div>
<p>
@Html.ActionLink("注册为新用户", "Register")
</p>
@* 为密码重置功能启用帐户确认后,请启用此项一次
<p>
@Html.ActionLink("Forgot your password?", "ForgotPassword")
</p>*@
}
</section>
2. 在相应的action方法上添加[ValidateAntiForgeryToken]
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindAsync(model.Email, model.Password);
if (user != null)
{
await SignInAsync(user, model.RememberMe);
return RedirectToLocal(returnUrl);
}
else
{
ModelState.AddModelError("", "用户名或密码无效。");
}
}
// 如果我们进行到这一步时某个地方出错,则重新显示表单
return View(model);
}
3.MVC在预防CSRF上的原理
@Html.AntiForgeryToken()方法会在浏览器上做两件事:
1. 页面上加上一个标签<input name="__RequestVerificationToken" type="hidden" value="密文A" />
2. 在浏览器上生成一个名为__RequestVerificationToken的Cookie,值为“密文B”
form表单提交时,会将页面上的密文A和浏览器的密文B一起提交给服务器端,服务器端分别对密文A和密文B进行解密,比对密文A和密文B解密后的明文字符串是否相同,如果相同,则验证通过。
那么密文A和密文B是从何而来呢,其实是上面的@Html.AntiForgeryToken()方法随机生成了一串明文,然后再对明文加密放在页面和cookie内,但是加密出来的密文不同。密文A每次刷新都会更新成不同的密文,但是一个浏览器进程内,COOKIE的密文好像不变(自己在firefox内试了几次,有兴趣的同学可以自己尝试一下)
上面说了MVC框架如何防止CSRF的,但是只限于FORM表单提交,那么问题来了,在一般ajax请求时,没有form表单提交,这个时候该如何防止CSRF呢?网络上有很多不错的答案。我在写该篇随笔的时候也借鉴了很多前辈的方法。
下面介绍我的方法:
1. 在全局共享页面,添加密文生成代码:
@using (Html.BeginForm(null, null, FormMethod.Post, new { id = "__AjaxAntiForgeryForm" }))
{
@Html.AntiForgeryToken()
}
2. 收紧ajax请求方法入口,写扩展ajax方法避免重复工作,一定要注意黄色标记
$.extend({
z_ajax: function (request) {
var form = $('#__AjaxAntiForgeryForm');
var antiForgery = $("input[name='__RequestVerificationToken']",form).val();
var data = $.extend({ __RequestVerificationToken: antiForgery }, request.data);
request = $.extend({
type: "POST",
dataType: "json",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
}, request);
request.data = data;
$.ajax(request);
}
3. 在需要的POST请求上,添加[ValidateAntiForgeryToken]
[HttpPost]
[ValidateAntiForgeryToken]
public JsonResult Test(string testString)
{
var trustedString = Encoder.HtmlEncode(testString);
return Json(trustedString);
}
4. 实现具体的ajax请求,该请求会自动将密文带到服务端,由服务端的特性验证
$(function () {
$("#test").click(function ()
{
$.z_ajax(
{
url: "/Home/Test",
data: {testString:'333333'},
error: function (request, textStatus, errorThrown) {
console.log(request, textStatus, errorThrown);
},
success: function (response)
{
alert(123);
}
});
})
})
经过以上的讲解,大家应该对MVC 防止CSRF有了一定的认识。
正如上面所说,在编写这篇随笔的时候,参考了很多前辈的思路和结晶。在这里就不一一列举了,如果有什么问题,欢迎大家随时反馈。
以上案例使用VS2013自动生成的MVC5站点作为解析。