Zapobieganie atakom fałszowania żądań między lokacjami (CSRF) w aplikacji MVC ASP.NET

Fałszerzować żądania między witrynami (CSRF) jest atakiem, w którym złośliwa witryna wysyła żądanie do podatnej na zagrożenia lokacji, w której użytkownik jest obecnie zalogowany

Oto przykład ataku CSRF:

  1. Użytkownik loguje się przy www.example.com użyciu uwierzytelniania formularzy.

  2. Serwer uwierzytelnia użytkownika. Odpowiedź z serwera zawiera plik cookie uwierzytelniania.

  3. Bez wylogowywania użytkownik odwiedza złośliwą witrynę internetową. Ta złośliwa witryna zawiera następujący formularz HTML:

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

    Zwróć uwagę, że akcja formularza publikuje w witrynie podatnej na zagrożenia, a nie złośliwą witrynę. Jest to część CSRF "cross-site".

  4. Użytkownik kliknie przycisk Prześlij. Przeglądarka zawiera plik cookie uwierzytelniania z żądaniem.

  5. Żądanie jest uruchamiane na serwerze z kontekstem uwierzytelniania użytkownika i może wykonywać wszystkie czynności, które może wykonać uwierzytelniony użytkownik.

Mimo że ten przykład wymaga kliknięcia przycisku formularza przez użytkownika, złośliwa strona może równie łatwo uruchomić skrypt, który automatycznie przesyła formularz. Ponadto użycie protokołu SSL nie zapobiega atakowi CSRF, ponieważ złośliwa witryna może wysłać żądanie "https://".

Zazwyczaj ataki CSRF są możliwe w przypadku witryn internetowych, które używają plików cookie do uwierzytelniania, ponieważ przeglądarki wysyłają wszystkie odpowiednie pliki cookie do docelowej witryny sieci Web. Jednak ataki CSRF nie są ograniczone do wykorzystywania plików cookie. Na przykład uwierzytelnianie podstawowe i szyfrowane jest również podatne na zagrożenia. Po zalogowaniu się użytkownika przy użyciu uwierzytelniania podstawowego lub szyfrowego. przeglądarka automatycznie wysyła poświadczenia do momentu zakończenia sesji.

Tokeny ochrony przed fałszerzami

Aby zapobiec atakom CSRF, ASP.NET MVC używa tokenów ochrony przed fałszercją, nazywanych również tokenami weryfikacji żądań.

  1. Klient żąda strony HTML zawierającej formularz.
  2. Serwer zawiera dwa tokeny w odpowiedzi. Jeden token jest wysyłany jako plik cookie. Druga jest umieszczana w ukrytym polu formularza. Tokeny są generowane losowo, aby przeciwnik nie mógł odgadnąć wartości.
  3. Gdy klient przesyła formularz, musi wysłać oba tokeny z powrotem do serwera. Klient wysyła token pliku cookie jako plik cookie i wysyła token formularza wewnątrz danych formularza. (Klient przeglądarki automatycznie wykonuje to po przesłaniu formularza przez użytkownika).
  4. Jeśli żądanie nie zawiera obu tokenów, serwer nie zezwala na żądanie.

Oto przykład formularza HTML z ukrytym tokenem formularza:

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

Tokeny chroniące przed fałszerzami działają, ponieważ złośliwa strona nie może odczytać tokenów użytkownika z powodu zasad tego samego źródła. (Zasady tego samego źródła uniemożliwiają dostęp do zawartości dokumentów hostowanych w dwóch różnych witrynach. W poprzednim przykładzie złośliwa strona może wysyłać żądania do example.com, ale nie może odczytać odpowiedzi).

Aby zapobiec atakom CSRF, użyj tokenów ochrony przed fałszercją z dowolnym protokołem uwierzytelniania, w którym przeglądarka dyskretnie wysyła poświadczenia po zalogowaniu się użytkownika. Obejmuje to protokoły uwierzytelniania opartego na plikach cookie, takie jak uwierzytelnianie formularzy, a także protokoły, takie jak uwierzytelnianie podstawowe i szyfrowe.

Należy wymagać tokenów ochrony przed fałszercją dla wszelkich metod niezabezpieczonych (POST, PUT, DELETE). Upewnij się również, że bezpieczne metody (GET, HEAD) nie mają żadnych skutków ubocznych. Ponadto jeśli włączysz obsługę między domenami, taką jak CORS lub JSONP, nawet bezpieczne metody, takie jak GET, są potencjalnie narażone na ataki CSRF, umożliwiając atakującemu odczytywanie potencjalnie poufnych danych.

Tokeny ochrony przed fałszerzami w ASP.NET MVC

Aby dodać tokeny chroniące przed fałszertwem do strony Razor, użyj metody pomocnika HtmlHelper.AntiForgeryToken :

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

Ta metoda dodaje pole ukrytego formularza, a także ustawia token pliku cookie.

Anti-CSRF i AJAX

Token formularza może być problemem dla żądań AJAX, ponieważ żądanie AJAX może wysyłać dane JSON, a nie dane formularza HTML. Jednym z rozwiązań jest wysłanie tokenów w niestandardowym nagłówku HTTP. Poniższy kod używa składni Razor do generowania tokenów, a następnie dodaje tokeny do żądania AJAX. Tokeny są generowane na serwerze przez wywołanie 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>

Podczas przetwarzania żądania wyodrębnij tokeny z nagłówka żądania. Następnie wywołaj metodę AntiForgery.Validate , aby zweryfikować tokeny. Metoda Validate zgłasza wyjątek, jeśli tokeny są nieprawidłowe.

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