XSRF/CSRF Prevention in ASP.NET MVC and Web Pages (Prevención de XSRF y CSRF en ASP.NET MVC y Web Pages)

por Rick Anderson

La falsificación de solicitud entre sitios (también conocida como XSRF o CSRF) es un ataque contra aplicaciones hospedadas en la web, en el que una aplicación web malintencionada puede influir en la interacción entre un explorador cliente y un sitio web de confianza para ese explorador. Estos ataques son posibles porque los exploradores web envían automáticamente algunos tipos de token de autenticación con cada solicitud a un sitio web. El ejemplo canónico es una cookie de autenticación, como el vale de autenticación de formularios de ASP.NET. Pero los sitios web que usan cualquier mecanismo de autenticación persistente (como la autenticación de Windows, Básica, etc.) pueden ser víctimas de estos ataques.

Un ataque XSRF es distinto de un ataque de suplantación de identidad (phishing). Los ataques de suplantación de identidad requieren interacción de la víctima. En un ataque de suplantación de identidad (phishing), un sitio web malintencionado imitará al sitio web de destino y se engaña a la víctima para que proporcione información confidencial al atacante. En un ataque XSRF, con frecuencia no hay interacción necesaria de la víctima. El atacante se basa en que el explorador envía automáticamente todas las cookies pertinentes al sitio web de destino.

Para más información, vea XSRF de Open Web Application Security Project (OWASP).

Anatomía de un ataque

Para describir un ataque XSRF, imagine un usuario que quiera realizar algunas transacciones bancarias en línea. Este usuario primero visita WoodgroveBank.com e inicia sesión, momento en el que el encabezado de respuesta contendrá su cookie de autenticación:

HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }

Como la cookie de autenticación es una cookie de sesión, el explorador la borrará automáticamente al finalizar el proceso del explorador. Pero hasta ese momento, el explorador incluirá automáticamente la cookie con cada solicitud a WoodgroveBank.com. El usuario ahora quiere transferir 1000 USD a otra cuenta, por lo que rellena un formulario en el sitio bancario y el explorador realiza esta solicitud al servidor:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00

Como esta operación tiene un efecto secundario (inicia una transacción monetaria) el sitio bancario ha elegido exigir HTTP POST para iniciar esta operación. El servidor lee el token de autenticación de la solicitud, busca el número de cuenta del usuario actual, comprueba que existen fondos suficientes y, después, inicia la transacción a la cuenta de destino.

Una vez que se completa la operación, el usuario sale del sitio bancario y visita otras ubicaciones en la web. Uno de esos sitios, fabrikam.com, incluye el siguiente marcado en una página insertada dentro de un elemento <iframe>:

<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
    <input type="hidden" name="toAcct" value="67890" />
    <input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
    document.getElementById('theForm').submit();
</script>

¿Qué hace que el explorador realice esta solicitud?:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00

El atacante aprovecha el hecho de que el usuario todavía puede tener un token de autenticación válido para el sitio web de destino, y usa un pequeño fragmento de código de Javascript para el explorador realice una operación HTTP POST en el sitio de destino automáticamente. Si el token de autenticación sigue siendo válido, el sitio bancario iniciará una transferencia de 250 USD en la cuenta que elija el atacante.

Mitigaciones ineficaces

Es interesante tener en cuenta que, en el escenario anterior, el hecho de que se accediera a WoodgroveBank.com mediante SSL y tuviera una cookie de autenticación solo de SSL no ha sido suficiente para frustrar el ataque. El atacante puede especificar el esquema de URI (https) en su elemento <form> y el explorador seguirá enviando cookies no expiradas al sitio de destino siempre que sean coherentes con el esquema de URI del destino previsto.

Se podría argumentar que el usuario simplemente no debe visitar sitios que no son de confianza, ya que visitar solo sitios de confianza ayuda a mantenerse la seguridad en línea. En cierto modo es verdad, pero desafortunadamente este consejo no siempre es práctico. Es posible que el usuario "confíe" en el sitio de noticias local ConsolidatedMessenger.com y lo visite, pero ese sitio tiene una vulnerabilidad XSS que permite a un atacante insertar el mismo fragmento de código que se ejecutaba en fabrikam.com.

Puede comprobar que las solicitudes entrantes tienen un encabezado Referer que hace referencia al dominio. Esto detendrá las solicitudes enviadas involuntariamente desde un dominio de terceros. Pero algunos usuarios deshabilitan el encabezado Referer del explorador por motivos de privacidad, y los atacantes a veces pueden suplantar ese encabezado si la víctima tiene instalado cierto software no seguro. La comprobación del encabezado Referer no se considera un enfoque seguro para evitar ataques XSRF.

Mitigaciones de XSRF en Web Stack Runtime

En ASP.NET Web Stack Runtime se usa una variante del patrón de token de sincronizador para defenderse de ataques XSRF. El formato general del patrón de token de sincronizador es que dos tokens anti XSRF se envían al servidor con cada solicitud HTTP POST (además del token de autenticación): un token como una cookie y el otro como un valor de formulario. Los valores de token generados por el runtime de ASP.NET no son deterministas ni predecibles por parte de un atacante. Cuando se envían los tokens, el servidor permitirá que la solicitud continúe solo si los dos tokens superan una comprobación de comparación.

El token de sesión de comprobación de solicitudes XSRF se almacena como una cookie HTTP y actualmente contiene la siguiente información en su carga:

  • Un token de seguridad, que consta de un identificador aleatorio de 128 bits.
    En la imagen siguiente se muestra el token de sesión de comprobación de solicitudes XSRF con las herramientas de desarrollo F12 de Internet Explorer: (Tenga en cuenta que esta es la implementación actual y está sujeta a cambios).

Screenshot that shows the My A S P dot NET M V C Application Index page. The Network tab is open.

El token de campo se almacena como <input type="hidden" /> y contiene la siguiente información en su carga:

Las cargas de los tokens anti-XSRF se cifran y firman, por lo que no puede ver el nombre de usuario al usar herramientas para examinar los tokens. Cuando la aplicación web tiene como destino ASP.NET 4.0, la rutina MachineKey.Encode proporciona servicios criptográficos. Cuando la aplicación web tiene como destino ASP.NET 4.5 o superior, la rutina MachineKey.Protect proporciona servicios criptográficos, lo que ofrece mejor rendimiento, extensibilidad y seguridad. Para más información, vea las entradas de blog siguientes:

Generación de los tokens

Para generar los tokens anti-XSRF, llame al método @Html.AntiForgeryToken desde una vista MVC o a @AntiForgery.GetHtml() desde una página de Razor. Después, el runtime realizará los pasos siguientes:

  1. Si la solicitud HTTP actual ya contiene un token de sesión anti-XSRF (la cookie anti-XSRF __RequestVerificationToken), el token de seguridad se extrae de él. Si la solicitud HTTP no contiene un token de sesión anti-XSRF o si se produce un error en la extracción del token de seguridad, se generará un nuevo token anti-XSRF aleatorio.
  2. Se genera un token de campo anti-XSRF mediante el token de seguridad del paso (1) anterior y la identidad del usuario que ha iniciado sesión actual. (Para más información sobre cómo determinar la identidad de usuario, vea la sección Escenarios con compatibilidad especial a continuación). Además, si se configura una instancia de IAntiForgeryAdditionalDataProvider, el runtime llamará a su método GetAdditionalData e incluirá la cadena devuelta en el token de campo. (Vea la sección Configuración y extensibilidad para más información).
  3. Si se ha generado un nuevo token anti-XSRF en el paso (1), se creará un token de sesión para contenerlo y se agregará a la colección de cookies HTTP de salida. El token de campo del paso (2) se encapsulará en un elemento <input type="hidden" /> y este marcado HTML será el valor devuelto de Html.AntiForgeryToken() o AntiForgery.GetHtml().

Validación de los tokens

Para validar los tokens anti-XSRF entrantes, el desarrollador incluye un atributo ValidateAntiForgeryToken en su acción o controlador de MVC, o llama a @AntiForgery.Validate() desde la página de Razor. El runtime realizará los pasos siguientes:

  1. El token de sesión entrante y el token de campo se leen, y se extrae el token anti-XSRF de cada uno. Los tokens anti-XSRF deben ser idénticos según el paso (2) en la rutina de generación.
  2. Si el usuario actual está autenticado, su nombre de usuario se compara con el nombre de usuario almacenado en el token de campo. Los nombres de usuario deben coincidir.
  3. Si se configura una instancia de IAntiForgeryAdditionalDataProvider, el runtime llama a su método ValidateAdditionalData. El método debe devolver el valor booleano true.

Si la validación se realiza correctamente, se permite que la solicitud continúe. Si se produce un error en la validación, el marco iniciará una excepción HttpAntiForgeryException.

Condiciones de error

A partir de ASP.NET Web Stack Runtime v2, cualquier excepción HttpAntiForgeryException que se inicie durante la validación contendrá información detallada sobre el error. Las condiciones de error definidas actualmente son las siguientes:

  • El token de sesión o el token de formulario no están presentes en la solicitud.
  • El token de sesión o el token de formulario no se pueden leer. La causa más probable de esto es una granja de servidores que ejecuta versiones no coincidentes de ASP.NET Web Stack Runtime o una granja en la que el elemento <machineKey> de Web.config difiere entre las máquinas. Puede usar una herramienta como Fiddler para forzar esta excepción si manipula cualquiera de los tokens anti-XSRF.
  • El token de sesión y el token de campo se han intercambiado.
  • El token de sesión y el token de campo contienen tokens de seguridad no coincidentes.
  • El nombre de usuario insertado en el token de campo no coincide con el nombre de usuario que ha iniciado sesión actualmente.
  • El método IAntiForgeryAdditionalDataProvider.ValidateAdditionalData ha devuelto false.

Las utilidades anti-XSRF también pueden realizar comprobaciones adicionales durante la generación o validación de tokens, y los errores durante estas comprobaciones pueden hacer que se inicien excepciones. Vea las secciones Autenticación basada en notificaciones, WIF o ACS y Configuración y extensibilidad para más información.

Escenarios con compatibilidad especial

Autenticación anónima

El sistema anti-XSRF contiene compatibilidad especial para usuarios anónimos, donde "anónimo" se define como un usuario para el que la propiedad IIdentity.IsAuthenticated devuelve false. Los escenarios incluyen proporcionar protección contra XSRF a la página de inicio de sesión (antes de autenticar al usuario) y esquemas de autenticación personalizados en los que la aplicación usa un mecanismo distinto de IIdentity para identificar a los usuarios.

Para admitir estos escenarios, recuerde que los tokens de sesión y campo están unidos por un token de seguridad, que es un identificador opaco de 128 bits generado aleatoriamente. Este token de seguridad se usa para realizar el seguimiento de la sesión de un usuario individual a medida que navega por el sitio, por lo que sirve eficazmente para un identificador anónimo. Se usa una cadena vacía en lugar del nombre de usuario para las rutinas de generación y validación descritas antes.

Autenticación basada en notificaciones, WIF o ACS

Normalmente, para las clases IIdentity integradas en .NET Framework IIdentity.Name es suficiente para identificar de forma única a un usuario determinado dentro de una aplicación determinada. Por ejemplo, FormsIdentity.Name devuelve el nombre de usuario almacenado en la base de datos de pertenencia (que es único para todas las aplicaciones en función de esa base de datos), WindowsIdentity.Name devuelve la identidad del usuario que cumple los requisitos del dominio, etc. Estos sistemas no solo proporcionan autenticación; también identifican a los usuarios en una aplicación.

Por otro lado, la autenticación basada en notificaciones no necesita obligatoriamente la identificación de un usuario determinado. En su lugar, los tipos ClaimsPrincipal y ClaimsIdentity se asocian a un conjunto de instancias de Claim, donde las notificaciones individuales pueden ser "mayor de 18 años", "es un administrador" o cualquier otra. Como el usuario no se ha identificado necesariamente, el runtime no puede usar la propiedad ClaimsIdentity.Name como identificador único para este usuario en particular. El equipo ha visto ejemplos reales en los que ClaimsIdentity.Name devuelve null, devuelve un nombre descriptivo (para mostrar) o devuelve una cadena que no es adecuada como identificador único para el usuario.

En muchas de las implementaciones que usan la autenticación basada en notificaciones se utiliza Azure Access Control Service (ACS) en concreto. ACS permite al desarrollador configurar proveedores de identidades individuales (como ADFS, el proveedor de cuentas Microsoft, proveedores de OpenID como Yahoo!, etc.) y los proveedores de identidades devuelven identificadores de nombre. Estos identificadores de nombre pueden contener información de identificación personal (DCP), como una dirección de correo electrónico, o bien se pueden anonimizar como un identificador personal privado (PPID). Pero la tupla (proveedor de identidades, identificador de nombre) sirve como un token de seguimiento adecuado para un usuario determinado mientras navega por el sitio, por lo que ASP.NET Web Stack Runtime puede usar la tupla en lugar del nombre de usuario al generar y validar tokens de campo anti-XSRF. Los URI concretos para el proveedor de identidades y el identificador de nombre son los siguientes:

  • https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

(Vea estapágina de la documentación de ACS para más información).

Al generar o validar un token, ASP.NET Web Stack Runtime intentará enlazar a los tipos en tiempo de ejecución:

  • Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (Para el SDK de WIF).
  • System.Security.Claims.ClaimsIdentity (Para .NET 4.5).

Si estos tipos existen y, si la instancia de IIIIdentity del usuario actual implementa o crea subclases uno de estos tipos, la utilidad anti-XSRF usará la tupla (proveedor de identidades, identificador de nombre) en lugar del nombre de usuario al generar y validar los tokens. Si no hay ninguna tupla de este tipo, se producirá un error en la solicitud que describe al desarrollador cómo configurar el sistema anti-XSRF para comprender el mecanismo de autenticación basado en notificaciones concreto en uso. Vea la sección Configuración y extensibilidad para más información.

Autenticación de OAuth/OpenID

Por último, la utilidad anti-XSRF tiene compatibilidad especial con aplicaciones que usan la autenticación de OAuth u OpenID. Esta compatibilidad se basa en la heurística: si el valor IIdentity.Name actual comienza con http:// o https://, las comparaciones de nombres de usuario se realizarán mediante un comparador Ordinal en lugar del comparador OrdinalIgnoreCase predeterminado.

Configuración y extensibilidad

En ocasiones, es posible que los desarrolladores quieran un control más estricto sobre los comportamientos de validación y generación anti-XSRF. Por ejemplo, es posible que el comportamiento predeterminado de los asistentes de MVC y Web Pages de agregar automáticamente cookies HTTP a la respuesta no sea deseable y que el desarrollador quiera conservar los tokens en otra parte. Existen dos API para facilitarlo:

AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);

El método GetTokens toma como entrada un token de sesión de comprobación de solicitudes XSRF existente (que puede ser null) y genera como salida un nuevo token de sesión de comprobación de solicitudes XSRF y un token de campo. Los tokens son simplemente cadenas opacas sin decoración; por ejemplo, el valor formToken no se encapsulará en una etiqueta <input>. El valor newCookieToken puede ser null; en ese caso, el valor oldCookieToken sigue siendo válido y no es necesario establecer ninguna cookie de respuesta nueva. El autor de la llamada de GetTokens es responsable de conservar las cookies de respuesta necesarias o generar cualquier marcado necesario; el propio método GetTokens no modificará la respuesta como efecto secundario. El método Validate toma los tokens de campo y sesión entrantes, sobre los que ejecuta la lógica de validación mencionada antes.

AntiForgeryConfig

El desarrollador puede configurar el sistema anti-XSRF desde Application_Start. La configuración se realiza mediante programación. Las propiedades del tipo AntiForgeryConfig estático se describen a continuación. La mayoría de los usuarios que utilizan notificaciones querrán establecer la propiedad UniqueClaimTypeIdentifier.

Propiedad Descripción
AdditionalDataProvider Una instancia de IAntiForgeryAdditionalDataProvider que proporciona datos adicionales durante la generación de tokens y consume datos adicionales durante la validación del token. El valor predeterminado es null. Para más información, vea la sección IAntiForgeryAdditionalDataProvider.
CookieName Cadena que proporciona el nombre de la cookie HTTP que se usa para almacenar el token de sesión anti-XSRF. Si no se establece este valor, se generará automáticamente un nombre en función de la ruta de acceso virtual implementada de la aplicación. El valor predeterminado es null.
RequireSsl Valor booleano que determina si los tokens anti-XSRF se deben enviar por medio de un canal protegido por SSL. Si este valor es true, las cookies generadas automáticamente tendrán establecida la marca "secure" y las API anti-XSRF iniciarán excepciones si se llaman desde dentro de una solicitud que no se envía mediante SSL. El valor predeterminado es false.
SuppressIdentityHeuristicChecks Valor booleano que determina si el sistema anti-XSRF debe desactivar su compatibilidad con identidades basadas en notificaciones. Si este valor es true, el sistema asume que IIdentity.Name es adecuado para su uso como identificador único para cada usuario y no intentará usar casos especiales de IClaimsIdentity ni ClClaimsIdentity, como se describe en la sección Autenticación basada en notificaciones, WIF o ACS. El valor predeterminado es false.
UniqueClaimTypeIdentifier Cadena que indica qué tipo de notificación es adecuado para su uso como identificador único para cada usuario. Si se establece este valor y el valor IIdentity actual se basa en notificaciones, el sistema intentará extraer una notificación del tipo especificado por UniqueClaimTypeIdentifier y el valor correspondiente se usará en lugar del nombre del usuario al generar el token de campo. Si no se encuentra el tipo de notificación, el sistema generará un error en la solicitud. El valor predeterminado es null, lo que indica que el sistema debe usar la tupla (proveedor de identidades, identificador de nombre) como se ha descrito antes en lugar del nombre del usuario.

IAntiForgeryAdditionalDataProvider

El tipo IAntiForgeryAdditionalDataProvider permite a los desarrolladores ampliar el comportamiento del sistema anti-XSRF mediante el recorrido de ida y vuelta de datos adicionales en cada token. Se llama al método GetAdditionalData cada vez que se genera un token de campo; el valor devuelto se inserta dentro del token generado. Un implementador podría devolver una marca de tiempo, un valor nonce o cualquier otro valor que quiera de este método.

Del mismo modo, se llama al método ValidateAdditionalData cada vez que se valida un token de campo y se pasa al método la cadena "datos adicionales" insertada en el token. La rutina de validación podría implementar un tiempo de espera (mediante la comprobación de la hora actual con la hora almacenada al crear el token), una rutina de comprobación de nonce o cualquier otra lógica.

Decisiones de diseño y consideraciones de seguridad

El token de seguridad que vincula los tokens de sesión y campo solo es técnicamente necesario al intentar proteger usuarios anónimos o no autenticados contra ataques XSRF. Cuando el usuario se autentica, el propio token de autenticación (presumiblemente enviado en forma de cookie) podría usarse como una mitad de un par de tokens de sincronizador. Pero hay escenarios válidos para proteger las páginas de inicio de sesión a las que llegan usuarios no autenticados y la lógica anti-XSRF se ha simplificado más mediante la generación y validación del token de seguridad, incluso para los usuarios autenticados. También proporciona cierta protección adicional en caso de que un atacante ponga en peligro un token de campo, ya que establecer o adivinar el token de sesión sería otro obstáculo para el atacante.

Los desarrolladores deben tener cuidado cuando varias aplicaciones se hospedan en un solo dominio. Por ejemplo, aunque example1.cloudapp.net y example2.cloudapp.net sean hosts diferentes, hay una relación de confianza implícita entre todos los hosts del dominio *.cloudapp.net. Esta relación de confianza implícita permite que hosts que podrían no ser de confianza afecten a las cookies de otros hosts (las directivas de mismo origen que rigen las solicitudes AJAX no se aplican necesariamente a las cookies HTTP). En ASP.NET Web Stack Runtime se proporciona cierta mitigación, ya que el nombre de usuario se inserta en el token de campo, por lo que incluso si un subdominio malintencionado consigue sobrescribir un token de sesión, no podrá generar un token de campo válido para el usuario. Pero cuando se hospedan en este entorno, las rutinas anti-XSRF integradas todavía no pueden defenderse contra el secuestro de sesión o el inicio de sesión XSRF.

Actualmente, las rutinas anti-XSRF no protegen contra el secuestro de clics. Las aplicaciones que quieran defenderse contra el secuestro de clics pueden hacerlo fácilmente si envían un encabezado X-Frame-Options: SAMEORIGIN con cada respuesta. Todos los exploradores recientes admiten este encabezado. Para más información, vea el blog de IE, el blog de SDL y OWASP. Es posible que en versiones futuras de ASP.NET Web Stack Runtime los asistentes anti-XSRF de MVC y Web Pages establezcan de manera automática este encabezado para que las aplicaciones estén protegidas automáticamente contra este ataque.

Los desarrolladores web deben seguir asegurándose de que su sitio no sea vulnerable a ataques XSS. Los ataques XSS son muy eficaces y una vulnerabilidad de seguridad correcta también interrumpiría las defensas de ASP.NET Web Stack Runtime contra ataques XSRF.

Confirmación

@LeviBroderick, que ha escrito gran parte del código de seguridad de ASP.NET de esta información.