防止 ASP.NET MVC 應用程式中的跨網站要求偽造(CSRF)攻擊Preventing Cross-Site Request Forgery (CSRF) Attacks in ASP.NET MVC Application

Mike Wassonby Mike Wasson

跨網站偽造要求(CSRF)是一種攻擊,惡意網站會將要求傳送至使用者目前登入的弱點網站Cross-Site Request Forgery (CSRF) is an attack where a malicious site sends a request to a vulnerable site where the user is currently logged in

以下是 CSRF 攻擊的範例:Here is an example of a CSRF attack:

  1. 使用者使用表單驗證登入 www.example.comA user logs into www.example.com using forms authentication.

  2. 伺服器會驗證使用者。The server authenticates the user. 伺服器的回應包含驗證 cookie。The response from the server includes an authentication cookie.

  3. 若未登出,使用者會造訪惡意網站。Without logging out, the user visits a malicious web site. 這個惡意網站包含下列 HTML 表單:This malicious site contains the following HTML form:

    <h1>You Are a Winner!</h1>
      <form action="http://example.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw" />
        <input type="hidden" name="Amount" value="1000000" />
      <input type="submit" value="Click Me"/>
    </form>
    

    請注意,表單動作會張貼到易受攻擊的網站,而不是惡意網站。Notice that the form action posts to the vulnerable site, not to the malicious site. 這是 CSRF 的「跨網站」部分。This is the "cross-site" part of CSRF.

  4. 使用者按一下 [提交] 按鈕。The user clicks the submit button. 瀏覽器會在要求中包含驗證 cookie。The browser includes the authentication cookie with the request.

  5. 此要求會在伺服器上以使用者的驗證內容執行,並可執行已驗證使用者允許執行的任何動作。The request runs on the server with the user's authentication context, and can do anything that an authenticated user is allowed to do.

雖然此範例需要使用者按一下表單按鈕,但惡意的網頁也可以輕鬆地執行自動提交表單的腳本。Although this example requires the user to click the form button, the malicious page could just as easily run a script that submits the form automatically. 此外,使用 SSL 並不會防止 CSRF 攻擊,因為惡意網站可以傳送「HTTPs://」要求。Moreover, using SSL does not prevent a CSRF attack, because the malicious site can send an "https://" request.

一般來說,使用 cookie 進行驗證的網站可能會發生 CSRF 攻擊,因為瀏覽器會將所有相關的 cookie 傳送至目的地網站。Typically, CSRF attacks are possible against web sites that use cookies for authentication, because browsers send all relevant cookies to the destination web site. 不過,CSRF 攻擊並不限於利用 cookie。However, CSRF attacks are not limited to exploiting cookies. 例如,基本和摘要式驗證也很容易受到攻擊。For example, Basic and Digest authentication are also vulnerable. 使用者使用基本或摘要式驗證登入之後。After a user logs in with Basic or Digest authentication. 瀏覽器會自動傳送認證,直到會話結束為止。the browser automatically sends the credentials until the session ends.

防偽標記Anti-Forgery Tokens

為了協助防止 CSRF 攻擊,ASP.NET MVC 會使用防偽權杖,也稱為要求驗證權杖To help prevent CSRF attacks, ASP.NET MVC uses anti-forgery tokens, also called request verification tokens.

  1. 用戶端會要求包含表單的 HTML 網頁。The client requests an HTML page that contains a form.
  2. 伺服器會在回應中包含兩個權杖。The server includes two tokens in the response. 一個權杖會當做 cookie 傳送。One token is sent as a cookie. 另一個則放在隱藏的表單欄位中。The other is placed in a hidden form field. 系統會隨機產生權杖,讓敵人無法猜測值。The tokens are generated randomly so that an adversary cannot guess the values.
  3. 當用戶端提交表單時,必須將這兩個權杖傳回給伺服器。When the client submits the form, it must send both tokens back to the server. 用戶端會傳送 cookie 權杖做為 cookie,並將表單 token 傳送至表單資料內。The client sends the cookie token as a cookie, and it sends the form token inside the form data. (當使用者提交表單時,瀏覽器用戶端會自動執行此工作)。(A browser client automatically does this when the user submits the form.)
  4. 如果要求不包含這兩個權杖,伺服器就不會允許該要求。If a request does not include both tokens, the server disallows the request.

以下是具有隱藏表單標記的 HTML 表單範例:Here is an example of an HTML form with a hidden form token:

<form action="/Home/Test" method="post">
    <input name="__RequestVerificationToken" type="hidden"   
           value="6fGBtLZmVBZ59oUad1Fr33BuPxANKY9q3Srr5y[...]" />    
    <input type="submit" value="Submit" />
</form>

防偽 token 可以使用,因為由於相同來源的原則,惡意網頁無法讀取使用者的權杖。Anti-forgery tokens work because the malicious page cannot read the user's tokens, due to same-origin policies. 相同來源原則會防止裝載于兩個不同網站的檔存取彼此的內容。(Same-origin policies prevent documents hosted on two different sites from accessing each other's content. 因此在先前的範例中,惡意網頁可以將要求傳送至 example.com,但它無法讀取回應。)So in the earlier example, the malicious page can send requests to example.com, but it cannot read the response.)

若要防止 CSRF 攻擊,請使用反偽造權杖搭配任何驗證通訊協定,瀏覽器會在使用者登入之後,以無訊息方式傳送認證。To prevent CSRF attacks, use anti-forgery tokens with any authentication protocol where the browser silently sends credentials after the user logs in. 這包括以 cookie 為基礎的驗證通訊協定,例如表單驗證,以及基本和摘要式驗證等通訊協定。This includes cookie-based authentication protocols, such as forms authentication, as well as protocols such as Basic and Digest authentication.

您應該針對任何 nonsafe 方法(POST、PUT、DELETE)要求防偽 token。You should require anti-forgery tokens for any nonsafe methods (POST, PUT, DELETE). 此外,請確定安全方法(GET、HEAD)沒有任何副作用。Also, make sure that safe methods (GET, HEAD) do not have any side effects. 此外,如果您啟用跨網域支援(例如 CORS 或 JSONP),那麼即使 GET 之類的安全方法也可能容易遭受 CSRF 攻擊,讓攻擊者能夠讀取潛在的敏感性資料。Moreover, if you enable cross-domain support, such as CORS or JSONP, then even safe methods like GET are potentially vulnerable to CSRF attacks, allowing the attacker to read potentially sensitive data.

ASP.NET MVC 中的防偽標記Anti-Forgery Tokens in ASP.NET MVC

若要將防偽標記新增至 Razor 頁面,請使用HtmlHelper. AntiForgeryToken helper 方法:To add the anti-forgery tokens to a Razor page, use the HtmlHelper.AntiForgeryToken helper method:

@using (Html.BeginForm("Manage", "Account")) {
    @Html.AntiForgeryToken()
}

這個方法會新增隱藏的表單欄位,也會設定 cookie token。This method adds the hidden form field and also sets the cookie token.

反 CSRF 和 AJAXAnti-CSRF and AJAX

由於 AJAX 要求可能會傳送 JSON 資料,而不是 HTML 表單資料,因此,表單 token 可能會是 AJAX 要求的問題。The form token can be a problem for AJAX requests, because an AJAX request might send JSON data, not HTML form data. 有一個解決方案是在自訂 HTTP 標頭中傳送權杖。One solution is to send the tokens in a custom HTTP header. 下列程式碼使用 Razor 語法來產生權杖,然後將權杖新增至 AJAX 要求。The following code uses Razor syntax to generate the tokens, and then adds the tokens to an AJAX request. 權杖是在伺服器上藉由呼叫 AntiForgery 來產生的 。 GetTokensThe tokens are generated at the server by calling AntiForgery.GetTokens.

<script>
    @functions{
        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
    }

    $.ajax("api/values", {
        type: "post",
        contentType: "application/json",
        data: {  }, // JSON data goes here
        dataType: "json",
        headers: {
            'RequestVerificationToken': '@TokenHeaderValue()'
        }
    });
</script>

當您處理要求時,請從要求標頭擷取權杖。When you process the request, extract the tokens from the request header. 然後呼叫AntiForgery方法來驗證權杖。Then call the AntiForgery.Validate method to validate the tokens. 如果權杖無效, Validate方法會擲回例外狀況。The Validate method throws an exception if the tokens are not valid.

void ValidateRequestHeader(HttpRequestMessage request)
{
    string cookieToken = "";
    string formToken = "";

    IEnumerable<string> tokenHeaders;
    if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
    {
        string[] tokens = tokenHeaders.First().Split(':');
        if (tokens.Length == 2)
        {
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        }
    }
    AntiForgery.Validate(cookieToken, formToken);
}