web-dev-qa-db-fra.com

Angular contre Asp.Net WebApi, implémente CSRF sur le serveur

J'implémente un site Web dans Angular.js, qui frappe un backend ASP.NET WebAPI.

Angular.js a quelques fonctionnalités intégrées pour aider à la protection anti-CSRF. Sur chaque requête http, il recherchera un cookie appelé "XSRF-TOKEN" et le soumettra sous la forme d'un en-tête appelé "X-XSRF-TOKEN".

Cela suppose que le serveur Web puisse définir le cookie XSRF-TOKEN après authentification de l'utilisateur, puis vérifier l'en-tête X-XSRF-TOKEN pour les demandes entrantes.

La documentation Angular indique:

Pour en tirer parti, votre serveur doit définir un jeton dans un cookie de session lisible en JavaScript appelé XSRF-TOKEN lors de la première requête HTTP GET. Lors de demandes ultérieures non-GET, le serveur peut vérifier que le cookie correspond à l'en-tête HTTP X-XSRF-TOKEN. Par conséquent, assurez-vous que seul le code JavaScript exécuté sur votre domaine a pu lire le jeton. Le jeton doit être unique pour chaque utilisateur et doit être vérifiable par le serveur (pour empêcher le JavaScript de créer ses propres jetons). Nous recommandons que le jeton soit un condensé du cookie d'authentification de votre site avec du sel pour plus de sécurité.

Je ne trouvais pas de bons exemples de cela pour ASP.NET WebAPI, je me suis donc lancé avec l'aide de diverses sources. Ma question est la suivante: est-ce que quelqu'un peut voir quelque chose qui ne va pas avec le code?

J'ai d'abord défini une classe d'assistance simple:

public class CsrfTokenHelper
{
    const string ConstantSalt = "<ARandomString>";

    public string GenerateCsrfTokenFromAuthToken(string authToken)
    {
        return GenerateCookieFriendlyHash(authToken);
    }

    public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken) 
    {
        return csrfToken == GenerateCookieFriendlyHash(authToken);
    }

    private static string GenerateCookieFriendlyHash(string authToken)
    {
        using (var sha = SHA256.Create())
        {
            var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
            var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
            return cookieFriendlyHash;
        }
    }
}

Ensuite, j'ai la méthode suivante dans mon contrôleur d'autorisation, et je l'appelle après avoir appelé FormsAuthentication.SetAuthCookie ():

    // http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
    // http://docs.angularjs.org/api/ng.$http
    private void SetCsrfCookie()
    {
        var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
        Debug.Assert(authCookie != null, "authCookie != null");
        var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
        var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
        HttpContext.Current.Response.Cookies.Add(csrfCookie);
    }

Ensuite, j'ai un attribut personnalisé que je peux ajouter aux contrôleurs pour leur faire vérifier l'en-tête csrf:

public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
    //  http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
    protected override bool IsAuthorized(HttpActionContext context)
    {
        // get auth token from cookie
        var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
        if (authCookie == null) return false;
        var authToken = authCookie.Value;

        // get csrf token from header
        var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
        if (String.IsNullOrEmpty(csrfToken)) return false;

        // Verify that csrf token was generated from auth token
        // Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header. 
        // This proves that our site made the request.
        return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
    }
}

Enfin, j'efface le jeton Csrf lorsque l'utilisateur se déconnecte:

HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");

Quelqu'un peut-il déceler des problèmes évidents (ou pas si évidents) avec cette approche?

69
dbruning

Votre code semble être bon. La seule chose à faire est que vous n’avez pas besoin de la plupart du code que vous avez en tant que web.api fonctionne "au-dessus" de asp.net mvc, et que ce dernier dispose d’un support intégré pour les jetons anti-contrefaçon.

Dans les commentaires, dbrunning et ccorrin craignent que vous ne puissiez utiliser les jetons AntiForgery intégrés que lorsque vous utilisez des aides HTML de MVC. Ce n'est pas vrai. Les assistants peuvent simplement exposer une paire de jetons basée sur une session que vous pouvez valider les uns contre les autres. Voir ci-dessous pour plus de détails.

METTRE À JOUR:

Vous pouvez utiliser AntiForgery de deux manières:

  • AntiForgery.GetTokens utilise deux paramètres de sortie pour renvoyer le jeton de cookie et le jeton de formulaire

  • AntiForgery.Validate(cookieToken, formToken) valide si la paire de jetons est valide 

Vous pouvez totalement réutiliser ces deux méthodes et utiliser formToken en tant que headerToken et cookieToken en tant que cookieToken. Ensuite, il suffit d'appeler valider à la fois dans l'attribut.

Une autre solution consiste à utiliser JWT (vérifiez par exemple MembershipReboot implementation)

Ce lien montre comment utiliser les jetons anti-falsification intégrés avec ajax:

<script>
    @functions{
        public string TokenHeaderValue()
        {
            string cookieToken, formToken;
            AntiForgery.GetTokens(null, out cookieToken, out formToken);
            return cookieToken + ":" + formToken;                
        }
    }

    $.ajax("api/values", {
        type: "post",
        contentType: "application/json",
        data: {  }, // JSON data goes here
        dataType: "json",
        headers: {
            'RequestVerificationToken': '@TokenHeaderValue()'
        }
    });
</script>


void ValidateRequestHeader(HttpRequestMessage request)
{
    string cookieToken = "";
    string formToken = "";

    IEnumerable<string> tokenHeaders;
    if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
    {
        string[] tokens = tokenHeaders.First().Split(':');
        if (tokens.Length == 2)
        {
            cookieToken = tokens[0].Trim();
            formToken = tokens[1].Trim();
        }
    }
    AntiForgery.Validate(cookieToken, formToken);
}

Regardez aussi cette question AngularJS ne trouve pas le cookie XSRF-TOKEN

8
vittore

Je pense que votre code est imparfait. L'idée générale d'empêcher CSRF est d'empêcher un jeton unique sur chaque DEMANDE, pas chaque session. Si le jeton anti-falsification est une valeur de session persistante, la capacité d'effectuer CSRF est toujours disponible. Vous devez fournir un jeton unique sur chaque demande ...

0
Kolchy

Cette solution n'est pas sécurisée car les attaques CSRF sont toujours possibles tant que le cookie Auth est valide. L’authentification et le cookie xsrf seront tous deux envoyés au serveur lorsqu’un attaquant vous demandera de passer une requête via un autre site. Par conséquent, vous êtes toujours vulnérable jusqu’à ce que l’utilisateur se déconnecte "de manière définitive".

Chaque demande ou session doit avoir son propre jeton unique pour empêcher véritablement les attaques CRSF. Cependant, la meilleure solution consiste probablement à ne pas utiliser une authentification basée sur les cookies, mais une authentification basée sur des jetons, telle que OAuth. Cela empêche les autres sites Web d'utiliser vos cookies pour effectuer des requêtes non désirées, car les jetons sont utilisés dans des en-têtes http au lieu de cookies. Et les en-têtes http ne sont pas envoyés automatiquement.

  1. Authentification basée sur les jetons avec ASP.NET Web API 2, Owin et Identity
  2. Authentification par jeton AngularJS avec ASP.NET Web API 2, Owin et Identity

Ces excellents articles de blog contiennent des informations sur la mise en œuvre de OAuth pour WebAPI. Les articles de blog contiennent également d'excellentes informations sur la manière de l'intégrer à AngularJS. 

Une autre solution pourrait être de désactiver CORS et d’accepter uniquement les demandes entrantes provenant de domaines de la liste blanche. Toutefois, cela ne fonctionnera pas pour les applications autres que les sites Web, telles que les clients mobiles et/ou de bureau. À côté de cela, une fois que votre site web sera vulnérable à une attaque XSS, l'attaquant sera toujours en mesure de créer des demandes sur votre behalve.

0
Dibran