tokens net name defense asp anti javascript asp.net angularjs asp.net-web-api csrf

javascript - net - token csrf



Angular contra Asp.Net WebApi, implementar CSRF en el servidor (4)

Estoy implementando un sitio web en Angular.js, que está llegando a un back-end de ASP.NET WebAPI.

Angular.js tiene algunas características incorporadas para ayudar con la protección anti-csrf. En cada solicitud http, buscará una cookie llamada "XSRF-TOKEN" y la enviará como un encabezado llamado "X-XSRF-TOKEN".

Esto depende de que el servidor web pueda establecer la cookie XSRF-TOKEN después de autenticar al usuario y luego verificar el encabezado X-XSRF-TOKEN para las solicitudes entrantes.

La documentación angular dice:

Para aprovechar esto, su servidor necesita establecer un token en una cookie de sesión legible por JavaScript llamada XSRF-TOKEN en la primera solicitud HTTP GET. En solicitudes posteriores que no son GET, el servidor puede verificar que la cookie coincida con el encabezado HTTP de X-XSRF-TOKEN y, por lo tanto, asegúrese de que solo el JavaScript que se ejecuta en su dominio podría haber leído el token. El token debe ser único para cada usuario y debe ser verificable por el servidor (para evitar que JavaScript genere sus propios tokens). Recomendamos que el token sea un resumen de la cookie de autenticación de su sitio con sal para mayor seguridad.

No pude encontrar ningún buen ejemplo de esto para ASP.NET WebAPI, así que hice mi propia versión con la ayuda de varias fuentes. Mi pregunta es: ¿alguien puede ver algo mal con el código?

Primero definí una clase de ayuda 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; } } }

Luego tengo el siguiente método en mi controlador de autorización, y lo llamo después de llamar a 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); }

Luego tengo un atributo personalizado que puedo agregar a los controladores para que comprueben el encabezado 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); } }

Por último, borro el token Csrf cuando el usuario cierra la sesión:

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

¿Alguien puede detectar problemas obvios (o no tan obvios) con ese enfoque?


Creo que tu código es defectuoso. La idea general de prevenir CSRF es evitar un token único en cada PETICIÓN, no en cada sesión. Si el token antifalsificación es un valor de sesión persistente, la capacidad de realizar CSRF aún permanece. Debe proporcionar un token único en cada solicitud ...


Esta solución no es segura ya que los ataques CSRF son posibles siempre que la cookie de autenticación sea válida. Tanto la autenticación como la cookie xsrf se enviarán al servidor cuando un atacante te haga realizar una solicitud a través de otro sitio y, por lo tanto, sigues siendo vulnerable hasta que el usuario finalice una sesión de cierre "difícil".

Cada solicitud o sesión debe tener su propio token único para prevenir realmente los ataques de CRSF. Pero probablemente la mejor solución es no usar autenticación basada en cookies, sino autenticación basada en tokens como OAuth. Esto evita que otros sitios web utilicen sus cookies para realizar solicitudes no deseadas, ya que los tokens se usan en encabezados http en lugar de cookies. Y los encabezados http no se envían automáticamente.

  1. Autenticación basada en tokens utilizando ASP.NET Web API 2, Owin e Identity
  2. Autenticación de Token AngularJS utilizando ASP.NET Web API 2, Owin e Identity

Estas excelentes publicaciones de blog contienen información sobre cómo implementar OAuth para WebAPI. Las publicaciones del blog también contienen gran información sobre cómo integrarlo con AngularJS.

Otra solución podría ser desactivar CORS y solo aceptar solicitudes entrantes de dominios incluidos en la lista blanca. Sin embargo, esto no funcionará para aplicaciones que no sean sitios web, como clientes de escritorio y dispositivos móviles. Además de eso, una vez que su sitio web es vulnerable a un ataque XSS, el atacante aún podrá falsificar solicitudes en su comportamiento.


No se han señalado problemas con el código, por lo que considero que la pregunta fue respondida.


Tu código parece estar bien. Lo único es que no necesita la mayoría del código que tiene, ya que web.api se ejecuta "en la parte superior" de asp.net mvc, y este último ha incorporado soporte para tokens antifalsificación.

En los comentarios, dbrunning y ccorrin expresan su preocupación de que solo pueda usar construir tokens AntiForgery solo cuando esté usando helpers MVC html. No es cierto. Los ayudantes pueden exponer par de tokens basados ​​en sesiones que puede validar uno contra el otro. Ver abajo para más detalles.

ACTUALIZAR:

Hay dos métodos que puede utilizar de AntiForgery:

  • AntiForgery.GetTokens utiliza dos parámetros de salida para devolver token de cookie y token de formulario

  • AntiForgery.Validate(cookieToken, formToken) valida si el par de tokens es válido

Totalmente puede reutilizar esos dos métodos y usar formToken como headerToken y cookieToken como cookieToken real. Entonces solo llame validate en ambos dentro del atributo.

Otra solución es usar JWT (verificar, por ejemplo, la implementación de MembershipReboot )

Este enlace muestra cómo usar tokens antifalsificación incorporados con 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); }

También eche un vistazo a esta pregunta AngularJS no puede encontrar la cookie XSRF-TOKEN