ASP.NET Core vs ataki typu XSRF (OWASP Top 10 #2)

Czas czytania ~ 150 sekund

Kolejny wpis pokazujący, jak radzić sobie z atakiem z listy OWASP Top 10. Poprzednim razem analizowaliśmy przekierowania, tym razem na tapetę trafiają ataki typu Cross Site Request Forgery, czyli w skrócie CSRF lub XSRF.

Ataki te mogą być często niedoceniane przez programistów, głównie ze względu na to, że wymagają stworzenia mechanizmu dedykowanego dla naszej strony. Mimo wszystko nie warto ich bagatelizować, bo straty po stronie użytkowników mogą być spore.

Mechanizm ataku

Na początku warto przypomnieć kilka mniej lub bardziej oczywistych faktów na temat działania przeglądarek internetowych

  • po pomyślnym zalogowaniu na stronie x, użytkownik dostaje cookie reprezentujące jego sesję serwerową, które będzie doklejane przez przeglądarkę do każdego kolejnego zapytania do tej strony x
  • strona hostowana pod adresem y domyślnie może wykonywać żądania HTTP pod adres x (nie licząc zapytań AJAX, przed którymi domyślnie chroni mechanizm Same Origin Policy)
  • formularz HTML (element form) można przesłać na serwer metodami GET oraz POST (domyślnie)
  • zatwierdzenie formularza może odbyć się z poziomu JavaScript (funkcja submit)

Teraz przeczytajmy jeszcze raz nazwę ataku. Chodzi o fałszowanie żądań, które można wykonywać na stronie innej niż ta podatna na ataki.

Mamy dwie strony: pierwsza z nich jest dobrze znana użytkownikowi (np. jego bank) i zawiera formularz, którego zatwierdzenie wykonuje pewną akcję serwerową. Druga strona (pod zupełnie inną domeną), kontrolowana przez atakującego, na którą wchodzi ofiara. Kolejność zdarzeń będzie następująca:

  1. Ofiara (użytkownik) loguje się na stronę swojego banku
  2. Serwer zwraca cookie użytkownikowi, otwierana jest sesja tego użytkownika
  3. Atakujący nakłania ofiarę do odwiedzenia swojej strony (np. wysyła link)
  4. Ofiara wchodzi na tą stronę, od razu po wejściu na nią zatwierdza się formularz HTTP, którego akcja ustawiona jest na stronę banku, zgadzają się też wszystkie inputy formularza
  5. Przeglądarka dokleja cookie (w końcu użytkownik przed chwilą się tam logował) i wysyła żądanie do serwera
  6. Serwer dostaje poprawne dane i cookie zalogowanego użytkownika, akcja zostaje wykonana

Jak może wyglądać taki, samozatwierdzający się formularz ? Np. tak:

abc

Oczywiście chwila zabawy z CSS-ami wystarczy, by użytkownik nie zobaczył nic świadczącego o zatwierdzeniu formularza, a jego uwagę możemy odciągnąć na swojej stronie czym innym (np. GIF z kotem).

Mamy tu więc sfałszowany formularz, w którym wszystkie inputy muszą być tak ustawione, by przejść walidację serwerową.

AntiForgery Tokens

Jak już zostało wspomniane, formularz jest sfałszowany i nie został wygenerowany przez właściwy serwer, do którego wysyła dane. Wystarczy więc na tym serwerze wykrywać sam fakt sfałszowania i odrzucać takie żądania po stronie serwera. Przeważnie stosuje się w tym celu mechanizm AntiForgery tokens:

  1. Przy generowaniu formularza wstawiamy dodatkowy ukryty input z losowo wygenerowanym kluczem
  2. Przy próbie zatwierdzenie walidujemy, czy klucz jest i czy został wygenerowany przez nas

Na szczęście w większości frameworków nie musimy robić tego ręcznie. W nowym ASP.NET formularze możemy w Razorze sprytnie budować przy użyciu mechanizmu Tag Helpers (podajemy nazwę kontrolera i jego metodę):

<form asp-controller="Home" asp-action="TransferFunds" method="post">
    Amount:  <input asp-for="Amount" /> <br />
    Account: <input asp-for="Account" /><br />
    Title: <input asp-for="Title" /><br />
    <button type="submit">Submit</button>
</form>

Jako parametr metody kontrolera przyjmujemy prostą klasę z trzema polami. Używając prostych tag helperów asp-for wygeneruje nam się taki formularz:

www

A więc nawet jeśli nie mamy pojęcia o atakach CSRF, ASP.NET Core i tak doda do formularza dodatkowy token. Ale sam token nie chroni nas jeszcze przed atakami. Aby zabezpieczyć się przed atakami musimy użyć atrybutu ValidateAntiForgeryToken nad metodą kontrolera.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult TransferFunds(BankTransaction request)
{
    //do something
    return RedirectToAction("Home");
}

W takim wypadku serwer zwróci błąd 400 / Bad Request jeśli zabraknie tokenu.

Jeśli podejrzymy Fiddlerem wiadomość wysyłaną przez przeglądarkę, możemy zobaczyć pewne podobieństwa (choć sam token nie zależy jedynie od cookie):

POST http://localhost:50075/Home/TransferFunds HTTP/1.1
Host: localhost:50075
Connection: keep-alive
Content-Length: 211
Cache-Control: max-age=0
Origin: http://localhost:50075
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://localhost:50075/Home/Contact
Accept-Encoding: gzip, deflate, br
Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: .AspNetCore.Antiforgery.UsIcT1a_FBw=CfDJ8IUYy9foNTJLpclI1imP2wUSpdfo3QC8Dz3W4nQZUlT1z1_NZDlXktSSmDEutZ3k56Yht8k48Rh2MEAQFZcNAzwQYv656a2qD-w_nWrKp-WgQ_lomBt4uOqFXwOHRngtO-3R3k36LJKEGfKf1nsv-hs

Amount=123&Account=a&Title=b&__RequestVerificationToken=CfDJ8IUYy9foNTJLpclI1imP2wU6d3ofN_DBzWN81TBOoeXusvKNU-F7nRYnIiqTJLd1enjPCO83pub0MWAXN5-F7b0wNa1rMGe7Hu0E40dEqmUYBE2neMpOViGWi0cp9lUu3ftiX7By1EfiLEWrexSjevA
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s