ASP.NET MVC: Prawidłowa obsługa wyjątków AntiForgery

ASP.NET MVC dba o to, aby niemożliwe było dokonanie ataku CSRF. Nie musimy sami generować tokenów i wszystko zostanie obsłużone przez framework. Niestety czasami taka obsługa nie jest zbyt user-friendly. Ostatnio spotkałem następujący scenariusz:

  • Otworzyłem stronę do logowania w dwóch osobnych tabach. Sesja jest więc współdzielona.
  • W  pierwszej z nich, kliknąłem loguj. Token w tym momencie na serwerze jest unieważniany.
  • W drugim tabie token wygenerowany i przechowany w ukrytym polu, nie pokrywa się już z tym na serwerze. Z tego względu, klikając loguj otrzymamy wyjątek: “The provided anti-forgery token was meant for user "", but the current user is "test".
  • Po zalogowaniu się, serwer usuwa wszystkie tokeny, które przechowuje w sesji. Nic dziwnego, że drugie zapytanie jest traktowane jako potencjalny atak. Niektóre strony (gmail, hotmail) mają powiadomienia, które umożliwiają automatycznie przekierowanie strony z drugiej zakładki, tak więc nie będzie możliwe kliknięcie drugi raz loguj. Inne podejście to implementacja filtra, który nas przekieruje do konkretnej strony np.:

    public class AntiForgeryHandler : HandleErrorAttribute
    {        
       public override void OnException(ExceptionContext filterContext)
       {
           if (filterContext.Exception is HttpAntiForgeryException &&
               filterContext.HttpContext.User.Identity.IsAuthenticated)
           {
               filterContext.HttpContext.Response.StatusCode = 200;             
               filterContext.ExceptionHandled = true;
    
               var urlHelper = new UrlHelper(filterContext.RequestContext);
               filterContext.HttpContext.Response.Redirect(urlHelper.Action("InvalidToken","Account"), true);
           }
           else
           {
               base.OnException(filterContext);
           }
       }
    }

    Filtr wyłapie wyjątek HttpAntiForgeryException i przekieruje użytkownika do jakieś akcji, która może wyświetlić komunikat o błędzie. Filtr nakładamy na akcję LogIn:

    [HttpPost]
    [AllowAnonymous]
    [AntiForgeryHandler]
    [ValidateAntiForgeryToken]
    public ActionResult Login(LoginModel model, string returnUrl)
    {
        // reszta logiki tutaj
    }

    Rozwiązanie nieperfekcyjne ale lepsze to, niż wyświetlanie nieobsłużonego wyjątku.

    4 thoughts on “ASP.NET MVC: Prawidłowa obsługa wyjątków AntiForgery”

    1. Trochę nie na temat, ale czy ktoś może polecić jakiś blogowy post, artykuł, źródła na GitHubie ukazujące jak prawidło zaimplementować mechanizm Identity wykorzystując ASP.NET MVC i Entity Framework? Poszukuje konkretnego rozwiązania, które pozwala na dostęp do wybranych akcji użytkownikom na podstawie ich ID – w sensie tylko dany użytkownik widzi i edytuje np. swoje zadania.

    2. Nie lepiej stworzyć klasę bazową dla kontrolera (która będzie bazowa dla wszystkich naszych kontrolerow) i w niej nadpisac onexcepton (czy jakoś tak) zamiast bawić się filtrami?

    3. Z tego powodu często na stronach logowania wyłącza się obronę przed CSRF – tym bardziej, że ten atak przed zalogowaniem użytkownika nie ma żadnego sensu.

    4. Zalezy jak czesto bedzie to sprawdzane, jesli w obrebie calej kilasy to yak jak marek napisal stworzyc klase bazowa, ktora nadpisze OnActionExecuting i sprawdzi tam czy zalogowany uzytkownik ma uprawnienia. Ale jesli bedzie to uzywane tylko dla kilku metod czy akcji to mysle ze atrybut jest bardziej czytelny

    Leave a Reply

    Your email address will not be published.