Blocage des attaques par redirection ouverte (C#)
par Jon Galloway
Ce tutoriel explique comment empêcher les attaques de redirection ouvertes dans vos applications MVC ASP.NET. Ce tutoriel décrit les modifications apportées à AccountController dans ASP.NET MVC 3 et montre comment appliquer ces modifications dans vos applications MVC 1.0 et 2 ASP.NET existantes.
Qu’est-ce qu’une attaque de redirection ouverte ?
Toute application web qui redirige vers une URL spécifiée par le biais de la requête, comme les données de chaîne de requête ou de formulaire, peut être falsifiée pour rediriger les utilisateurs vers une URL externe malveillante. Cette falsification est appelée attaque de redirection ouverte.
Chaque fois que votre logique d’application redirige vers une URL spécifiée, vous devez vérifier que l’URL de redirection n’a pas été falsifiée. La connexion utilisée dans le AccountController par défaut pour ASP.NET MVC 1.0 et ASP.NET MVC 2 est vulnérable aux attaques de redirection ouvertes. Heureusement, il est facile de mettre à jour vos applications existantes pour utiliser les corrections de l’ASP.NET MVC 3 Preview.
Pour comprendre la vulnérabilité, examinons le fonctionnement de la redirection de connexion dans un projet d’application web MVC 2 par défaut ASP.NET. Dans cette application, la tentative de visite d’une action de contrôleur qui a l’attribut [Autoriser] redirige les utilisateurs non autorisés vers la vue /Account/LogOn. Cette redirection vers /Account/LogOn inclut un paramètre de chaîne de requête returnUrl afin que l’utilisateur puisse être renvoyé à l’URL initialement demandée après s’être connecté avec succès.
Dans la capture d’écran ci-dessous, nous pouvons voir qu’une tentative d’accès à la vue /Account/ChangePassword lorsqu’elle n’est pas connectée entraîne une redirection vers /Account/LogOn ? ReturnUrl=%2fAccount%2fChangePassword%2f.
Figure 01 : Page de connexion avec une redirection ouverte
Étant donné que le paramètre de chaîne de requête ReturnUrl n’est pas validé, un attaquant peut le modifier pour injecter n’importe quelle adresse URL dans le paramètre afin de mener une attaque de redirection ouverte. Pour illustrer cela, nous pouvons modifier le paramètre ReturnUrl en https://bing.com, de sorte que l’URL de connexion résultante sera /Account/LogOn ? ReturnUrl=https://www.bing.com/. Une fois la connexion réussie au site, nous sommes redirigés vers https://bing.com. Étant donné que cette redirection n’est pas validée, elle peut plutôt pointer vers un site malveillant qui tente de tromper l’utilisateur.
Une attaque de redirection ouverte plus complexe
Les attaques par redirection ouverte sont particulièrement dangereuses, car un attaquant sait que nous essayons de nous connecter à un site web spécifique, ce qui nous rend vulnérables à une attaque par hameçonnage. Par exemple, un attaquant peut envoyer des e-mails malveillants aux utilisateurs du site web dans une tentative de capture de leurs mots de passe. Voyons comment cela fonctionnerait sur le site NerdDinner. (Notez que le site NerdDinner en direct a été mis à jour pour se protéger contre les attaques de redirection ouverte.)
Tout d’abord, un attaquant nous envoie un lien vers la page de connexion sur NerdDinner qui inclut une redirection vers sa page falsifiée :
http://nerddinner.com/Account/LogOn?returnUrl=http://nerddiner.com/Account/LogOn
Notez que l’URL de retour pointe vers nerddiner.com, qui ne contient pas un « n » dans le mot dîner. Dans cet exemple, il s’agit d’un domaine que l’attaquant contrôle. Lorsque nous accédons au lien ci-dessus, nous accédons à la page de connexion NerdDinner.com légitime.
Figure 02 : page de connexion NerdDinner avec une redirection ouverte
Lorsque nous nous connectons correctement, l’action LogOn du ASP.NET accountController MVC nous redirige vers l’URL spécifiée dans le paramètre de chaîne de requête returnUrl. Dans ce cas, il s’agit de l’URL que l’attaquant a entrée, qui est http://nerddiner.com/Account/LogOn
. Sauf si nous sommes extrêmement vigilants, il est très probable que nous ne le remarquerons pas, en particulier parce que l’attaquant a pris soin de s’assurer que sa page falsifiée ressemble exactement à la page de connexion légitime. Cette page de connexion inclut un message d’erreur demandant que nous nous reconnections. Maladroit, nous devons avoir mal tapé notre mot de passe.
Figure 03 : Écran de connexion NerdDinner forgé
Lorsque nous retapons notre nom d’utilisateur et notre mot de passe, la page de connexion falsifiée enregistre les informations et nous renvoie au site NerdDinner.com légitime. À ce stade, le site NerdDinner.com nous a déjà authentifiés, de sorte que la page de connexion falsifiée peut rediriger directement vers cette page. Le résultat final est que l’attaquant a notre nom d’utilisateur et notre mot de passe, et nous ne sommes pas conscients que nous l’avons fourni.
Analyse du code vulnérable dans l’action LogOn AccountController
Le code de l’action LogOn dans une application MVC 2 ASP.NET est illustré ci-dessous. Notez qu’une fois la connexion réussie, le contrôleur retourne une redirection vers returnUrl. Vous pouvez voir qu’aucune validation n’est effectuée par rapport au paramètre returnUrl.
Listing 1 – ASP.NET action LogOn MVC 2 dans 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);
}
Examinons maintenant les modifications apportées à l’action LogOn MVC 3 ASP.NET. Ce code a été modifié pour valider le paramètre returnUrl en appelant une nouvelle méthode dans la classe d’assistance System.Web.Mvc.Url nommée IsLocalUrl()
.
Listing 2 – ASP.NET action LogOn MVC 3 dans 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);
}
Cela a été modifié pour valider le paramètre d’URL de retour en appelant une nouvelle méthode dans la classe d’assistance System.Web.Mvc.Url, IsLocalUrl()
.
Protection de vos applications MVC 1.0 et MVC 2 ASP.NET
Nous pouvons tirer parti des modifications ASP.NET MVC 3 dans nos applications ASP.NET MVC 1.0 et 2 existantes en ajoutant la méthode d’assistance IsLocalUrl() et en mettant à jour l’action LogOn pour valider le paramètre returnUrl.
La méthode UrlHelper IsLocalUrl() appelle simplement une méthode dans System.Web.WebPages, car cette validation est également utilisée par pages Web ASP.NET applications.
Listing 3 : méthode IsLocalUrl() de l’ASP.NET MVC 3 UrlHelper class
public bool IsLocalUrl(string url) {
return System.Web.WebPages.RequestExtensions.IsUrlLocalToHost(
RequestContext.HttpContext.Request, url);
}
La méthode IsUrlLocalToHost contient la logique de validation réelle, comme indiqué dans la description 4.
Listing 4 – Méthode IsUrlLocalToHost() de la classe System.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"
}
Dans notre application MVC 1.0 ou 2 ASP.NET, nous allons ajouter une méthode IsLocalUrl() au AccountController, mais nous vous encourageons à l’ajouter à une classe d’assistance distincte si possible. Nous allons apporter deux petites modifications à la version ASP.NET MVC 3 de IsLocalUrl() afin qu’elle fonctionne à l’intérieur du AccountController. Tout d’abord, nous allons passer d’une méthode publique à une méthode privée, car les méthodes publiques dans les contrôleurs sont accessibles en tant qu’actions de contrôleur. Deuxièmement, nous allons modifier l’appel qui vérifie l’hôte d’URL par rapport à l’hôte de l’application. Cet appel utilise un champ RequestContext local dans la classe UrlHelper. Au lieu de l’utiliser. RequestContext.HttpContext.Request.Url.Host, nous allons l’utiliser. Request.Url.Host. Le code suivant montre la méthode IsLocalUrl() modifiée pour une utilisation avec une classe de contrôleur dans ASP.NET applications MVC 1.0 et 2.
Listing 5 – Méthode IsLocalUrl(), qui est modifiée pour une utilisation avec une classe de contrôleur 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"
}
}
Maintenant que la méthode IsLocalUrl() est en place, nous pouvons l’appeler à partir de notre action LogOn pour valider le paramètre returnUrl, comme indiqué dans le code suivant.
Listing 6 : méthode LogOn mise à jour qui valide le paramètre 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.");
}
}
}
Nous pouvons maintenant tester une attaque de redirection ouverte en tentant de vous connecter à l’aide d’une URL de retour externe. Utilisons /Account/LogOn ? ReturnUrl=https://www.bing.com/ à nouveau.
Figure 04 : Test de l’action LogOn mise à jour
Une fois la connexion établie, nous sommes redirigés vers l’action Accueil/Index Controller plutôt que vers l’URL externe.
Figure 05 : Échec de l’attaque d’ouverture de redirection
Résumé
Des attaques de redirection ouverte peuvent se produire lorsque des URL de redirection sont passées en tant que paramètres dans l’URL d’une application. Le modèle MVC 3 ASP.NET inclut du code pour la protection contre les attaques de redirection ouverte. Vous pouvez ajouter ce code avec des modifications à ASP.NET applications MVC 1.0 et 2. Pour vous protéger contre les attaques de redirection ouverte lors de la connexion à ASP.NET applications 1.0 et 2, ajoutez une méthode IsLocalUrl() et validez le paramètre returnUrl dans l’action LogOn.