跨站請求僞造

1. 什麼是跨站請求僞造(CSRF)javascript

  CSRF(Cross-site request forgery跨站請求僞造,也被稱爲「One Click Attack」或者Session Riding,一般縮寫爲CSRF或者XSRF,是一種對網站的惡意利用。儘管聽起來像跨站腳本(XSS),但它與XSS很是不一樣,而且攻擊方式幾乎相左。XSS利用站點內的信任用戶,而CSRF則經過假裝來自受信任用戶的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊每每不大流行(所以對其進行防範的資源也至關稀少)和難以防範,因此被認爲比XSS更具危險性。html

  以上是來自百度百科的概念。下面各自舉個簡單的例子說明:java

  XSS:假設沒有預防XSS,我在文章評論區域輸入:<script>while(1)alert("呵呵")</script> 而且成功提交。那麼下次進來這個頁面進來,就會不斷彈出對話框,致使該頁面沒法被正常瀏覽;對此咱們在輸出內容時,若是該內容是用戶輸入的,那麼就應該進行Html Encode,如上面的腳本就會以普通文本的形式顯示出來。不只如此,攻擊腳本還能夠讀取用戶cookie信息,作任何腳本能作的事情。ajax

  CSRF:與XSS不一樣,CSRF利用的當前信任用戶,讓用戶不知不覺的「本身提交」數據。例如用戶 B 在用戶 A 的文章評論區域上傳一張圖片,如:瀏覽器

[img]http://images2015.cnblogs.com/blog/798800/201512/798800-20151230205214839-1087717627.jpg[/img]安全

而後把它改爲:服務器

[img]http://Another/Forgery.html[/img]cookie

這是一張無效的圖片,而且來自於另外一個站點。在 Forgery.html 中,僞造者建立一個表單,action指向要攻擊的頁面,當window.onload 執行時自動提交表單。mvc

那麼用戶 A 在訪問該頁面時,就會發起請求到Another/Forgery.html。因爲Web的身份驗證信息一般會保存在瀏覽器cookie中,而cookie每次都會隨請求提交到服務器,因此這個請求會把cookie 和 Forgery.html 的表單信息一塊兒提交到服務器。服務器對Cookie進行相關驗證,而且認爲這是一個正常的請求,執行相關操做。app

2. 模擬一次攻擊

    按照上面的思路,接下來咱們模擬一次攻擊。新建一個mvc項目,主要有兩個頁面,一個頁面用於顯示用戶姓名和評論,另外一個頁面用於用戶本身修改姓名。只是爲了演示,這裏咱們固定一個用戶:張三。如:  

    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>

  

  對應Action爲Update,爲了提升安全性,咱們給它標記一個[HttpPost]特性(實際狀況這裏會進行身份驗證,而後根據用戶id去修改信息)。如:

        [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>

  能夠看到,該頁面表達的action指向了前面的站點的修改頁面,而且在頁面load完後,就會自動提交。當張三點擊這個連接後,會發生什麼呢?以下:

 

3. 如何防止

    3.1 儘早防範

  永遠不要相信用戶提交的數據。一般咱們會在前臺和後臺對用戶的輸入進行驗證,確保數據的正確性和安全性。以上面的例子,若是用戶上傳一張圖片,而後修改爲.html的格式,那麼應該不讓它保存。咱們能夠看博客園的例子,若是這樣作,能夠保存,但顯示出來就是普通文本的格式,這樣頁面加載時就不會對這個url發起請求。再看csdn,則會彈出提示非法輸入。

  cnblogs:  

  csdn:   

  固然,這樣只是第一道屏障。不少網站也可能像上面能夠輸入外部連接,若是用戶去點擊,依然可能被攻擊。因此咱們還須要進一步防範。

    3.2 MVC 的作法

  前面咱們模擬了CSRF的過程,接下看MVC裏如何應對這種狀況。

  ValidateAntiForgeryAttribute特性

  這是一個繼承了FilterAttribute 和 實現了 IActionFilter 的標記特性,它能夠應用在Controller或者Action上面。咱們知道實現IActionFilter的 Filter會在 Action執行前進行相關處理,具體邏輯在 IActionFilter接口的 OnAuthorization 方法中。MVC 就是在這個方法中進行驗證的。具體是如何驗證的呢?

  AntiForgeryToken方法

  ValidateAntiForgery特性表示操做須要驗證,咱們還須要使用HtmlHelper的 AntiForgeryToken方法,這是一個實例方法。具體是在View的表單裏調用該方法,該方法會生成一個name爲__RequestVerificationToken的 input hidden標籤,值就是防僞令牌。除此以外,還會生成一個一樣名稱而且標記爲HttpOnly的cookie,值也是經過加密生成的防僞令牌。ValidateAntiForgery特性的OnAuthorization方法就是根據這兩個進行驗證的。具體是:

  1. 用戶請求該頁面,AntiForgeryToken方法會生成一個input hidden 和 cookie,值都是通過加密處理的Token。

  2. 用戶提交請求,若是Action(Controller)標記了 ValidateAntiForgery特性,則進行驗證。

      2.1 若是表單沒有一個name爲 __RequestVerificationToken的元素,則拋出HttpAntiForgeryException。

    2.2 若是沒有一個name爲__RequestVerificationToken的cookie,則拋出HttpAntiForgeryException。

    2.3 解析input 和 cookie 的值,判斷是否匹配(包括用戶名、時間等的比較),不匹配則拋出HttpAntiForgeryException。

  3. 接收到異常,顯示錯誤頁或拋出黃頁。

  至於 input 的值 和 cookie 的生成,mvc內部會根據當前用戶名,時間以及集合 MachineKey 等去加密生成,確保不會輕易被猜出。有興趣的朋友能夠經過源碼瞭解詳細過程。

  按照上面的作法,咱們給Update加上一個[ValidateAntiForgery]特性,而且在表單調用HtmlHelper的AntiForgeryToken方法。此時若是用戶點擊連接,同樣訪問了Forgery.html,而且自動提交表單,cookie仍是同樣會提交,但僞造頁面沒法知道input hidden 的值,因此沒法經過驗證。

    3.3 WebForm 的作法

  WebForm 沒有 AntiForgeryToken方法 能夠直接使用,不過知道MVC的實現過程後,咱們也能夠本身實現一套。

  在頁面表單,像mvc同樣,咱們也輸出一個名稱爲:_RequestVerificationToken 的 input hidden 標籤,值爲序列化後的Token,具體是調用 HttpRespose 的擴展方法AntiForgeryToken。AntiForgeryToken方法不只會輸入input hidden,還會將Guid存儲在Context.Item,這是一個在一次請求內各個時期可使用的集合,在頁面週期完成後,咱們判斷是否有這個標記,若是有,還須要將它寫入到Cookie當中。

  表單:

    <form id="Form1" action="UpdateAntiCsrf.aspx" method="post" runat="server">
        <div>
            <%=Response.AntiForgeryToken() %>
            <input type="text" name="name"/>
            <input type="submit"/>
        </div>
    </form>

  AntiForgeryToken擴展方法:

    public static class HttpResposeExtentions
    {
        public static string AntiForgeryToken(this HttpResponse response)
        {
            HttpContext context = HttpContext.Current;
            if (context == null)
            {
                throw new InvalidOperationException("無效請求!");
            }
            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));                       
        }
    }

  對於驗證Token,和將GUID寫入到Cookie是經過一個AntiCsrfModule完成的,它主要攔截頁面執行前和執行後兩個事件。頁面執行後完成上面是否須要將GUID寫入Cookie的判斷,而頁面執行前則判斷是否須要驗證,以及驗證結果,一旦不匹配,就拋出異常。代碼以下:

    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("無效請求!");
                    }
                    string value = request.Form["_RequestVerificationToken"];
                    if (string.IsNullOrEmpty(value))
                    {
                        throw new InvalidOperationException("無效請求!");
                    }
                    ObjectStateFormatter formatter = new ObjectStateFormatter();
                    Guid? guid = formatter.Deserialize(value) as Guid?;
                    if(guid.HasValue && guid.Value.ToString() == cookie.Value)
                    {
                        return;
                    }
                    throw new InvalidOperationException("無效請求!");
                }
            }
        }

        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);
            }
        }
    }  

  對於須要驗證的頁面,經過一個ValidateAntiForgeryAttribute特性標記,以下:

    public class ValidateAntiForgeryAttribute : Attribute
    {
    }

  一樣,咱們像前面同樣模擬一次攻擊。結果如咱們所想,會拋出黃頁。

  

    3.4 Ajax 方式

  上面咱們都是經過Post 表單的形式提交數據,若是是以ajax提交的呢?咱們能夠在後臺判斷請求是不是Ajax請求,若是不是則不容許操做。由於js受同源策略限制,另外一個域在沒有被受權的狀況下,腳本是沒法和本域進行通訊的。也就是Another/Forgery.html能夠以post的形式提交數據到咱們後臺,但沒辦法以ajax的形式提交,也沒辦法調用咱們頁面的方法或者訪問dom元素。

4. 博客園的實現

  例子就在身邊。咱們看到博客園【設置基本資料】模塊,查看源碼就會發現這裏用用到了這個技術。

  表單:

  

  Cookie:

相關文章
相關標籤/搜索