1. 什么是跨站請(qǐng)求偽造(CSRF)
CSRF(Cross-site request forgery跨站請(qǐng)求偽造,也被稱為“One Click Attack”或者session Riding,通??s寫(xiě)為CSRF或者XSRF,是一種對(duì)網(wǎng)站的惡意利用。盡管聽(tīng)起來(lái)像跨站腳本(XSS),但它與XSS非常不同,并且攻擊方式幾乎相左。XSS利用站點(diǎn)內(nèi)的信任用戶,而CSRF則通過(guò)偽裝來(lái)自受信任用戶的請(qǐng)求來(lái)利用受信任的網(wǎng)站。與XSS攻擊相比,CSRF攻擊往往不大流行(因此對(duì)其進(jìn)行防范的資源也相當(dāng)稀少)和難以防范,所以被認(rèn)為比XSS更具危險(xiǎn)性。
以上是來(lái)自百度百科的概念。下面各自舉個(gè)簡(jiǎn)單的例子說(shuō)明:
XSS:假設(shè)沒(méi)有預(yù)防XSS,我在文章評(píng)論區(qū)域輸入:<scr CSRF:與XSS不同,CSRF利用的當(dāng)前信任用戶,讓用戶不知不覺(jué)的“自己提交”數(shù)據(jù)。例如用戶 B 在用戶 A 的文章評(píng)論區(qū)域上傳一張圖片,如: [img]http://images2015.VEVb.com/blog/798800/201512/798800-20151230205214839-1087717627.jpg[/img] 然后把它改成: [img]http://Another/Forgery.html[/img] 這是一張無(wú)效的圖片,并且來(lái)自于另一個(gè)站點(diǎn)。在 Forgery.html 中,偽造者創(chuàng)建一個(gè)表單,action指向要攻擊的頁(yè)面,當(dāng)window.onload 執(zhí)行時(shí)自動(dòng)提交表單。 那么用戶 A 在訪問(wèn)該頁(yè)面時(shí),就會(huì)發(fā)起請(qǐng)求到Another/Forgery.html。由于Web的身份驗(yàn)證信息通常會(huì)保存在瀏覽器cookie中,而cookie每次都會(huì)隨請(qǐng)求提交到服務(wù)器,所以這個(gè)請(qǐng)求會(huì)把cookie 和 Forgery.html 的表單信息一起提交到服務(wù)器。服務(wù)器對(duì)Cookie進(jìn)行相關(guān)驗(yàn)證,并且認(rèn)為這是一個(gè)正常的請(qǐng)求,執(zhí)行相關(guān)操作。 2. 模擬一次攻擊 按照上面的思路,接下來(lái)我們模擬一次攻擊。新建一個(gè)mvc項(xiàng)目,主要有兩個(gè)頁(yè)面,一個(gè)頁(yè)面用于顯示用戶姓名和評(píng)論,另一個(gè)頁(yè)面用于用戶自己修改姓名。只是為了演示,這里我們固定一個(gè)用戶:張三。如: 顯示頁(yè)面為: 本站點(diǎn)的另一個(gè)頁(yè)面用于修改用戶姓名: 對(duì)應(yīng)Action為Update,為了提高安全性,我們給它標(biāo)記一個(gè)[HttpPost]特性(實(shí)際情況這里會(huì)進(jìn)行身份驗(yàn)證,然后根據(jù)用戶id去修改信息)。如: 可以看到上面的評(píng)論區(qū)域有一個(gè)鏈接,來(lái)自另一個(gè)站點(diǎn),它的代碼很簡(jiǎn)單,與我們的修改頁(yè)面類(lèi)似,如下: 可以看到,該頁(yè)面表達(dá)的action指向了前面的站點(diǎn)的修改頁(yè)面,并且在頁(yè)面load完后,就會(huì)自動(dòng)提交。當(dāng)張三點(diǎn)擊這個(gè)鏈接后,會(huì)發(fā)生什么呢?如下: 3. 如何防止 3.1 盡早防范 永遠(yuǎn)不要相信用戶提交的數(shù)據(jù)。通常我們會(huì)在前臺(tái)和后臺(tái)對(duì)用戶的輸入進(jìn)行驗(yàn)證,確保數(shù)據(jù)的正確性和安全性。以上面的例子,如果用戶上傳一張圖片,然后修改成.html的格式,那么應(yīng)該不讓它保存。我們可以看博客園的例子,如果這樣做,可以保存,但顯示出來(lái)就是普通文本的格式,這樣頁(yè)面加載時(shí)就不會(huì)對(duì)這個(gè)url發(fā)起請(qǐng)求。再看csdn,則會(huì)彈出提示非法輸入。 VEVb: csdn: 當(dāng)然,這樣只是第一道屏障。很多網(wǎng)站也可能像上面可以輸入外部鏈接,如果用戶去點(diǎn)擊,依然可能被攻擊。所以我們還需要進(jìn)一步防范。 3.2 MVC 的做法 前面我們模擬了CSRF的過(guò)程,接下看MVC里如何應(yīng)對(duì)這種情況。 ValidateAntiForgeryAttribute特性 這是一個(gè)繼承了FilterAttribute 和 實(shí)現(xiàn)了 IActionFilter 的標(biāo)記特性,它可以應(yīng)用在Controller或者Action上面。我們知道實(shí)現(xiàn)IActionFilter的 Filter會(huì)在 Action執(zhí)行前進(jìn)行相關(guān)處理,具體邏輯在 IActionFilter接口的 OnAuthorization 方法中。MVC 就是在這個(gè)方法中進(jìn)行驗(yàn)證的。具體是如何驗(yàn)證的呢? AntiForgeryToken方法 ValidateAntiForgery特性表示操作需要驗(yàn)證,我們還需要使用HtmlHelper的 AntiForgeryToken方法,這是一個(gè)實(shí)例方法。具體是在View的表單里調(diào)用該方法,該方法會(huì)生成一個(gè)name為_(kāi)_RequestVerificationToken的 input hidden標(biāo)簽,值就是防偽令牌。除此之外,還會(huì)生成一個(gè)同樣名稱并且標(biāo)記為HttpOnly的cookie,值也是通過(guò)加密生成的防偽令牌。ValidateAntiForgery特性的OnAuthorization方法就是根據(jù)這兩個(gè)進(jìn)行驗(yàn)證的。具體是: 1. 用戶請(qǐng)求該頁(yè)面,AntiForgeryToken方法會(huì)生成一個(gè)input hidden 和 cookie,值都是經(jīng)過(guò)加密處理的Token。 2. 用戶提交請(qǐng)求,如果Action(Controller)標(biāo)記了 ValidateAntiForgery特性,則進(jìn)行驗(yàn)證。 2.1 如果表單沒(méi)有一個(gè)name為 __RequestVerificationToken的元素,則拋出HttpAntiForgeryException。 2.2 如果沒(méi)有一個(gè)name為_(kāi)_RequestVerificationToken的cookie,則拋出HttpAntiForgeryException。 2.3 解析input 和 cookie 的值,判斷是否匹配(包括用戶名、時(shí)間等的比較),不匹配則拋出HttpAntiForgeryException。 3. 接收到異常,顯示錯(cuò)誤頁(yè)或拋出黃頁(yè)。 至于 input 的值 和 cookie 的生成,mvc內(nèi)部會(huì)根據(jù)當(dāng)前用戶名,時(shí)間以及集合 MachineKey 等去加密生成,確保不會(huì)輕易被猜出。有興趣的朋友可以通過(guò)源碼了解詳細(xì)過(guò)程。 按照上面的做法,我們給Update加上一個(gè)[ValidateAntiForgery]特性,并且在表單調(diào)用HtmlHelper的AntiForgeryToken方法。此時(shí)如果用戶點(diǎn)擊鏈接,一樣訪問(wèn)了Forgery.html,并且自動(dòng)提交表單,cookie還是一樣會(huì)提交,但偽造頁(yè)面無(wú)法知道input hidden 的值,所以無(wú)法通過(guò)驗(yàn)證。 3.3 WebForm 的做法 WebForm 沒(méi)有 AntiForgeryToken方法 可以直接使用,不過(guò)知道MVC的實(shí)現(xiàn)過(guò)程后,我們也可以自己實(shí)現(xiàn)一套。 在頁(yè)面表單,像mvc一樣,我們也輸出一個(gè)名稱為:_RequestVerificationToken 的 input hidden 標(biāo)簽,值為序列化后的Token,具體是調(diào)用 HttpRespose 的擴(kuò)展方法AntiForgeryToken。AntiForgeryToken方法不僅會(huì)輸入input hidden,還會(huì)將Guid存儲(chǔ)在Context.Item,這是一個(gè)在一次請(qǐng)求內(nèi)各個(gè)時(shí)期可以使用的集合,在頁(yè)面周期完成后,我們判斷是否有這個(gè)標(biāo)記,如果有,還需要將它寫(xiě)入到Cookie當(dāng)中。 表單: AntiForgeryToken擴(kuò)展方法: 對(duì)于驗(yàn)證Token,和將GUID寫(xiě)入到Cookie是通過(guò)一個(gè)AntiCsrfModule完成的,它主要攔截頁(yè)面執(zhí)行前和執(zhí)行后兩個(gè)事件。頁(yè)面執(zhí)行后完成上面是否需要將GUID寫(xiě)入Cookie的判斷,而頁(yè)面執(zhí)行前則判斷是否需要驗(yàn)證,以及驗(yàn)證結(jié)果,一旦不匹配,就拋出異常。代碼如下: 對(duì)于需要驗(yàn)證的頁(yè)面,通過(guò)一個(gè)ValidateAntiForgeryAttribute特性標(biāo)記,如下: 同樣,我們像前面一樣模擬一次攻擊。結(jié)果如我們所想,會(huì)拋出黃頁(yè)。 3.4 Ajax 方式 上面我們都是通過(guò)Post 表單的形式提交數(shù)據(jù),如果是以ajax提交的呢?我們可以在后臺(tái)判斷請(qǐng)求是否是Ajax請(qǐng)求,如果不是則不允許操作。因?yàn)閖s受同源策略限制,另一個(gè)域在沒(méi)有被授權(quán)的情況下,腳本是無(wú)法和本域進(jìn)行通信的。也就是Another/Forgery.html可以以post的形式提交數(shù)據(jù)到我們后臺(tái),但沒(méi)辦法以ajax的形式提交,也沒(méi)辦法調(diào)用我們頁(yè)面的方法或者訪問(wèn)dom元素。 4. 博客園的實(shí)現(xiàn) 例子就在身邊。我們看到博客園【設(shè)置基本資料】模塊,查看源碼就會(huì)發(fā)現(xiàn)這里用用到了這個(gè)技術(shù)。 表單: Cookie: public class CurrentUser { PRivate static CurrentUser currentUser = new CurrentUser(){Name="張三"}; public string Name{get;set;} public static CurrentUser Current { get { return currentUser; } } }
<div> 修改用戶信息: </div> <form action="/home/update" method="post"> <p> <label>用戶名:</label> <input name="name" /> </p> <p> <input type="submit" /> </p> </form>
[HttpPost] public ActionResult Update(string name) { CurrentUser.Current.Name = name; return RedirectToAction("Index"); }
<body> <div> <form id="form" action="http://localhost:50025/home/update" method="post"> <input type="hidden" name="name" value="2b" /> </form> </div></body><script type="text/javascript"> window.onload = function () { document.getElementById("form").submit(); }</script>
<form id="Form1" action="UpdateAntiCsrf.aspx" method="post" runat="server"> <div> <%=Response.AntiForgeryToken() %> <input type="text" name="name"/> <input type="submit"/> </div> </form>
public static class HttpResposeExtentions { public static string AntiForgeryToken(this HttpResponse response) { HttpContext context = HttpContext.Current; if (context == null) { throw new InvalidOperationException("無(wú)效請(qǐng)求!"); } Guid guid = Guid.NewGuid(); context.Items["_RequestVerificationToken"] = guid; ObjectStateFormatter formatter = new ObjectStateFormatter(); return string.Format("<input type='hidden' name='_RequestVerificationToken' value={0} />", formatter.Serialize(guid)); } }
public class AntiCsrfModule : IHttpModule { public void Dispose () { } public void Init(Httpapplication app) { app.PreRequestHandlerExecute += new EventHandler(app_PreRequestHandlerExecute); app.PostRequestHandlerExecute += new EventHandler(app_PostRequestHandlerExecute); } void app_PreRequestHandlerExecute(object sender, EventArgs e) { HttpContext context = ((HttpApplication)sender).Context; HttpRequest request = context.Request; IHttpHandler handler = context.Handler; if (handler.GetType().IsDefined(typeof(ValidationAntiForgeryAttribute), true)) { if (request.HttpMethod.Equals("POST", StringComparison.CurrentCultureIgnoreCase)) { HttpCookie cookie = request.Cookies["_RequestVerificationToken"]; if (cookie == null) { throw new InvalidOperationException("無(wú)效請(qǐng)求!"); } string value = request.Form["_RequestVerificationToken"]; if (string.IsNullOrEmpty(value)) { throw new InvalidOperationException("無(wú)效請(qǐng)求!"); } ObjectStateFormatter formatter = new ObjectStateFormatter(); Guid? guid = formatter.Deserialize(value) as Guid?; if(guid.HasValue && guid.Value.ToString() == cookie.Value) { return; } throw new InvalidOperationException("無(wú)效請(qǐng)求!"); } } } void app_PostRequestHandlerExecute(object sender, EventArgs e) { HttpContext context = ((HttpApplication)sender).Context; Guid? guid = context.Items["_RequestVerificationToken"] as Guid?; if (guid.HasValue) { HttpCookie cookie = new HttpCookie("_RequestVerificationToken", guid.Value.ToString()); cookie.HttpOnly = false; context.Response.Cookies.Add(cookie); } } }
public class ValidateAntiForgeryAttribute : Attribute { }
|
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注