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. 🙂
Szymon
13 czerwca 2019 at 16:54Dobrze, 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)?
Paweł Skaruz
14 czerwca 2019 at 12:50Cześć!
Myślę, że jest to możliwe. Wystarczy, że na poczatku zrobisz zapytanie GET po token, a następnie użyjesz tego tokena w headerze przy kolejnych zapytaniach. Mogę polecić tego blog posta jak to zrobić https://odetocode.com/blogs/scott/archive/2017/02/06/anti-forgery-tokens-and-asp-net-core-apis.aspx