CSRF 一 何為CSRF CSRF(Cross-site request forgery跨站請求偽造,也被稱成為“one click attack”或者session riding,通常縮寫為CSRF或者XSRF,是一種對網站的惡意利用。需要註意的是,CSRF與XSS的區別,CSRF是其他網站進行 ...
CSRF
一 何為CSRF
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進行簡單瞭解後,我們先來看看CSRF攻擊受害者需要幾步。
受害者必須依次完成兩個步驟:
1.登錄受信任網站A,併在本地生成Cookie。
2.在不登出A的情況下,訪問危險網站B。
此時危險網站B擁有受害者在信任網站A的登錄驗證cookie,假設Cookie沒有失效或者過期,那麼危險網站B就可以發起假冒的請求,來獲取受害者在信任網站A的信息或者在受害者不知情的情況下,進行資金轉移等。
三 MVC是如何防止CSRF的
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內試了幾次,有興趣的同學可以自己嘗試一下)
四 AJAX請求如何防止CSRF
上面說了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站點作為解析。