Sdílet prostřednictvím


Prevence útoků založených na otevřeném přesměrování (C#)

Jon Galloway

Tento kurz vysvětluje, jak můžete zabránit útokům s otevřeným přesměrováním v aplikacích ASP.NET MVC. Tento kurz popisuje změny, které byly provedeny v kontroleru Účtů v ASP.NET MVC 3, a ukazuje, jak tyto změny použít v existujících aplikacích ASP.NET MVC 1.0 a 2.

Co je útok s otevřeným přesměrováním?

Libovolnou webovou aplikaci, která přesměruje na adresu URL určenou prostřednictvím požadavku, jako je řetězec dotazu nebo data formuláře, může být potenciálně manipulována a přesměrovat uživatele na externí škodlivou adresu URL. Tato manipulace se označuje jako útok s otevřeným přesměrováním.

Kdykoli logika aplikace přesměruje na zadanou adresu URL, musíte ověřit, že s adresou URL přesměrování nedošlo k manipulaci. Přihlášení použité ve výchozím ovládacím panelu AccountController pro ASP.NET MVC 1.0 a ASP.NET MVC 2 je zranitelné vůči útokům s otevřeným přesměrováním. Naštěstí je snadné aktualizovat stávající aplikace tak, aby používaly opravy z ASP.NET MVC 3 Preview.

Abychom pochopili ohrožení zabezpečení, podívejme se na to, jak přesměrování přihlášení funguje ve výchozím projektu webové aplikace ASP.NET MVC 2. Pokus o návštěvu akce kontroleru s atributem [Authorize] v této aplikaci přesměruje neautorizované uživatele do zobrazení /Account/LogOn. Toto přesměrování na /Account/LogOn bude obsahovat parametr řetězce dotazu returnUrl, aby se uživatel po úspěšném přihlášení mohl vrátit na původně požadovanou adresu URL.

Na snímku obrazovky níže vidíme, že pokus o přístup k zobrazení /Account/ChangePassword, když nejste přihlášení, vede k přesměrování na /Account/LogOn? ReturnUrl=%2fAccount%2fChangePassword%2f.

Snímek obrazovky znázorňující stránku Přihlášení aplikace M V C Záhlaví je zvýrazněné.

Obrázek 01: Přihlašovací stránka s otevřeným přesměrováním

Vzhledem k tomu, že parametr querystring ReturnUrl není ověřený, může ho útočník upravit tak, aby do parametru vložil libovolnou adresu URL a provedl tak otevřený útok přesměrování. Abychom si to ukázali, můžeme upravit parametr ReturnUrl na https://bing.com, aby výsledná přihlašovací adresa URL byla /Account/LogOn? ReturnUrl=https://www.bing.com/. Po úspěšném přihlášení k webu jsme přesměrováni na https://bing.comadresu . Vzhledem k tomu, že toto přesměrování není ověřeno, může místo toho odkazovat na škodlivý web, který se pokusí oklamat uživatele.

Složitější útok s otevřeným přesměrováním

Otevřené útoky přesměrování jsou obzvláště nebezpečné, protože útočník ví, že se pokoušíme přihlásit na konkrétní web, což nás činí zranitelnými vůči phishingovým útokům. Útočník může například uživatelům webu poslat škodlivé e-maily, aby se pokusili zachytit jejich hesla. Podívejme se, jak by to fungovalo na webu NerdDinner. (Upozorňujeme, že živý web NerdDinner byl aktualizován, aby byl chráněn před útoky s otevřeným přesměrováním.)

Nejprve nám útočník pošle odkaz na přihlašovací stránku na webu NerdDinner, který obsahuje přesměrování na jeho zkameněnou stránku:

http://nerddinner.com/Account/LogOn?returnUrl=http://nerddiner.com/Account/LogOn

Všimněte si, že návratová adresa URL odkazuje na nerddiner.com, kterému ve slově večeře chybí "n". V tomto příkladu se jedná o doménu, kterou útočník řídí. Když přejdeme na výše uvedený odkaz, přejdeme na legitimní přihlašovací stránku NerdDinner.com.

Snímek obrazovky znázorňující domovskou stránku Nerd Dinner dot com Záhlaví je zvýrazněné a vyplněné U R L, které odkazuje na Nerd Diner dot com.

Obrázek 02: Přihlašovací stránka NerdDinner s otevřeným přesměrováním

Když se správně přihlásíme, akce LogOn ASP.NET MVC AccountController nás přesměruje na adresu URL zadanou v parametru řetězce dotazu returnUrl. V tomto případě je to adresa URL, kterou útočník zadal, což je http://nerddiner.com/Account/LogOn. Pokud nejsme extrémně opatrní, je velmi pravděpodobné, že si toho nevšimneme, zejména proto, že útočník pečlivě zajistil, aby jeho zkažená stránka vypadala přesně jako legitimní přihlašovací stránka. Tato přihlašovací stránka obsahuje chybovou zprávu s žádostí, abychom se znovu přihlásili. Neohrabuje nás, museli jsme heslo chybně zadat.

Snímek obrazovky s zkameněnou přihlašovací stránkou Nerd Dinner a výzvou uživatele k opětovnému zadání přihlašovacích údajů Zvýrazní se zkamenělé U R L v záhlaví.

Obrázek 03: Přihlašovací obrazovka Forged NerdDinner

Když znovu zadáme uživatelské jméno a heslo, zkamenělá přihlašovací stránka uloží informace a pošle nás zpátky na legitimní NerdDinner.com web. V tomto okamžiku už nás NerdDinner.com web ověřil, takže zkažená přihlašovací stránka se může přesměrovat přímo na tuto stránku. Konečným výsledkem je, že útočník má naše uživatelské jméno a heslo a my nevíme, že jsme mu je poskytli.

Zobrazení ohroženého kódu v akci LogOn kontroleru účtu

Kód akce LogOn v aplikaci ASP.NET MVC 2 je zobrazený níže. Všimněte si, že po úspěšném přihlášení vrátí kontroler přesměrování na returnUrl. Vidíte, že se neprovádí žádné ověření s parametrem returnUrl.

Výpis 1 – akce logon ASP.NET MVC 2 v 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);
}

Teď se podíváme na změny akce logon ASP.NET MVC 3. Tento kód byl změněn tak, aby ověřil parametr returnUrl voláním nové metody v pomocné třídě System.Web.Mvc.Url s názvem IsLocalUrl().

Výpis 2 – akce logon ASP.NET MVC 3 v 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);
}

Toto bylo změněno, aby se ověřil parametr návratové adresy URL voláním nové metody v pomocné třídě System.Web.Mvc.Url . IsLocalUrl()

Ochrana aplikací ASP.NET MVC 1.0 a MVC 2

Můžeme využít ASP.NET změny MVC 3 v našich stávajících aplikacích ASP.NET MVC 1.0 a 2 přidáním pomocné metody IsLocalUrl() a aktualizací akce LogOn pro ověření parametru returnUrl.

Metoda UrlHelper IsLocalUrl() ve skutečnosti volá metodu v System.Web.WebPages, protože toto ověření je také používáno ASP.NET webové stránky aplikace.

Výpis 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);
}

IsUrlLocalToHost Metoda obsahuje skutečnou logiku ověřování, jak je znázorněno v výpisu 4.

Výpis 4 – Metoda IsUrlLocalToHost() z třídy 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"
}

V naší aplikaci ASP.NET MVC 1.0 nebo 2 přidáme do AccountController metodu IsLocalUrl(), ale pokud je to možné, doporučujeme ji přidat do samostatné pomocné třídy. Provedeme dvě malé změny ASP.NET MVC 3 verze IsLocalUrl(), aby fungovala uvnitř AccountController. Nejprve ji změníme z veřejné metody na privátní, protože k veřejným metodám v řadičích je možné přistupovat jako k akcím kontroleru. Za druhé upravíme volání, které kontroluje hostitele adresy URL vůči hostiteli aplikace. Toto volání využívá místní pole RequestContext ve třídě UrlHelper. Místo toho, abyste tuto funkci používali. RequestContext.HttpContext.Request.Url.Host, použijeme ho. Request.Url.Host. Následující kód ukazuje upravenou metodu IsLocalUrl() pro použití s třídou kontroleru v aplikacích ASP.NET MVC 1.0 a 2.

Výpis 5 – Metoda IsLocalUrl(), která je upravená pro použití s třídou kontroleru 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"
   }
}

Teď, když je metoda IsLocalUrl() na místě, můžeme ji volat z akce LogOn a ověřit parametr returnUrl, jak je znázorněno v následujícím kódu.

Výpis 6 – aktualizovaná metoda LogOn, která ověřuje 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."); 
        } 
    }
}

Teď můžeme otestovat útok s otevřeným přesměrováním tak, že se pokusíme přihlásit pomocí externí adresy URL pro vrácení. Použijeme /Account/LogOn? ReturnUrl=https://www.bing.com/ znovu.

Snímek obrazovky znázorňující stránku Přihlášení aplikace M V C Záhlaví se zvýrazní a vyplní externí návratovou sadou.

Obrázek 04: Testování aktualizované akce LogOn

Po úspěšném přihlášení jsme přesměrováni na akci Kontroler domů nebo indexu místo na externí adresu URL.

Snímek obrazovky zobrazící stránku Index aplikace My M V C

Obrázek 05: Otevřený útok přesměrování byl poražen

Souhrn

K útokům s otevřeným přesměrováním může dojít, když se adresy URL přesměrování předávají jako parametry v adrese URL aplikace. Šablona ASP.NET MVC 3 obsahuje kód pro ochranu před útoky s otevřeným přesměrováním. Tento kód můžete přidat s určitými úpravami pro aplikace ASP.NET MVC 1.0 a 2. Pokud chcete chránit před otevřenými útoky přesměrování při přihlašování k aplikacím ASP.NET 1.0 a 2, přidejte metodu IsLocalUrl() a v akci LogOn ověřte parametr returnUrl.