Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Autor: Jon Galloway
W tym samouczku wyjaśniono, jak zapobiegać otwartym atakom przekierowania w aplikacjach MVC ASP.NET. W tym samouczku omówiono zmiany wprowadzone w module AccountController w ASP.NET MVC 3 i pokazano, jak można zastosować te zmiany w istniejących ASP.NET aplikacjach MVC 1.0 i 2.
Co to jest atak typu open redirection?
Każda aplikacja internetowa, która przekierowuje do adresu URL określonego za pośrednictwem żądania, takiego jak ciąg zapytania lub dane formularza, może potencjalnie zostać naruszona w celu przekierowania użytkowników do zewnętrznego, złośliwego adresu URL. Ta manipulacja jest nazywana otwartym atakiem przekierowania.
Za każdym razem, gdy logika aplikacji przekierowuje do określonego adresu URL, musisz sprawdzić, czy adres URL przekierowania nie został naruszony. Logowanie używane w domyślnym kontrolerze AccountController dla ASP.NET MVC 1.0 i ASP.NET MVC 2 jest narażone na otwarte ataki przekierowania. Na szczęście można łatwo zaktualizować istniejące aplikacje, aby używać poprawek z wersji zapoznawczej ASP.NET MVC 3.
Aby zrozumieć tę lukę w zabezpieczeniach, przyjrzyjmy się, jak działa przekierowanie logowania w domyślnym projekcie aplikacji internetowej MVC 2 ASP.NET. W tej aplikacji próba odwiedzenia akcji kontrolera z atrybutem [Autoryzuj] spowoduje przekierowanie nieautoryzowanych użytkowników do widoku /Account/LogOn. To przekierowanie do /Account/LogOn będzie zawierać parametr querystring returnUrl, aby użytkownik mógł zostać zwrócony do pierwotnie żądanego adresu URL po pomyślnym zalogowaniu.
Na poniższym zrzucie ekranu widać, że próba uzyskania dostępu do widoku /Account/ChangePassword po niepologowaniu powoduje przekierowanie do /Account/LogOn? ReturnUrl=%2fAccount%2fChangePassword%2f.
Rysunek 01. Strona logowania z otwartym przekierowaniem
Ponieważ parametr querystring returnUrl nie jest weryfikowany, atakujący może zmodyfikować go w celu wstrzyknięcia dowolnego adresu URL do parametru w celu przeprowadzenia otwartego ataku przekierowania. Aby to zademonstrować, możemy zmodyfikować parametr ReturnUrl na https://bing.com, więc wynikowy adres URL logowania będzie /Account/LogOn? ReturnUrl=https://www.bing.com/. Po pomyślnym zalogowaniu się do witryny nastąpi przekierowanie do https://bing.com. Ponieważ to przekierowanie nie jest weryfikowane, może zamiast tego wskazywać złośliwą witrynę, która próbuje oszukać użytkownika.
Bardziej złożony atak typu open redirection
Otwarte ataki przekierowania są szczególnie niebezpieczne, ponieważ osoba atakująca wie, że próbujemy zalogować się do określonej witryny internetowej, co sprawia, że jesteśmy narażeni na atak wyłudzający informacje. Na przykład osoba atakująca może wysłać złośliwe wiadomości e-mail do użytkowników witryny internetowej, próbując przechwycić swoje hasła. Przyjrzyjmy się, jak to działałoby na stronie NerdDinner. (Pamiętaj, że aktywna witryna NerdDinner została zaktualizowana w celu ochrony przed otwartymi atakami przekierowania).
Najpierw osoba atakująca wysyła do nas link do strony logowania w aplikacji NerdDinner, która zawiera przekierowanie do sfałszowanej strony:
http://nerddinner.com/Account/LogOn?returnUrl=http://nerddiner.com/Account/LogOn
Zwróć uwagę, że URL zwrotny wskazuje na nerddiner.com, w którym brakuje litery 'n' w słowie 'dinner'. W tym przykładzie jest to domena, którą kontroluje osoba atakująca. Gdy uzyskujemy dostęp do powyższego linku, jesteśmy przekierowani do wiarygodnej strony logowania NerdDinner.com.
Rysunek 02. Strona logowania NerdDinner z otwartym przekierowaniem
Gdy poprawnie się zalogujemy, akcja LogOn ASP.NET MVC AccountController przekierowuje nas do adresu URL określonego w parametrze querystring returnUrl. W takim przypadku jest to adres URL wprowadzony przez osobę atakującą, czyli http://nerddiner.com/Account/LogOn. Chyba że jesteśmy bardzo czujni, bardzo prawdopodobne jest, że tego nie zauważymy, zwłaszcza dlatego, że osoba atakująca była ostrożna, aby upewnić się, że ich sfałszowana strona wygląda dokładnie tak jak legalna strona logowania. Ta strona logowania zawiera komunikat o błędzie z żądaniem ponownego zalogowania. Ale z nas niezdary, musieliśmy błędnie wpisać nasze hasło.
Rysunek 03. Ekran logowania sfałszowany NerdDinner
Gdy ponownie wpiszemy nazwę użytkownika i hasło, sfałszowana strona logowania zapisuje informacje i wysyła nas z powrotem do uzasadnionej witryny NerdDinner.com. W tym momencie witryna NerdDinner.com już nas uwierzytelniła, więc sfałszowana strona logowania może przekierowywać bezpośrednio do tej strony. Wynikiem końcowym jest to, że osoba atakująca ma nazwę użytkownika i hasło, a my nie wiemy, że udostępniliśmy je im.
Patrząc na podatny na zagrożenia kod w metodzie LogOn klasy AccountController.
Poniżej przedstawiono kod akcji LogOn w aplikacji ASP.NET MVC 2. Warto zauważyć, że po pomyślnym zalogowaniu kontroler zwraca przekierowanie do adresu returnUrl. Widać, że nie jest wykonywana walidacja względem parametru returnUrl.
Wykaz 1 — akcja LogOn w ASP.NET MVC 2 AccountController.cs
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (!String.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Teraz przyjrzyjmy się zmianom w akcji LogOn w ASP.NET MVC 3. Ten kod został zmieniony w celu zweryfikowania parametru returnUrl przez wywołanie nowej metody w klasie pomocnika System.Web.Mvc.Url o nazwie IsLocalUrl().
Lista 2 — akcja LogOn ASP.NET MVC 3 w AccountController.cs
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("",
"The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
Zostało to zmienione w celu zweryfikowania zwracanego parametru adresu URL przez wywołanie nowej metody w klasie pomocnika System.Web.Mvc.Url. IsLocalUrl()
Ochrona aplikacji ASP.NET MVC 1.0 i MVC 2
Możemy wykorzystać zmiany wprowadzone w ASP.NET MVC 3 w naszych istniejących aplikacjach ASP.NET MVC 1.0 i 2, dodając metodę pomocniczą IsLocalUrl() i aktualizując akcję logowania LogOn, aby zweryfikować poprawność parametru returnUrl.
Metoda UrlHelper IsLocalUrl() rzeczywiście wywołuje metodę w pliku System.Web.WebPages, ponieważ ta walidacja jest również używana przez aplikacje ASP.NET Web Pages.
Listing 3 — metoda IsLocalUrl() z ASP.NET MVC 3 UrlHelper class
public bool IsLocalUrl(string url) {
return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost(
RequestContext.HttpContext.Request, url);
}
Metoda IsUrlLocalToHost zawiera rzeczywistą logikę walidacji, jak pokazano na liście 4.
Listing 4 — metoda IsUrlLocalToHost() z klasy System.Web.WebPages RequestExtensions
public static bool IsUrlLocalToHost(this HttpRequestBase request, string url)
{
return !url.IsEmpty() &&
((url[0] == '/' && (url.Length == 1 ||
(url[1] != '/' && url[1] != '\\'))) || // "/" or "/foo" but not "//" or "/\"
(url.Length > 1 &&
url[0] == '~' && url[1] == '/')); // "~/" or "~/foo"
}
W naszej aplikacji ASP.NET MVC 1.0 lub 2 dodamy metodę IsLocalUrl() do kontrolki AccountController, ale zachęcamy do dodania jej do oddzielnej klasy pomocniczej, jeśli jest to możliwe. Wprowadzimy dwie małe zmiany w metodzie IsLocalUrl() w wersji ASP.NET MVC 3, aby działała wewnątrz kontrolera AccountController. Najpierw zmienimy ją z metody publicznej na prywatną, ponieważ metody publiczne w kontrolerach mogą być dostępne jako akcje kontrolera. Po drugie zmodyfikujemy wywołanie sprawdzające host adresu URL na hoście aplikacji. To wywołanie korzysta z lokalnego pola RequestContext w klasie UrlHelper. Zamiast używać this.RequestContext.HttpContext.Request.Url.Host, użyjemy this.Request.Url.Host. Poniższy kod przedstawia zmodyfikowaną metodę IsLocalUrl() do użycia z klasą kontrolera w aplikacjach ASP.NET MVC 1.0 i 2.
Lista 5 — metoda IsLocalUrl(), która jest modyfikowana do użycia z klasą kontrolera MVC
private bool IsLocalUrl(string url)
{
if (string.IsNullOrEmpty(url))
{
return false;
}
else
{
return ((url[0] == '/' && (url.Length == 1 ||
(url[1] != '/' && url[1] != '\\'))) || // "/" or "/foo" but not "//" or "/\"
(url.Length > 1 &&
url[0] == '~' && url[1] == '/')); // "~/" or "~/foo"
}
}
Teraz, gdy metoda IsLocalUrl() jest w miejscu, możemy wywołać ją z akcji LogOn, aby zweryfikować parametr returnUrl, jak pokazano w poniższym kodzie.
Lista 6 — zaktualizowana metoda LogOn, która weryfikuje parametr returnUrl
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
FormsService.SignIn(model.UserName, model.RememberMe);
if (IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("",
"The user name or password provided is incorrect.");
}
}
}
Teraz możemy przetestować otwarty atak przekierowania, próbując zalogować się przy użyciu zewnętrznego URL zwrotnego. Użyjemy /Account/LogOn? ReturnUrl=https://www.bing.com/ ponownie.
Rysunek 04. Testowanie zaktualizowanej akcji LogOn
Po pomyślnym zalogowaniu nastąpi przekierowanie do akcji Home/Index Controller zamiast zewnętrznego adresu URL.
Rysunek 05: Atak z otwartym przekierowaniem został zneutralizowany
Podsumowanie
Otwarte ataki przekierowania mogą wystąpić, gdy adresy URL przekierowania są przekazywane jako parametry w adresie URL aplikacji. Szablon ASP.NET MVC 3 zawiera kod chroniący przed otwartymi atakami przekierowania. Ten kod można dodać z pewnymi modyfikacjami do aplikacji ASP.NET MVC 1.0 i 2. Aby chronić przed otwartymi atakami przekierowania podczas logowania się do aplikacji ASP.NET 1.0 i 2, dodaj metodę IsLocalUrl() i zweryfikuj parametr returnUrl w akcji LogOn.