Freigeben über


Verhindern von Offenen Umleitungsangriffen (C#)

von Jon Galloway

In diesem Lernprogramm wird erläutert, wie Sie offene Umleitungsangriffe in Ihren ASP.NET MVC-Anwendungen verhindern können. In diesem Lernprogramm werden die Änderungen erläutert, die im AccountController in ASP.NET MVC 3 vorgenommen wurden, und zeigt, wie Sie diese Änderungen in Ihren vorhandenen ASP.NET MVC 1.0- und 2-Anwendungen anwenden können.

Was ist ein Open Redirection-Angriff?

Jede Webanwendung, die zu einer URL umleitet, die über die Anforderung angegeben wird, z. B. die Abfragezeichenfolge oder Formulardaten, kann möglicherweise manipuliert werden, um Benutzer an eine externe, schädliche URL umzuleiten. Diese Manipulation wird als Open-Redirect-Angriff bezeichnet.

Wenn Ihre Anwendungslogik zu einer angegebenen URL umleitet, müssen Sie überprüfen, ob die Umleitungs-URL nicht manipuliert wurde. Die Anmeldung, die im Standard accountController für ASP.NET MVC 1.0 und ASP.NET MVC 2 verwendet wird, ist anfällig für offene Umleitungsangriffe. Glücklicherweise ist es einfach, Ihre vorhandenen Anwendungen zu aktualisieren, um die Korrekturen aus der ASP.NET MVC 3 Preview zu verwenden.

Um die Sicherheitsanfälligkeit zu verstehen, sehen wir uns an, wie die Anmeldeumleitung in einem Standardprojekt ASP.NET MVC 2-Webanwendung funktioniert. In dieser Anwendung führt der Versuch, eine Controlleraktion mit dem Attribut [Authorize] zu besuchen, nicht autorisierte Benutzer zur Ansicht "/Account/LogOn" um. Diese Umleitung zu /Account/LogOn enthält einen returnUrl-Abfragezeichenfolgenparameter, sodass der Benutzer nach der erfolgreichen Anmeldung an die ursprünglich angeforderte URL zurückgegeben werden kann.

Im folgenden Screenshot können wir sehen, dass ein Versuch, auf die Ansicht "/Account/ChangePassword" zuzugreifen, wenn man nicht angemeldet ist, zu einer Umleitung zu /Account/LogOn?ReturnUrl=%2fAccount%2fChangePassword%2f führt.

Screenshot der Seite

Abbildung 01: Anmeldeseite mit einer geöffneten Umleitung

Da der Parameter "ReturnUrl" in der Abfragezeichenfolge nicht validiert wird, kann ein Angreifer ihn ändern, um eine beliebige URL-Adresse einzufügen und so einen offenen Umleitungsangriff durchzuführen. Um dies zu veranschaulichen, können wir den Parameter https://bing.comReturnUrl so ändern, dass die resultierende Anmelde-URL "/Account/LogOn" lautet? ReturnUrl=https://www.bing.com/. Nach der erfolgreichen Anmeldung auf der Website werden wir zu https://bing.com weitergeleitet. Da diese Umleitung nicht überprüft wird, könnte sie stattdessen auf eine schädliche Website verweisen, die versucht, den Benutzer zu verleiten.

Komplexerer Open Redirection-Angriff

Offene Umleitungsangriffe sind besonders gefährlich, da ein Angreifer weiß, dass wir uns bei einer bestimmten Website anmelden möchten, was uns anfällig für einen Phishingangriff macht. Beispielsweise könnte ein Angreifer böswillige E-Mails an Websitebenutzer senden, um seine Kennwörter zu erfassen. Sehen wir uns an, wie dies auf der NerdDinner-Website funktioniert. (Beachten Sie, dass die Live-NerdDinner-Website aktualisiert wurde, um vor offenen Umleitungsangriffen zu schützen.)

Zuerst sendet uns ein Angreifer einen Link zur Anmeldeseite auf NerdDinner, die eine Umleitung zu ihrer gefeiteten Seite enthält:

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

Beachten Sie, dass die Rückgabe-URL auf nerddiner.com verweist, bei dem ein "n" aus dem Wort Abendessen fehlt. In diesem Beispiel ist dies eine Domäne, die der Angreifer steuert. Wenn wir auf den obigen Link zugreifen, werden wir zur legitimen NerdDinner.com Anmeldeseite weitergeleitet.

Screenshot der Startseite von Nerd Dinner dot com. Die Titelleiste wird hervorgehoben und mit der U R L gefüllt, die auf Nerd Dinner dot com verweist.

Abbildung 02: NerdDinner-Anmeldeseite mit einer offenen Umleitung

Wenn wir uns ordnungsgemäß anmelden, leitet die LogOn-Aktion des ASP.NET MVC AccountController uns an die URL weiter, die im parameter returnUrl-Abfragezeichenfolge angegeben ist. In diesem Fall ist es die URL, die der Angreifer eingegeben hat, was heißt http://nerddiner.com/Account/LogOn. Es sei denn, wir sind äußerst aufmerksam, es ist sehr wahrscheinlich, dass wir dies nicht bemerken werden, insbesondere weil der Angreifer vorsichtig war, um sicherzustellen, dass seine gefaltete Seite genau wie die legitime Anmeldeseite aussieht. Diese Anmeldeseite enthält eine Fehlermeldung, die anfordert, dass wir uns erneut anmelden. Vermutlich haben wir uns vertippt mit unserem Passwort.

Screenshot der gefälschten Nerd Dinner Log-On-Seite, auf der der Benutzer aufgefordert wird, seine Anmeldeinformationen erneut einzugeben. Die gefälschte URL in der Titelleiste ist hervorgehoben.

Abbildung 03: "Gefälschter NerdDinner-Anmeldebildschirm"

Wenn wir unseren Benutzernamen und das Kennwort erneut eingeben, speichert die geschmiedete Anmeldeseite die Informationen und sendet uns zurück an die legitime NerdDinner.com Website. Zu diesem Zeitpunkt hat die Website NerdDinner.com uns bereits authentifiziert, sodass die gefälschte Anmeldeseite direkt zu dieser Seite umleiten kann. Das Endergebnis ist, dass der Angreifer über unseren Benutzernamen und sein Kennwort verfügt, und wir wissen nicht, dass wir ihn ihnen zur Verfügung gestellt haben.

Betrachten des anfälligen Codes in der AccountController LogOn-Aktion

Der Code für die LogOn-Aktion in einer ASP.NET MVC 2-Anwendung wird unten angezeigt. Beachten Sie, dass der Controller bei einer erfolgreichen Anmeldung eine Umleitung an die returnUrl zurückgibt. Sie können sehen, dass keine Überprüfung für den returnUrl-Parameter ausgeführt wird.

Listing 1 – ASP.NET MVC 2 LogOn-Aktion in 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);
}

Sehen wir uns nun die Änderungen an der ASP.NET MVC 3 LogOn-Aktion an. Dieser Code wurde geändert, um den returnUrl-Parameter zu überprüfen, indem eine neue Methode in der Hilfsklasse System.Web.Mvc.Url mit dem Namen aufgerufen IsLocalUrl()wird.

Auflistung 2 – ASP.NET MVC 3 LogOn-Aktion in 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);
}

Dies wurde geändert, um den Rückgabe-URL-Parameter zu überprüfen, indem eine neue Methode in der Hilfsklasse System.Web.Mvc.Url aufgerufen wird. IsLocalUrl()

Schützen Ihrer ASP.NET MVC 1.0- und MVC 2-Anwendungen

Wir können die ASP.NET MVC 3-Änderungen in unseren vorhandenen ASP.NET MVC 1.0- und 2-Anwendungen nutzen, indem wir die IsLocalUrl()-Hilfsmethode hinzufügen und die LogOn-Aktion aktualisieren, um den returnUrl-Parameter zu überprüfen.

Die UrlHelper IsLocalUrl()-Methode ruft tatsächlich nur eine Methode in System.Web.WebPages auf, da diese Überprüfung auch von ASP.NET Webanwendungen verwendet wird.

Listing 3 – Die IsLocalUrl()-Methode aus dem ASP.NET MVC 3 UrlHelper class

public bool IsLocalUrl(string url) {
    return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost(
        RequestContext.HttpContext.Request, url);
}

Die IsUrlLocalToHost-Methode enthält die tatsächliche Gültigkeitsprüfungslogik, wie in Listing 4 dargestellt.

Listing 4 – IsUrlLocalToHost()-Methode aus der System.Web.WebPages RequestExtensions-Klasse

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"
}

In unserer ASP.NET MVC 1.0- oder 2-Anwendung fügen wir dem AccountController eine IsLocalUrl()-Methode hinzu, aber Sie werden empfohlen, sie ggf. einer separaten Hilfsklasse hinzuzufügen. Wir nehmen zwei kleine Änderungen an der ASP.NET MVC 3-Version von IsLocalUrl() vor, damit sie innerhalb des AccountController funktioniert. Zunächst ändern wir sie von einer öffentlichen Methode in eine private Methode, da auf öffentliche Methoden in Controllern als Controlleraktionen zugegriffen werden kann. Zweitens werden wir den Aufruf ändern, der den URL-Host mit dem Anwendungshost vergleicht. Dieser Aufruf verwendet ein lokales RequestContext-Feld in der UrlHelper-Klasse. Statt dieses RequestContext.HttpContext.Request.Url.Host zu verwenden, verwenden wir dieses Request.Url.Host. Der folgende Code zeigt die geänderte IsLocalUrl()-Methode für die Verwendung mit einer Controllerklasse in ASP.NET MVC 1.0- und 2-Anwendungen.

Listing 5 – IsLocalUrl()-Methode, die für die Verwendung mit einer MVC Controller-Klasse geändert wird

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"
   }
}

Da nun die IsLocalUrl()-Methode vorhanden ist, können wir sie aus unserer LogOn-Aktion aufrufen, um den returnUrl-Parameter zu überprüfen, wie im folgenden Code gezeigt.

Listing 6 – Aktualisierte LogOn-Methode, die den returnUrl-Parameter überprüft

[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."); 
        } 
    }
}

Jetzt können wir einen offenen Umleitungsangriff testen, indem wir versuchen, sich mit einer externen Rückgabe-URL anzumelden. Lass uns das /Account/LogOn?ReturnUrl=https://www.bing.com/ erneut verwenden.

Screenshot, der die Seite

Abbildung 04: Testen der aktualisierten LogOn-Aktion

Nach der erfolgreichen Anmeldung werden wir anstelle der externen URL zur Aktion des Start- und Index-Controllers umgeleitet.

Screenshot der Seite

Abbildung 05: Offene Umleitungsangriff besiegt

Zusammenfassung

Offene Umleitungsangriffe können auftreten, wenn Umleitungs-URLs als Parameter in der URL für eine Anwendung übergeben werden. Die ASP.NET MVC 3-Vorlage enthält Code zum Schutz vor offenen Umleitungsangriffen. Sie können diesen Code mit einigen Änderungen an ASP.NET MVC 1.0- und 2-Anwendungen hinzufügen. Um vor offenen Umleitungsangriffen beim Anmelden bei ASP.NET 1.0- und 2-Anwendungen zu schützen, fügen Sie eine IsLocalUrl()-Methode hinzu, und überprüfen Sie den returnUrl-Parameter in der LogOn-Aktion.