Freigeben über


Verhindern von Angriffen durch offene Umleitungen (C#)

von Jon Galloway

In diesem Tutorial wird erläutert, wie Sie Offene Umleitungsangriffe in Ihren ASP.NET MVC-Anwendungen verhindern können. In diesem Tutorial werden die Änderungen erläutert, die im AccountController in ASP.NET MVC 3 vorgenommen wurden, und es wird veranschaulicht, 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 die Formulardaten, kann möglicherweise manipuliert werden, um Benutzer zu einer externen, schädlichen URL umzuleiten. Diese Manipulation wird als offener Umleitungsangriff 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 Standardkontocontroller 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. Wenn Sie in dieser Anwendung versuchen, eine Controlleraktion mit dem Attribut [Authorize] zu besuchen, werden nicht autorisierte Benutzer zur Ansicht /Account/LogOn umgeleitet. Diese Umleitung zu /Account/LogOn enthält einen returnUrl-Abfragezeichenfolgenparameter, sodass der Benutzer zur ursprünglich angeforderten URL zurückgegeben werden kann, nachdem er sich erfolgreich angemeldet hat.

Im folgenden Screenshot sehen Sie, dass ein Versuch, auf die Ansicht /Account/ChangePassword zuzugreifen, wenn sie nicht angemeldet ist, zu einer Umleitung zu /Account/LogOn führt. ReturnUrl=%2fAccount%2fChangePassword%2f.

Screenshot: Anmeldeseite

Abbildung 01: Anmeldeseite mit geöffneter Umleitung

Da der ReturnUrl-Abfragezeichenfolgenparameter nicht überprüft wird, kann ein Angreifer ihn ändern, um eine beliebige URL-Adresse in den Parameter einzufügen, um einen offenen Umleitungsangriff durchzuführen. Um dies zu veranschaulichen, können wir den ReturnUrl-Parameter in https://bing.comändern, sodass die resultierende Anmelde-URL /Account/LogOn? ReturnUrl=https://www.bing.com/. Nach erfolgreicher Anmeldung bei der Website werden wir zu https://bing.comweitergeleitet. Da diese Umleitung nicht überprüft wird, könnte sie stattdessen auf eine böswillige Website verweisen, die versucht, den Benutzer auszutricksen.

Ein komplexerer Angriff auf die offene Umleitung

Offene Umleitungsangriffe sind besonders gefährlich, da ein Angreifer weiß, dass wir versuchen, uns bei einer bestimmten Website anzumelden, was uns anfällig für einen Phishing-Angriff macht. Ein Angreifer könnte beispielsweise schädliche E-Mails an Websitebenutzer senden, um ihre Kennwörter zu erfassen. Sehen wir uns an, wie dies auf der NerdDinner-Website funktionieren würde. (Beachten Sie, dass die live NerdDinner-Website aktualisiert wurde, um vor offenen Umleitungsangriffen zu schützen.)

Zunächst sendet uns ein Angreifer einen Link zur Anmeldeseite auf NerdDinner, der eine Umleitung zu seiner gefälschten 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, der ein "n" im Wort Dinner 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: Startseite des Nerd Dinner dot com Die Titelleiste ist hervorgehoben und mit der U R L gefüllt, die auf Nerd Diner dot com zeigt.

Abbildung 02: NerdDinner-Anmeldeseite mit geöffneter Umleitung

Wenn wir uns ordnungsgemäß anmelden, leitet uns die ASP.NET LogOn-Aktion des MVC-Kontocontrollers an die URL weiter, die im abfragezeichenfolgenparameter returnUrl angegeben ist. In diesem Fall ist es die URL, die der Angreifer eingegeben hat, nämlich http://nerddiner.com/Account/LogOn. Es ist sehr wahrscheinlich, dass wir dies nicht bemerken, wenn wir nicht äußerst wachsam sind, insbesondere, weil der Angreifer darauf geachtet hat, dass seine gefälschte Seite genau wie die legitime Anmeldeseite aussieht. Diese Anmeldeseite enthält eine Fehlermeldung, in der wir aufgefordert werden, uns erneut anzumelden. Unbeholfen müssen wir unser Kennwort falsch eingegeben haben.

Screenshot der gefälschten Anmeldeseite für Nerd Dinner, auf der der Benutzer aufgefordert wird, seine Anmeldeinformationen erneut einzugeben. Die gefälschte U R L in der Titelleiste ist hervorgehoben.

Abbildung 03: Schmiedeter NerdDinner-Anmeldebildschirm

Wenn wir unseren Benutzernamen und das Kennwort neu eingeben, speichert die gefälschte Anmeldeseite die Informationen und sendet uns zurück an die legitime NerdDinner.com Website. An diesem Punkt hat uns die NerdDinner.com Website bereits authentifiziert, sodass die gefälschte Anmeldeseite direkt zu dieser Seite weitergeleitet werden kann. Das Endergebnis ist, dass der Angreifer über unseren Benutzernamen und unser Kennwort verfügt, und wir wissen nicht, dass wir es für ihn bereitgestellt haben.

Untersuchen des anfälligen Codes in der LogOn-Aktion "AccountController"

Der Code für die LogOn-Aktion in einer ASP.NET MVC 2-Anwendung ist unten dargestellt. Beachten Sie, dass der Controller bei erfolgreicher Anmeldung eine Umleitung zur returnUrl zurückgibt. Sie können sehen, dass keine Überprüfung für den returnUrl-Parameter durchgefü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 System.Web.Mvc.Url-Hilfsklasse namens IsLocalUrl()aufgerufen wird.

Listing 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 durch Aufrufen einer neuen Methode in der System.Web.Mvc.Url-Hilfsklasse zu IsLocalUrl()überprüfen.

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 Web Pages-Anwendungen 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 Validierungslogik, wie in Listing 4 gezeigt.

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 nach Möglichkeit 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 ändern wir den Aufruf, der den URL-Host mit dem Anwendungshost überprüft. Dieser Aufruf verwendet ein lokales RequestContext-Feld in der UrlHelper-Klasse. Anstatt diesen zu verwenden. RequestContext.HttpContext.Request.Url.Host wird verwendet. Request.Url.Host. Der folgende Code zeigt die geänderte IsLocalUrl()-Methode zur 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"
   }
}

Nachdem die IsLocalUrl()-Methode nun eingerichtet ist, können wir sie über unsere 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. Verwenden Sie /Account/LogOn? ReturnUrl=https://www.bing.com/ erneut.

Screenshot: Anmeldeseite

Abbildung 04: Testen der aktualisierten LogOn-Aktion

Nach der erfolgreichen Anmeldung werden wir zur Aktion Start-/Indexcontroller und nicht zur externen URL weitergeleitet.

Screenshot: Seite

Abbildung 05: Open Redirection-Angriff 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. Fügen Sie zum Schutz vor offenen Umleitungsangriffen bei der Anmeldung bei ASP.NET 1.0- und 2-Anwendungen eine IsLocalUrl()-Methode hinzu, und überprüfen Sie den returnUrl-Parameter in der LogOn-Aktion.