.Net

ASP.Net Web Forms : comment se prémunir des attaques CSRF ?

Par Marc, le 16 novembre 2018, mis à jour le 6 décembre 2018 — CSRF, faille, sécurité — 7 minutes de lecture
Si la mode tend à l’utilisation de Framwork JavaScript, au développement de Single Page Application, à la mise en place de Progressive Web App ou encore à la résurgence de sites internet statiques (si, si, ça revient), il y a encore quelques personnes, comme moi par exemple, qui continuent de créer ou maintenir des applications en ASP.Net Web Forms. Je vous avoue que je ne fais pas ça par plaisir le soir quand je rentre à la maison, mais ça ne m’empêche pas de le faire avec le plus grand des sérieux dès que je suis au boulot. Mais venons-en plutôt au sujet qui nous intéresse ici à savoir la CSRF. Parce que lorsqu’on maintient des applis qui commencent à dater et qui n’ont pas pu « profiter » des mécanismes de sécurité automatique mis en place par les dernières versions du Framework .Net, du moins à partir de 2012, il faut bien implémenter soit même la sécurité. Mais avant d’aborder la solution, un petit mot sur le pourquoi du comment.

Qu’est ce que la faille CSRF ?

Déjà, pour commencer,  qu’est-ce qu’il veut dire cet acronyme venue tout droit des enfers ? Il signifie « Cross-Site Request Forgery » et on peut traduire ça par « falsification de requête inter-sites ». Ce n’est pas spécialement plus clair dit comme ça, mais pour résumer la chose assez brièvement la faille CSRF permet à une personne mal attentionné, un méchant hacker donc,  d’effectuer une action sur votre ordinateur en tirant parti de vos données d’authentification sans même que vous vous en rendiez compte. Par exemple, quelqu’un pourrait très bien supprimer l’intégralité de vos articles sur votre blog perso ou encore opérer un virement sur votre compte bancaire à votre insu.

Mais comment ça marche ?

Pour rester sur l’exemple du blog, que je trouve nettement plus parlant que celui du virement bancaire, vous devez vous rendre sur une url du même type que celle ci-dessous pour supprimer un article.
http://monpetitblog.fr/supprimerArticle.php?id=78
Si vous êtes connecté, l’article qui porte l’ID 78 sera supprimé, mais si vous ne l’êtes pas, vous aurez un message d’erreur vous invitant à vous authentifier. Ce qui est un fonctionnement tout ce qu’il y a de plus normal. Maintenant, mettons-nous dans la tête du pirate ou du pote pas très drôle qui veut vous faire une blague. Il pourrait très bien vous envoyer un mail avec un super bon plan pour un voyage au Japon à moitié prix ou encore la photo du dernier meme à la mode qui fait rire tous ses collègues de bureau. Le genre de mail parfaitement anodin qu’on ouvre sans trop réfléchir. Sauf qu’au lieu de vous diriger vers le site d’une agence de voyages, le lien du bon plan contenu dans l’email pourrait très bien pointer vers la page de suppression plus haut. Et quant à l’image rigolotte, le simple fait de cliquer dessus pourrait par exemple lancer un script JavaScript qui valide le formulaire de cette même page. Enfin, si vous êtes connecté à votre blog au moment de cliquer sur le lien ou sur l’image, et bien l’un de vos articles serait supprimé. C’est aussi simple que cela….

Pas de panique, des solutions existent !

Ce n’est pas parce qu’on peut se faire avoir aussi facilement qu’il faut vous se mettre à transpirer à grosses gouttes pour autant. La plupart des CMS actuels sont « théoriquement » sécurisés et il en va de même pour une grande majorité des plateformes de blogs gratuits. Par contre, si vous utilisez un moteur maison ou une appli qui accuse le poids des années, il faudrait penser à vous pencher sur la question. Et bien qu’il soit compliqué de s’en protéger à 100%, il existe plusieurs solutions pour se protéger de cette faille :
  • Un bouton de confirmation: c’est tout bête, mais le simple ajout d’une étape de confirmation avant la création, mise à jour ou suppression d’un article est déjà une très bonne solution pour réduire les risques.
  • Le captcha: comme pour l’étape de confirmation, le captcha demande à l’utilisateur de réaliser une action pour valider le formulaire. C’est efficace, mais pas forcément très ergonomique.
  • L’authentification par jeton: c’est justement cette dernière solution qui va nous intéresse ici. En gros, on génère un token unique à l’initialisation du formulaire qui est ensuite vérifié avant chaque modification.

Parfait, mais comment je fais avec mon appli en Web Forms ?

À partir de Visual Studio 2012, Microsoft a mis en place une protection anti CSRF directement intégrée dans le code source de la page site.Master des projets Web Forms. Et c’est justement ce bout de code qu’on peut utiliser pour se prémunir des attaques CSRF. Néanmoins, il faut respecter les conditions ci-dessous pour que ça fonctionne :
  • tous les formulaires qui modifient des données doivent s’appuyer sur la page Master
  • toutes les requêtes de modification de données doivent utiliser le ViewState
  • le site web doit être exempt des vulnérabilités XSS.
Une fois toutes ces conditions mise en place, il faut suffit de modifier la page site.Master.cs de votre projet avec le code ci-dessous. J’ai repris le même que celui généré par Visual Studio et j’ai juste créé une méthode supplémentaire pour un peu plus de clarté. C’est testé et approuvé de mon côté, mais si jamais vous connaissez un autre moyen plus efficace, n’hésitez surtout pas à m’en parler dans les commentaires. Je suis preneur.
public partial class SiteMaster : MasterPage
{
    private const string AntiXsrfTokenKey = "__AntiXsrfToken";
    private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
    private string _antiXsrfTokenValue;
 
    protected void Page_Init(object sender, EventArgs e)
    {
        // .........
        setCsrfCookie();
    }
 
    protected void master_Page_PreLoad(object sender, EventArgs e)
        {
            //During the initial page load, add the Anti-XSRF token and user
            //name to the ViewState
            if (!IsPostBack)
            {
                //Set Anti-XSRF token
                ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
 
                //If a user name is assigned, set the user name
                ViewState[AntiXsrfUserNameKey] =
                Context.User.Identity.Name ?? String.Empty;
            }
            //During all subsequent post backs to the page, the token value from
            //the cookie should be validated against the token in the view state
            //form field. Additionally user name should be compared to the
            //authenticated users name
            else
            {
                //Validate the Anti-XSRF token
                if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
                || (string)ViewState[AntiXsrfUserNameKey] !=
                (Context.User.Identity.Name ?? String.Empty))
            {
            throw new InvalidOperationException("Validation ofAnti-XSRF token failed.");
            }
        }
    }

    private void setCsrfCookie()
    {
        //First, check for the existence of the Anti-XSS cookie
        var requestCookie = Request.Cookies[AntiXsrfTokenKey];
        Guid requestCookieGuidValue;
 
        //If the CSRF cookie is found, parse the token from the cookie.
        //Then, set the global page variable and view state user
        //key. The global variable will be used to validate that it matches in the view state form field in the Page.PreLoad
        //method.
        if (requestCookie != null
        && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
        {
            //Set the global token variable so the cookie value can be
            //validated against the value in the view state form field in
            //the Page.PreLoad method.
            _antiXsrfTokenValue = requestCookie.Value;
 
            //Set the view state user key, which will be validated by the
            //framework during each request
            Page.ViewStateUserKey = _antiXsrfTokenValue;
        }
        //If the CSRF cookie is not found, then this is a new session.
        else
        {
            //Generate a new Anti-XSRF token
            _antiXsrfTokenValue = Guid.NewGuid().ToString("N");
 
            //Set the view state user key, which will be validated by the
            //framework during each request
            Page.ViewStateUserKey = _antiXsrfTokenValue;
 
            //Create the non-persistent CSRF cookie
            var responseCookie = new HttpCookie(AntiXsrfTokenKey)
            {
                //Set the HttpOnly property to prevent the cookie from
                //being accessed by client side script
                HttpOnly = true,
 
                //Add the Anti-XSRF token to the cookie value
                Value = _antiXsrfTokenValue
            };
 
            //If we are using SSL, the cookie should be set to secure to
            //prevent it from being sent over HTTP connections
            if (FormsAuthentication.RequireSSL &&
            Request.IsSecureConnection)
            responseCookie.Secure = true;
 
            //Add the CSRF cookie to the response
            Response.Cookies.Set(responseCookie);
        }
 
            Page.PreLoad += master_Page_PreLoad;
    }
}
Sources : leblogduhacker.frsoftware-security.sans.org

Marc

Développeur full-stack depuis maintenant près de 7 ans, j'ai décidé d'ouvrir ce blog afin de capitaliser mes acquis, essayer de partager mes connaissances, découvrir de nouvelles technologies et surtout échanger avec d’autres développeurs.

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.