Blog posts

Bezpieczna aplikacja ASP.NET Core (cz. I) – atak CSRF

Bezpieczna aplikacja ASP.NET Core (cz. I) – atak CSRF

ASP.NET Core Security, Daj Się Poznać 2017, dotnetcore, security

Gdy zaczynałem przygodę z aplikacjami webowymi, nie sądziłem, że istnieje aż tyle zagrożeń, na które trzeba zwrócić uwagę. Współczesne frameworki dają same w sobie wiele zabezpieczeń i o niektóre rzeczy nie musimy się już troszczyć. Jestem jednak pewny, że bardzo ważna jest świadomość niebezpieczeństwa na jakie są wystawione nasze aplikacje, a co dalej idzie nasi klienci.

Tutaj narodził się pomysł serii „Bezpieczna aplikacja ASP.NET Core”. W każdej z części chciałbym opisać zagrożenie oraz sposób w jaki się przed nim bronić. Bez owijania w bawełnę czas na pierwsze z nich – atak CSRF.

Czym jest CSRF?

Cross-site request forgery (CSRF lub XSRF) – jest to metoda ataku na serwis internetowy. Ofiarami tego ataku stają się często użytkownicy, którzy nieświadomie przesyłają do serwera żądania spreparowane przez inne osoby. Zazwyczaj celem ataku jest wykorzystanie uprawnień ofiary do wykonania operacji w systemie.

Cechy, które charakteryzują ten atak to:

  • Zazwyczaj dotyczą serwisów, które wymagają zalogowania się
  • Niewidoczne zmuszenie użytkownika (jego przeglądarki) do wykonania żądania HTTP
  • Żądanie polega na wykonaniu w jego imieniu jakiejś operacji zmieniającej stan systemu

Przykład ataku:

 <h1>Wygrałeś iPhone 7!</h1>
   <form action="http://pawelskaruz.pl/Accoun/ChangePassword" method="post">
       <input type="hidden" name="Password" value="password" />
       <input type="submit" value="Kliknj aby odebrać nagrodę"/>
   </form>

ASP.NET Antiforgery, czyli obrona

ASP.NET Core zawiera pakiet pod nazwą Antiforgery, który zapewnia ochronę przed atakami CSRF. Pakiet ten implementuje rozwiązanie oparte na tokenach, zalecane przez organizację OWASP. Specyfikacja tego zalecenia znajduje się tutaj.

W skrócie polega ono na mechanizmie wykorzystującym token set (2 tokeny). Powinny się one znaleźć w każdym zapytaniu trafiającym do walidatora Antiforgery. Set składa się z:

  • Tokenu przechowywanego w formie ciasteczka; jest on pseudolosowy i zaszyfrowany przy użyciu Data Protection API
  • Drugiego tokenu, który powinien znaleźć się w polu formularza, nagłówku żądania lub w innym ciasteczku

Tokeny generowane są po stronie serwera i zwracane do przeglądarki podczas odpowiedzi HTTP.

Walidator Antiforgery odrzuci request jeśli:

  • wygenerowane wartości tokenów się nie zgadzają
  • którykolwiek z tokenów nie został przesłany w odpowiedzi

Jak korzystać z pakietu Antiforgery?

Pakiet Microsoft.AspNetCore.Antiforgery jest dostępny jako zależność pakietu Microsoft.AspNetCore.Mvc. Otrzymujemy go w standardzie tworząc aplikację ASP.NET MVC. Serwisy Antiforgery są automatycznie rejestrowane wraz z serwisami MVC, czyli podczas wywołania services.addMvc() w metodzie Startup.ConfigureServices() .

Zacznijmy od początku, czyli generowania tokenów. Dobra wiadomość, ponieważ tokeny generują się automatycznie, o ile korzystamy z tag helperów do utworzenia formularza. Dla przykładu, formularz:

<form asp-controller="Test" asp-action="Upload">
    <button type="submit">Wyślij</button>
</form>

oraz

@using (Html.BeginForm("Upload", "Test"))
{
   <button type="submit">Wyślij</button>
}

Spowoduje wygenerowanie:

<form action="/Test/Upload" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Mz65afQqgxLo0q34z89hfKdLf4oIbjF8yZyh_XqrJ4yvZK2JmiG03LnjHlP-_g70QdMN2G1udP-oe3vn0mDLvyWxzLgdVPOz5DnnUAgi7UDP_kqdGd9LpvikQZZP5zcnzo3VvBR-wDsAllpxbx7D1c">
   <button type="submit">Wyślij</button>
</form>

Jeśli natomiast nie korzystamy z wyżej wymienionych helperów do tworzenia formularzy, możemy skorzystać z tag helpera @Html.AntiForgeryToken() , który wygeneruje ukryte pole tokenu.

Zapytania AJAX’owe

W przypadku aplikacji SPA często wykonujemy zapytania AJAX’owe. Wtedy nie wysyłamy formularza, ale np. pojedynczą wartość. Istnieje również możliwość zabezpieczenia tych zapytań przed atakami CSRF. Wystarczy, aby token umieścić w nagłówku zapytania. Przykład:

@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Xsrf
@functions{
    public string GetAntiXsrfRequestToken()
    {
        return Xsrf.GetAndStoreTokens(Context).RequestToken;
    }
}

<script>
   $("#button").click(function () {
      $.ajax({
         type: "POST",
         dataType: "html",
         headers:
         {
            "RequestVerificationToken": '@GetAntiXsrfRequestToken()'
         },
         url: '@Url.Action("Upload", "Test")'
       });
    });
</script>

A co z ciasteczkami?

Pakiet Antiforgery traktuje oba tokeny jako set, więc „ciasteczkowy” token generowany jest automatycznie wraz z polami w formularzu. Następnie jest on wysyłany do przeglądarki. Warto wspomnieć o kilku ważnych rzeczach:

  • Ciasteczko antiforgery jest oznaczone jako http only, więc nie jest dostępne z poziomu JavaScriptu
  • W przypadku kilku formularzy na stronie, ciasteczko generowane jest tylko raz i jest zgodne dla wszystkich tokenów w formularzach
  • Nazwa ciasteczka to „.AspNetCore.AntiForgery.#„, gdzie # to hash nazwy aplikacji. Nazwa ta może zostać zmieniona w konfiguracji
  • Jeśli chcemy zapewnić wymóg SSL, wystarczy ustawić RequireSsl na true. Ustawi to flagę secure ciasteczka

Walidacja tokenów

Do walidacji poprawności tokenów dostarczone zostały dwa filtry: ValidateAntiForgeryToken, AutoValidateAntiforgeryToken. Filtry te sprawdzają tokeny, a następnie przepuszczają zapytanie do serwera lub je odrzucają.

ValidateAntiForgeryToken

Jest to filtr, który możemy wykorzystać w akcji kontrolera, całym kontrolerze lub globalnie w aplikacji. Jego użycie np. w kontrolerze spowoduje walidację wszystkich akcji (również GET).

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Upload(UploadViewModel vm)
{
    ...
}

Jeśli chcemy zignorować walidację dla pojedynczej akcji, możemy ją oznaczyć filtrem IgnoreAntiforgeryToken.

AutoValidateAntiforgeryToken

Działa podobnie jak ValidateAntiForgeryToken, ale nie powoduje walidacji requestów następujących typów:

  • GET
  • HEAD
  • OPTIONS
  • TRACE

Użycie dla kontrolera:

[AutoValidateAntiforgeryToken]
public class TestController : Controller
{
   ...
}

Możemy również dodać ten filtr globalnie dla aplikacji

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

Zalecane jest używanie globalnie AutoValidateAntiforgeryToken, lub ValidateAntiForgeryToken dla konkretnych akcji kontrolora.

Konfiguracja Antyforgery

Konfiguracji możemy dokonać podczas rejestrowania serwisu. Do ustawienia jest wiele opcji bardzo dobrze opisanych w dokumentacji. Przykład konfiguracji:

services.AddAntiforgery(options => 
{
  options.CookieDomain = "pawelskaruz.pl";
  options.CookieName = "X-CSRF-TOKEN";
  options.CookiePath = "/";
  options.FormFieldName = "AntiforgeryToken";
  options.HeaderName = "X-CSRF-TOKEN";
  options.RequireSsl = false;
  otpions.SuppressXFrameOptionsHeader = false;
});

Podsumowanie

Mam nadzieje, że w przystępny sposób przedstawiłem czym jest atak CSRF, oraz jak się przed nim bronić. Wiele rzeczy mamy dostępnych od razu podczas projektowania aplikacji. Wystarczy więc kilka linijek kodu, aby nasza aplikacja była bezpieczniejsza.

A na koniec serdecznie zapraszam do odwiedzania bloga. Będą pojawiać się kolejne części serii „Bezpieczna aplikacja ASP.NET Core” oraz inne wskazówki jak uczynić nasze aplikacje .NET’owe bardziej bezpiecznymi. 🙂

About the author

Senior specialist, developer. Pasjonat programowania rozwijający się przy projektach komercyjnych oraz jako kontrybutor open source. W wolnym czasie lubi pobiegać za piłką, a także obejrzeć dobry film lub serial.

3 Comments

  1. Szymon
    13 czerwca 2019 at 16:54
    Reply

    Dobrze, a co w sytuacji, gdy mam aplikację webową zabezpieczoną w ten sposób, i chciałbym utworzyć jej wersję mobilną (aplikację natywną). Czy mogę w jakiś sposób zmodyfikować ten mechanizm, aby dopuszczał on requesty z aplikacji mobilnej (np. dodany jakiś header przez HttpClienta)?

Pozostaw odpowiedź Szymon Anuluj pisanie odpowiedzi

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *