Angulaire contre Asp.Net WebApi, met en œuvre CSRF sur le serveur

Je met en œuvre un site Web dans Angular.js, qui frappe un backend ASP.NET WebAPI.

Angular.js possède des fonctionnalités intégrées pour la protection anti-csrf. Sur chaque demande HTTP, il recherchera un cookie appelé "XSRF-TOKEN" et le soumettra comme un en-tête appelé "X-XSRF-TOKEN".

Cela dépend du fait que le serveur Web peut configurer le cookie XSRF-TOKEN après avoir authentifié l'utilisateur, puis vérifier l'en-tête X-XSRF-TOKEN pour les demandes entrantes.

La documentation angulaire indique:

Pour profiter de cela, votre serveur doit définir un jeton dans un cookie de session lisible par JavaScript appelé XSRF-TOKEN lors de la première requête HTTP GET. Sur les demandes non-GET suivantes, le serveur peut vérifier que le cookie correspond à l'en-tête HTTP X-XSRF-TOKEN, et donc être sûr que seul JavaScript fonctionnant sur votre domaine aurait pu lire le jeton. Le jeton doit être unique pour chaque utilisateur et doit être vérifié par le serveur (pour éviter que le JavaScript ne crée ses propres jetons). Nous recommandons que le jeton soit un résumé du cookie d'authentification de votre site avec du sel pour plus de sécurité.

Je n'ai pas pu trouver de bons exemples pour ASP.NET WebAPI, alors j'ai mis le mien avec l'aide de diverses sources. Ma question est – quelqu'un peut-il voir quelque chose de mal avec le code?

D'abord, j'ai défini une classe d'aide 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 que j'appelle 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 les 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, je nettoie le jeton Csrf lorsque l'utilisateur se déconnecte:

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

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

Votre code semble être bien. La seule chose est, vous n'avez pas besoin de la plupart du code que vous avez en tant que web.api s'exécute "en haut" de Asp.net mvc, et ce dernier a intégré le support pour les jetons anti-faux.

Dans les commentaires, dbrunning et ccorrin expriment des préoccupations que vous ne pouvez utiliser que l'utilisation de jetons AntiForgery uniquement lorsque vous utilisez des assistants html MVC. Ce n'est pas vrai. Les assistants peuvent simplement exposer une paire de jetons de session que vous pouvez valider les uns contre les autres. Voir ci-dessous pour plus de détails.

METTRE À JOUR:

Il existe deux méthodes que vous pouvez utiliser à partir d'AntiForgery:

  • AntiForgery.GetTokens utilise deux paramètres de sortie pour renvoyer un jeton de cookies et un jeton de forme

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

Vous pouvez totalement utiliser ces deux méthodes et utiliser formToken comme headerToken et cookieToken en tant que biscuit réelleToken. Ensuite, appelez simplement validation sur les deux dans l'attribut.

Une autre solution consiste à utiliser JWT (vérifiez par exemple la mise en œuvre de MembershipReboot )

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 peut pas trouver le cookie XSRF-TOKEN

Je pense que votre code est défectueux. Toute l'idée autour 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 persistante de la session, la capacité d'effectuer CSRF reste. Vous devez fournir un jeton unique sur chaque demande …

Cette solution n'est pas sécurisée car les attaques CSRF sont encore possibles tant que le cookie Auth est valide. L'auth et le cookie xsrf seront envoyés au serveur lorsqu'un attaquant vous fait effectuer une demande via un autre site et, par conséquent, vous êtes encore vulnérable jusqu'à ce que l'utilisateur effectue un déconnexion "difficile".

Chaque demande ou session devrait avoir son propre jeton unique pour empêcher vraiment les attaques de la CRSF. Mais probablement, la meilleure solution est de ne pas utiliser l'authentification basée sur les cookies mais l'authentification basée sur les jetons comme OAuth. Cela empêche les autres sites Web d'utiliser vos cookies pour effectuer des requêtes indésirables, car les jetons sont utilisés dans les en-têtes http au lieu des cookies. Et les en-têtes HTTP ne sont pas automatiquement envoyés.

  1. Authentification par jeton à l'aide de ASP.NET Web API 2, Owin et Identité
  2. Authentification Token AngularJS à l'aide de ASP.NET Web API 2, Owin et Identité

Ces excellents articles de blog contiennent des informations sur la façon d'implémenter OAuth pour WebAPI. Les publications du blog contiennent également d'excellentes informations sur l'intégration avec AngularJS.

Une autre solution pourrait être de désactiver CORS et d'accepter uniquement les demandes reçues à partir des domaines de liste blanche. Cependant, cela ne fonctionnera pas pour les applications autres que le site Web, telles que les clients mobiles et / ou de bureau. À côté de cela, une fois que votre site Web est vulnérable à une attaque XSS, l'attaquant sera toujours en mesure de forger des demandes à votre avis.

Aucun problème n'a été signalé avec le code, alors je considère la question posée.