[ASP.NET] 實現與防範 CSRF 跨網站請求偽造攻擊

CSRF/XSRF 全名 Cross Site Request Forgery 中文翻成「跨網站偽造請求」,CSRF 在 2013 年的 OWASP TOP 10 安全弱點排行第 8 名,此篇就來談一下 CSRF 是什麼? 並且舉例幾個攻擊方式範例與防範做法。

前言


  在看資安資料時看到了 CSRF 攻擊的資料,之前雖然知道但是並沒有特別去認真看看詳細的攻擊方式與防範方法,現在特別紀錄一下實際進行測試與防範的方法。

 

  CSRF/XSRF 全名 Cross Site Request Forgery 中文翻成「跨網站偽造請求」,CSRF 在 2013 年的 OWASP  TOP 10 安全弱點排行第 8 名,此篇就來談一下 CSRF 是什麼? 並且舉例幾個攻擊方式範例與防範做法。

 

CSRF 的攻擊行為


  CSRF 主要的攻擊行為就是利用當使用者合法取得網站使用認證後,透過某些方式偽造網站合法使用者的身份進行非法的存取動作,合法使用者即可能在不自覺的情況下被引導到駭客的攻擊網頁。

 

  一般網站在使用者進行驗證後,會將驗證成功的資訊存放於 Cookie 中,當使用者尚未進行登出動作時,此 Cookie 都還視為有效認證,而在使用者瀏覽網站其他頁面時即會將此認證資訊回傳伺服器以便進行其他操作,但如此時使用者在不自覺的情況下瀏覽了駭客置入攻擊代碼的網頁,駭客即可使用相同一份身分驗證資訊發送操作動作回該認證網站進行攻擊。

 

  參考以下圖片流程:

 

  在 ASP.NET 網站中使用的 Form 驗證模式的 FormsAuthentication 類別也是將驗證資訊產生於 Cookie 中,就會有可能發生以上的問題,所以我們必須要額外增加一些防範的方法來避免被 CSRF 攻擊,而為了避免被 CSRF 攻擊我們應該遵循以下作法:

  1. 避免使用 GET 方法傳送參數及操作新增/更新/刪除動作。
  2. 避免使用 HTTP Referer Header 來驗證來源要求者。(Referer 可以偽造)
  3. 應使用 票證(Token) 來驗證來源要求者。( SessionID + salt, 亂數值 )
  4. 使用 ViewStateUseKey 屬性驗證來源。

 

攻擊範例說明


  接下來會舉例兩種 CSRF 攻擊範例來加以說明,首先模擬情境,用戶登入了一個資金帳戶的網站,此網站使用 Form 驗證並且會產生 Cookie 資訊,用戶登入後會轉跳到詳細資金頁面 (Detail.aspx) 並且可以在此頁面進行轉帳動作,而轉帳動作是呼叫一隻泛型處理常式 (TransferHandler.ashx),在 TransferHandler.ashx 的操作上是接收 QueryString 進行處理。 (一般當然不會這樣搞,在此純粹為了測試 XD)

 

  操作畫面

 

  現在假設非法的駭客透過方法知道了處理轉帳動作的是 TransferHandler.ashx 這支程式處理,並且知道了完整路徑參數如下


http://localhost:53810/WebGet/TransferHandler.ashx?fromid=Arvin&targetid=Channy&money=100

 

  但由於 TransferHandler.ashx 裡面有驗證該使用者必須要登入才可以執行,且駭客並沒有 Arvin 此帳戶的帳戶密碼等資訊可以登入,如下


public class TransferHandler : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {

        if (context.User.Identity.IsAuthenticated)
        {
                // do something ....
        }
    }
}

 

  因為無法登入帳戶,所以直接執行此 URL 時並不能將資金轉出,如下

 

  所以在此駭客想到可以使用 CSRF 的方式來誘騙登入後的 Arvin 進到非法頁面來觸發轉帳,因此在 Arvin 登入後,駭客丟了一個可以看美女圖的網站給 Arvin,Arvin 看到連結就欣喜若狂的點下去該網址......

 

  看完美女圖的 Arvin 覺得意猶未盡,但是想起來要做轉帳的動作,所以又回到轉帳網頁刷新了一下餘額,卻突然發現餘額竟然莫名其妙的減少了!!這下 Arvin 真的是 人財兩失 了。

 

  其實在 Arvin 進入美女圖網頁後駭客就在此網頁埋了上述的 URL (一個隱藏的 IMG 圖片連結),如下


<div>
    Show Girl Picture >>
    <asp:Button ID="Button1" runat="server" Text="More..." />
    <br />
    <img alt="" class="style1" src="00000000.png" /><br />
    <img 
        src="http://localhost:53810/WebGet/TransferHandler.ashx?fromid=Arvin&targetid=Channy&money=100"
        style=" display: none; " ></img>
    <br />
    <br />
</div>

 

  透過這個 URL ,讓此網頁去呼叫轉帳處理的程式,這裡記住因為 Arvin 登入帳戶後並未登出,所以當從 Arvin 瀏覽器發送此網址要求時也將一併帶入之前登入的 Cookie 資訊回伺服器驗證,也就造成使用者通過了驗證而進行轉帳。

 

  第一個範例是使用 GET 方法透過 QueryString 而進行 CSRF 攻擊的範例,接下來,好!我不用 GET 改用 POST 總可以了吧?將 TransferHandler.ashx 程式調整後加入了判斷 HTTP Method 及使用 Form 的取值方式,如下


public void ProcessRequest(HttpContext context)
{
    if (context.User.Identity.IsAuthenticated)
    {
        if (context.Request.RequestType == "POST")
        {
            if (string.IsNullOrEmpty(context.Request.Form["txtFromAccount"])) return;
            if (string.IsNullOrEmpty(context.Request.Form["txtTargetAccount"])) return;
            if (string.IsNullOrEmpty(context.Request.Form["txtTransferMoney"])) return;
            
            string fromId = context.Request.Form["txtFromAccount"].ToString();
            string targetId = context.Request.Form["txtTargetAccount"].ToString();
            string money = context.Request.Form["txtTransferMoney"].ToString();
 
            // do something ...
       }
    }
}

 

  後來 Arvin 又碰到了一次需要轉帳的需求,於是又登入了網銀想要進行轉帳,但由於駭客非常瞭解人性的弱點,又一次的丟了一個 更讚的美女圖 網址給 Arvin ,Arvin 在天人交戰後還是經不起誘惑又點了網址去看美女以撫慰第一次的傷痛。

 

  當 Arvin 看完美女圖回來要轉帳時,卻又發現帳戶中餘額又變少了!讓 Arvin 瞬間倒地... 後人謠傳不知是因為美女圖還是因為被駭客竊取金錢而倒....

 

  其實在第二次的美女圖網址,駭客使用了 iframe 的方式來竊取金錢,透過內崁 iframe 在呼叫 Submit ,就變成使用 POST 的方式來提交轉帳動作,所以僅僅將 GET 改為 POST 是不能有效防範 CSRF 攻擊的,代碼如下


<script>
    window.onload = function () {
        var iframe = document.frames["ifrm"];
        iframe.document.getElementById("form1").submit();
    }
</script>
<div>
    Show Girl Picture >>
    <asp:Button ID="Button1" runat="server" Text="More..." />
    <br />
    <iframe id="ifrm" src="money.htm" style=" display: none; "></iframe>
    <img alt="" class="style1" src="00000001.png" /><br />
    <br />
</div>

  iframe


<form id="form1" method="post" 
    action="http://localhost:53686/WebPost/TransferHandler.ashx">
    <input type="text" name="txtFromAccount" value="Arvin" />
    <input type="text" name="txtTargetAccount" value="Channy" />
    <input type="text" name="txtTransferMoney" value="100" />
    <input type="submit" value="transfer" />
</form>

 

防範方式說明


  在受騙了兩次之後 Arvin 覺得自己好傻好天真,抱著事不過三的原則,決定使用更有效的方式來防範駭客的攻擊,首先使用 票證 (Token) 的驗證方式來處理。

 

  所謂使用 Token 驗證,就是在來源頁與目標頁透過一組編碼進行驗證,首先當客戶進入轉帳頁面時,Server 端即產生一組 Token 存放在 Server 端保存,並且同時將此 Token 一併 Response 給 Client ,在 Server 保存的部分可以透過 Session 來保存而 Client 的部分可以存放在一個 HiddenField 之中,等到客戶端提交要求後再將此 Token 回傳給 Server 進行驗證比對,因此組 Token 只有該登入使用者當下操作時會產生,所以駭客應無法得知此 Token 且因無法知道此 Token 所以當駭客呼叫轉帳 URL 時就無法進行驗證。 (XSS 或修改頁面的情況先排除)

 

  而此 Token 編碼的方式一般可以使用 Session ID + Salt 進行 MD5 加密來用,範例代碼如下

  Detail.aspx


protected void Page_Load(object sender, EventArgs e)
{
    if (User.Identity.IsAuthenticated)
    {
        GenToken();
        LoadBalances();
    }
    else
        Response.Redirect("Default.aspx");
}

private void GenToken()
{
    string token = CreateToken();
    SaveTokenToClient(token);
    SaveTokenToServer(token);
}

private string CreateToken()
{
    string tokenKey = this.Session.SessionID + DateTime.Now.Ticks.ToString();
    System.Security.Cryptography.MD5CryptoServiceProvider md5 = 
            new System.Security.Cryptography.MD5CryptoServiceProvider();
    byte[] b = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(tokenKey));
    return BitConverter.ToString(b).Replace("-", string.Empty);
}

private void SaveTokenToClient(string pToken)
{
    hfToken.Value = pToken;
}

private void SaveTokenToServer(string pToken)
{
    Session["Token"] = pToken;
}

 

  TransferHandler.ashx


if (context.User.Identity.IsAuthenticated)
{
    if (context.Request.RequestType == "POST")
    {
        if (string.IsNullOrEmpty(context.Request.Form["txtFromAccount"])) return;
        if (string.IsNullOrEmpty(context.Request.Form["txtTargetAccount"])) return;
        if (string.IsNullOrEmpty(context.Request.Form["txtTransferMoney"])) return;
        if (string.IsNullOrEmpty(context.Request.Form["hfToken"])) return;
        
        string fromId = context.Request.Form["txtFromAccount"].ToString();
        string targetId = context.Request.Form["txtTargetAccount"].ToString();
        string money = context.Request.Form["txtTransferMoney"].ToString();
        string token = context.Request.Form["hfToken"].ToString();

        if (VeriftyToken(token))
        {
            // do something ...
        }
    }
    else
        context.Response.Write("Invalid Access!");
}

private bool VeriftyToken(string clientToken)
{
    if (string.IsNullOrEmpty(clientToken)) return false;

    string serverToken = HttpContext.Current.Session["Token"].ToString();
    if (clientToken.Equals(serverToken))
        return true;
    else
        return false;
}

 

  當進入資金明細頁 (Detail.aspx) 時產生一組 Token 於 Server 和 Client ,並在提交轉帳時將 Token 傳入處理程式 (TransferHandler.ashx) 中驗證比較,如此就能夠確保此要求是由使用者於資金明細頁操作而來。

 

  另外一種方式是使用 ViewStateUserKey 屬性,設定 ViewStateUserKey 可以防範惡意使用者攻擊應用程式,透過指定識別項指派給使用者進行驗證,而 ViewStateUserKey 只能在 Page_Init() 事件中指定,一般建議可以指定 Session ID,但是當使用 Session ID 卻發生「ViewState MAC 的驗證失敗.......」的錯誤時,表示你的 Session ID 可能已經逾時或變動了,如果要確保這種情不要發生則可以改使用 User.Identity.Name ,相關代碼如下


protected void Page_Init(object sender, EventArgs e)
{
    ViewStateUserKey = User.Identity.Name;
}

 

  最後,透過了以上的兩種防範方式,現在 Arvin 可以無憂無慮的瀏覽美女圖了 XDD,以上描述若有遺漏或錯誤,歡迎提醒我修正喔,謝謝。

 

 

範例程式碼


TCSRF.rar

 

參考資料


FormsAuthentication 類別

Page.ViewStateUserKey 屬性

善用 ASP.NET 內建功能來擊退網路攻擊

浅谈CSRF攻击方式

ASP.NET 使用 ViewStateUseKey 來防禦 CSRF 的攻擊

ViewStateUserKey + shared hosting + ViewStateMac validation failure

 

 


以上文章敘述如有錯誤及觀念不正確,請不吝嗇指教
如有侵權內容也請您與我反應~謝謝您 :)