1. 程式人生 > >防止ASP.NET Core中的跨站點請求偽造(XSRF / CSRF)攻擊

防止ASP.NET Core中的跨站點請求偽造(XSRF / CSRF)攻擊

命名 oval 描述 new 常用 wrap rtu dagger 濾波器

跨站點請求偽造(也稱為XSRF或CSRF,發音為see-surf)是對Web托管應用程序的攻擊,其中惡意Web應用程序可以影響客戶端瀏覽器與信任該瀏覽器的Web應用程序之間的交互。這些攻擊是可能的,因為Web瀏覽器會在每次向網站發出請求時自動發送某些類型的身份驗證令牌。這種漏洞利用形式也稱為一鍵式攻擊會話,因為攻擊利用了用戶以前經過身份驗證的會話。

CSRF攻擊的一個例子:

  1. 用戶www.good-banking-site.com登錄使用表單身份驗證。服務器對用戶進行身份驗證並發出包含身份驗證cookie的響應。該站點容易受到攻擊,因為它信任它使用有效身份驗證cookie接收的任何請求。

  2. 用戶訪問惡意網站www.bad-crook-site.com

    惡意網站www.bad-crook-site.com包含類似於以下內容的HTML表單:

    HTML
    <h1>Congratulations! You‘re a Winner!</h1>
    <form action="http://good-banking-site.com/api/account" method="post">
        <input type="hidden" name="Transaction" value="withdraw">
        <input type="hidden" name="Amount" value="1000000">
        <input type="submit" value="Click to collect your prize!">
    </form>
    

    請註意,表單的action帖子是易受攻擊的站點,而不是惡意站點。這是CSRF的“跨站點”部分。

  3. 用戶選擇提交按鈕。瀏覽器發出請求並自動包含所請求域的身份驗證cookie www.good-banking-site.com

  4. 請求www.good-banking-site.com使用用戶的身份驗證上下文服務器上運行,並且可以執行允許經過身份驗證的用戶執行的任何操作。

除了用戶選擇提交表單的按鈕的場景之外,惡意站點還可以:

  • 運行自動提交表單的腳本。
  • 將表單提交作為AJAX請求發送。
  • 使用CSS隱藏表單。

除了最初訪問惡意站點之外,這些備選方案不需要用戶的任何操作或輸入。

使用HTTPS不會阻止CSRF攻擊。惡意站點可以https://www.good-banking-site.com/像發送不安全請求一樣輕松地發送請求。

某些攻擊針對響應GET請求的端點,在這種情況下,可以使用圖像標記來執行操作。這種形式的攻擊在允許圖像但阻止JavaScript的論壇網站上很常見。在GET請求中更改狀態的應用程序(其中變量或資源被更改)容易受到惡意攻擊。改變狀態的GET請求是不安全的。最佳做法是永遠不要在GET請求上更改狀態。

對於使用cookie進行身份驗證的Web應用程序,CSRF攻擊是可能的,因為:

  • 瀏覽器存儲由Web應用程序發布的cookie。
  • 存儲的cookie包括經過身份驗證的用戶的會話cookie。
  • 無論在瀏覽器中如何生成應用程序請求,瀏覽器都會將與域關聯的所有Cookie發送到Web應用程序。

但是,CSRF攻擊不僅限於利用cookie。例如,Basic和Digest身份驗證也很容易受到攻擊。用戶使用“基本”或“摘要”身份驗證登錄後,瀏覽器會自動發送憑據,直到會話†結束。

†在此上下文中,會話是指用戶通過身份驗證的客戶端會話。它與服務器端會話或ASP.NET核心會話中間件無關

用戶可以采取預防措施來防範CSRF漏洞:

  • 完成使用後,請註銷Web應用程序。
  • 定期清除瀏覽器cookie。

但是,CSRF漏洞基本上是Web應用程序的問題,而不是最終用戶。

身份驗證基礎知識

基於Cookie的身份驗證是一種流行的身份驗證形式。基於令牌的身份驗證系統越來越受歡迎,特別是對於單頁應用程序(SPA)。

當用戶使用其用戶名和密碼進行身份驗證時,會向他們發放一個令牌,其中包含可用於身份驗證和授權的身份驗證票證。令牌存儲為cookie,伴隨客戶端發出的每個請求。生成和驗證此cookie由Cookie身份驗證中間件執行。所述中間件串行化一個用戶主要成加密的cookie。在後續請求中,中間件驗證cookie,重新創建主體,並將主體分配給HttpContextUser屬性

基於令牌的身份驗證

用戶通過身份驗證後,會發出令牌(而不是防偽令牌)。令牌包含聲明形式的用戶信息或參考令牌,用於將應用指向應用中維護的用戶狀態。當用戶嘗試訪問需要身份驗證的資源時,會使用Bearer令牌形式的附加授權標頭將令牌發送到應用程序。這使得app無國籍。在每個後續請求中,令牌都在服務器端驗證請求中傳遞。此令牌未加密 ; 它是編碼的在服務器上,令牌被解碼以訪問其信息。要在後續請求中發送令牌,請將令牌存儲在瀏覽器的本地存儲中。如果令牌存儲在瀏覽器的本地存儲中,請不要擔心CSRF漏洞。當令牌存儲在cookie中時,CSRF是一個問題。

在一個域托管多個應用

共享托管環境容易受到會話劫持,登錄CSRF和其他攻擊。

雖然example1.contoso.net並且example2.contoso.net是不同的主機,但*.contoso.net下的主機之間存在隱式信任關系此隱式信任關系允許可能不受信任的主機影響彼此的cookie(管理AJAX請求的同源策略不一定適用於HTTP cookie)。

可以通過不共享域來防止在同一域上托管的應用之間利用可信cookie的攻擊。當每個應用程序托管在其自己的域上時,不會使用隱式cookie信任關系。

ASP.NET Core防偽配置

警告

ASP.NET Core使用ASP.NET Core Data Protection實現防偽必須將數據保護堆棧配置為在服務器場中工作。有關更多信息,請參閱配置數據保護

在ASP.NET Core 2.0或更高版本中,FormTagHelper將防偽標記註入HTML表單元素。Razor文件中的以下標記會自動生成防偽標記:

CSHTML
<form method="post">
    ...
</form>

類似地,如果表單的方法不是GET IHtmlHelper.BeginForm默認生成防偽標記。

<form>標記包含method="post"屬性並且以下任一情況為真時,就會自動生成HTML表單元素的防偽標記

  • action屬性為empty(action="")。
  • 未提供action屬性(<form method="post">)。

可以禁用為HTML表單元素自動生成防偽標記:

  • 使用以下asp-antiforgery屬性顯式禁用防偽令牌

    CSHTML
    <form method="post" asp-antiforgery="false">
        ...
    </form>
    
  • 使用Tag Helper,表單元素選擇了Tag Helpers 選擇退出符號

    CSHTML
    <!form method="post">
        ...
    </!form>
    
  • FormTagHelper從視圖中刪除FormTagHelper可以從通過添加以下指令到剃刀圖的圖中移除:

    CSHTML
    @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
    

註意

Razor頁面會自動受到XSRF / CSRF的保護。有關更多信息,請參閱XSRF / CSRF和Razor頁面

防禦CSRF攻擊的最常用方法是使用同步器令牌模式(STP)。當用戶請求包含表單數據的頁面時,將使用STP:

  1. 服務器將與當前用戶身份相關聯的令牌發送到客戶端。
  2. 客戶端將令牌發送回服務器以進行驗證。
  3. 如果服務器收到與經過身份驗證的用戶身份不匹配的令牌,則拒絕該請求。

令牌是唯一且不可預測的。令牌還可用於確保一系列請求的正確排序(例如,確保請求序列:第1頁 - 第2頁 - 第3頁)。ASP.NET Core MVC和Razor Pages模板中的所有表單都會生成防偽令牌。以下一對視圖示例生成防偽令牌:

CSHTML
<form asp-controller="Manage" asp-action="ChangePassword" method="post">
    ...
</form>

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

<form>不使用帶有HTML幫助器的標簽助手@ Html.AntiForgeryToken的情況下,明確地向元素添加防偽標記

CSHTML
<form action="/" method="post">
    @Html.AntiForgeryToken()
</form>

在上述每種情況下,ASP.NET Core都會添加一個類似於以下內容的隱藏表單字段:

CSHTML
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw">

ASP.NET Core包含三個用於處理防偽令牌的過濾器

  • ValidateAntiForgeryToken
  • AutoValidateAntiforgeryToken
  • IgnoreAntiforgeryToken

防偽選項

定制防偽選項Startup.ConfigureServices

C#
services.AddAntiforgery(options => 
{
    // Set Cookie properties using CookieBuilder properties†.
    options.FormFieldName = "AntiforgeryFieldname";
    options.HeaderName = "X-CSRF-TOKEN-HEADERNAME";
    options.SuppressXFrameOptionsHeader = false;
});

Cookie使用CookieBuilder的屬性設置防偽屬性

選項描述
曲奇餅 確定用於創建防偽cookie的設置。
FormFieldName 防偽系統用於在視圖中呈現防偽令牌的隱藏表單字段的名稱。
HeaderName 防偽系統使用的標頭名稱。如果null,系統僅考慮表單數據。
SuppressXFrameOptionsHeader 指定是否禁止生成X-Frame-Options標頭。默認情況下,標頭生成的值為“SAMEORIGIN”。默認為false

有關更多信息,請參閱CookieAuthenticationOptions

使用IAntiforgery配置防偽功能

IAntiforgery提供API來配置防偽功能。IAntiforgery可以ConfigureStartup班級方法中申請以下示例使用應用程序主頁中的中間件生成防偽令牌,並將其作為cookie發送到響應中(使用本主題後面描述的默認Angular命名約定):

C#
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery)
{
    app.Use(next => context =>
    {
        string path = context.Request.Path.Value;

        if (
            string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
            string.Equals(path, "/index.html", StringComparison.OrdinalIgnoreCase))
        {
            // The request token can be sent as a JavaScript-readable cookie, 
            // and Angular uses it by default.
            var tokens = antiforgery.GetAndStoreTokens(context);
            context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, 
                new CookieOptions() { HttpOnly = false });
        }

        return next(context);
    });
}

需要防偽驗證

ValidateAntiForgeryToken是一個動作過濾器,可應用於單個操作,控制器或全局。除非請求包含有效的防偽令牌,否則將阻止對已應用此過濾器的操作發出的請求。

C#
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account)
{
    ManageMessageId? message = ManageMessageId.Error;
    var user = await GetCurrentUserAsync();

    if (user != null)
    {
        var result = 
            await _userManager.RemoveLoginAsync(
                user, account.LoginProvider, account.ProviderKey);

        if (result.Succeeded)
        {
            await _signInManager.SignInAsync(user, isPersistent: false);
            message = ManageMessageId.RemoveLoginSuccess;
        }
    }

    return RedirectToAction(nameof(ManageLogins), new { Message = message });
}

ValidateAntiForgeryToken屬性需要一個令牌來請求它所裝飾的動作方法,包括HTTP GET請求。如果該ValidateAntiForgeryToken屬性應用於應用程序的控制器,則可以使用該IgnoreAntiforgeryToken屬性覆蓋該屬性。

註意

ASP.NET Core不支持自動將防偽標記添加到GET請求。

僅針對不安全的HTTP方法自動驗證防偽令牌

ASP.NET Core應用程序不會為安全HTTP方法(GET,HEAD,OPTIONS和TRACE)生成防偽令牌。可以使用AutoValidateAntiforgeryToken屬性而不是廣泛應用ValidateAntiForgeryToken屬性然後用IgnoreAntiforgeryToken屬性覆蓋它此屬性與屬性的工作方式相同,只是它不需要使用以下HTTP方法發出的請求的令牌:ValidateAntiForgeryToken

  • 得到
  • OPTIONS
  • 跟蹤

我們建議AutoValidateAntiforgeryToken廣泛使用非API方案。這可確保默認情況下保護POST操作。替代方法是默認忽略防偽標記,除非ValidateAntiForgeryToken應用於單個操作方法。在這種情況下,更有可能的是,POST操作方法不會被錯誤保護,使應用程序容易受到CSRF攻擊。所有POST都應該發送防偽令牌。

API沒有用於發送令牌的非cookie部分的自動機制。實現可能取決於客戶端代碼實現。一些例子如下所示:

類級示例:

C#
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{

全球範例:

C#
services.AddMvc(options => 
    options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()));

覆蓋全局或控制器防偽屬性

所述IgnoreAntiforgeryToken濾波器用於消除對於給定動作(或控制器)的防偽標記的需要。應用時,此過濾器將覆蓋更高級別(全局或控制器)上指定的過濾器ValidateAntiForgeryTokenAutoValidateAntiforgeryToken過濾器。

C#
[Authorize]
[AutoValidateAntiforgeryToken]
public class ManageController : Controller
{
    [HttpPost]
    [IgnoreAntiforgeryToken]
    public async Task<IActionResult> DoSomethingSafe(SomeViewModel model)
    {
        // no antiforgery token required
    }
}

身份驗證後刷新令牌

通過將用戶重定向到視圖或Razor Pages頁面來驗證用戶身份後,應刷新令牌。

JavaScript,AJAX和SPA

在傳統的基於HTML的應用程序中,使用隱藏的表單字段將防偽令牌傳遞給服務器。在現代基於JavaScript的應用程序和SPA中,許多請求都是以編程方式進行的。這些AJAX請求可以使用其他技術(例如請求標頭或cookie)來發送令牌。

如果cookie用於存儲身份驗證令牌並驗證服務器上的API請求,則CSRF是一個潛在的問題。如果使用本地存儲來存儲令牌,則可能會減輕CSRF漏洞,因為本地存儲中的值不會隨每個請求自動發送到服務器。因此,建議使用本地存儲在客戶端上存儲防偽令牌並將令牌作為請求頭發送。

JavaScript的

將JavaScript與視圖一起使用,可以使用視圖中的服務創建令牌。Microsoft.AspNetCore.Antiforgery.IAntiforgery服務註入視圖並調用GetAndStoreTokens

C#
@{
    ViewData["Title"] = "AJAX Demo";
}
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

<input type="hidden" id="RequestVerificationToken" 
       name="RequestVerificationToken" value="@GetAntiXsrfRequestToken()">

<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>

<div class="row">
    <p><input type="button" id="antiforgery" value="Antiforgery"></p>
    <script>
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function() {
            if (xhttp.readyState == XMLHttpRequest.DONE) {
                if (xhttp.status == 200) {
                    alert(xhttp.responseText);
                } else {
                    alert(‘There was an error processing the AJAX request.‘);
                }
            }
        };

        document.addEventListener(‘DOMContentLoaded‘, function() {
            document.getElementById("antiforgery").onclick = function () {
                xhttp.open(‘POST‘, ‘@Url.Action("Antiforgery", "Home")‘, true);
                xhttp.setRequestHeader("RequestVerificationToken", 
                    document.getElementById(‘RequestVerificationToken‘).value);
                xhttp.send();
            }
        });
    </script>
</div>

這種方法消除了直接處理從服務器設置cookie或從客戶端讀取cookie的需要。

上面的示例使用JavaScript來讀取AJAX POST標頭的隱藏字段值。

JavaScript還可以訪問cookie中的令牌,並使用cookie的內容創建帶有令牌值的標頭。

C#
context.Response.Cookies.Append("CSRF-TOKEN", tokens.RequestToken, 
    new Microsoft.AspNetCore.Http.CookieOptions { HttpOnly = false });

假設腳本請求在調用的標頭中發送令牌X-CSRF-TOKEN,請配置防偽服務以查找X-CSRF-TOKEN標頭:

C#
services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");

以下示例使用JavaScript通過適當的標頭發出AJAX請求:

JavaScript的
function getCookie(cname) {
    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(‘;‘);
    for(var i = 0; i <ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ‘ ‘) {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

var csrfToken = getCookie("CSRF-TOKEN");

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (xhttp.readyState == XMLHttpRequest.DONE) {
        if (xhttp.status == 200) {
            alert(xhttp.responseText);
        } else {
            alert(‘There was an error processing the AJAX request.‘);
        }
    }
};
xhttp.open(‘POST‘, ‘/api/password/changepassword‘, true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader("X-CSRF-TOKEN", csrfToken);
xhttp.send(JSON.stringify({ "newPassword": "ReallySecurePassword999$$$" }));

AngularJS

AngularJS使用約定來解決CSRF。如果服務器發送帶有名稱的cookie XSRF-TOKEN,則AngularJS $http服務在向服務器發送請求時將cookie值添加到標頭中。這個過程是自動的。標頭不需要顯式設置。標題名稱是X-XSRF-TOKEN服務器應檢測此標頭並驗證其內容。

對於ASP.NET Core API,使用此約定:

  • 配置您的應用以在名為cookie的cookie中提供令牌XSRF-TOKEN
  • 配置防偽服務以查找名為的標頭X-XSRF-TOKEN
C#
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

查看或下載示例代碼如何下載

擴展防偽

所述IAntiForgeryAdditionalDataProvider類型允許開發者通過往返附加數據在每個令牌延長反CSRF系統的行為。GetAdditionalData方法被調用時產生的場令牌每次,並且返回值被嵌入所生成的令牌內。實現者可以返回時間戳,隨機數或任何其他值,然後在驗證令牌時調用ValidateAdditionalData以驗證此數據。客戶端的用戶名已嵌入生成的令牌中,因此無需包含此信息。如果令牌包括補充數據但未IAntiForgeryAdditionalDataProvider配置,則不驗證補充數據。

防止ASP.NET Core中的跨站點請求偽造(XSRF / CSRF)攻擊