Sobre WebApi, cookies y CSRF

Buenas! Hace poco Pedro Hurtado ha escrito un post titulado “Una evidencia, una frikada y un despropósito”. En él habla de varias cosas, relacionadas con la seguridad de aplicaciones web, pero quiero centrarme en un solo punto (el despropósito). Básicamente lo que dice Pedro es que si usas autenticación por cookies en WebApi eres vulnerable a ataques CSRF.

Vayamos por partes…

Antes que nada debemos aclarar que es un ataque CSRF. No es sencillo de definir, pero básicamente se trata de una vulnerabilidad que explota la confianza que un sitio web tiene en un usuario (previamente autenticado en dicho sitio web). La explicación de la wikipedia no está nada mal (aunque mejor la entrada inglesa).

El ejemplo que pone Pedro es el siguiente, suponiendo que tenemos habilitada la autenticación por cookies y que el usuario se haya logado en la aplicación web (es decir, por lo tanto que la cookie esté emitida), una página web maliciosa puede hacer un ataque CSRF con algo tan simple como:

  1. <p>[FROM ATTACKER] You have been hacked??? Who knows…</p>
  2.  
  3. <div style=«display:none»>
  4.     <form id=«attacker» method=«post» action=«http://localhost:35815/api/prueba»>
  5.         <input type=«hidden» name=«valor» value=«hacked» />
  6.         <input type=«hidden» name=«valor2» value=«csrf» />
  7.     </form>
  8. </div>
  9.  
  10. @section scripts {
  11.     <script>
  12.         $(«#attacker»).submit();
  13.     </script>
  14. }

Este código estará en cualquier vista de otra aplicación web distinta a la que define el controlador web api. En mi caso en mi proyecto tengo:

image

El proyecto web CSRFVictim define el controlador WebApi llamado Prueba con el método Post. Tengo pues las dos aplicaciones ejecutándose en IISExpress:

image

La aplicación CSRFAttacker es la que tiene la vista anterior. Cuando navego a http://localhost:35594/Home/Attack (una URL de CSRFAttacker) se termina ejecutando el método Post del controlador Prueba de CSRFVictim. ¡Ojo! Con la condición de que ANTES el usuario se haya autenticado en la aplicación CSRFVictim.

Esto tiene toda la lógica del mundo: al autenticarse en CSRFVictim se genera una cookie de autenticación. Cuando luego se navega a CSRFAttacker desde el mismo navegador se hace el POST hacia CSRFVictim, el navegador envía la cookie de autenticación… Para el controlador WebApi es, a todos los efectos, como si la petición viniese de CSRFVictim. Es una petición válida: tiene la cookie de autenticación. Porque el navegador la envía. Insisto: es necesario que el usuario esté autenticado antes en CSRFVictim.

De esto podemos desprender un par de cosillas:

  1. Intenta estar logado en cualquier sitio web el mínimo tiempo posible. Y mientras estés logado en un sitio evita visitar otras URLs (especialmente las no confiables). Evita siempre el uso de opciones tipo “remember me”. Son cookies que carga el demonio.
  2. Vigila con las llamadas POST a tu aplicación. Si te autenticas por cookies eres vulnerable a CSRF. Con independencia de si usas WebApi, un controlador MVC o cualquier otra cosa. Si no haces nada más, eres por defecto vulnerable.

Si en lugar de un controlador WebApi usas un controlador MVC que responde a un POST, que se origina en un formulario HTML (p. ej. un formulario de login) puedes protegerte utilizando HtmlHelper.AntiForgeryToken(). Mira este post para los detalles: http://blog.stevensanderson.com/2008/09/01/prevent-cross-site-request-forgery-csrf-using-aspnet-mvcs-antiforgerytoken-helper/

Pero si usas WebApi no puedes usar la técnica de AntiForgeryToken, porque esa se basa en generar un input hidden… eso funciona para vistas HTML pero no para controladores que están pensados para ser llamados programáticamente. Esto nos lleva a otro consejo:

  • Evita, siempre que puedas, la autenticación por cookies en tus servicios web api.

La autenticación basada en cookies es la causa de la vulnerabilidad. Pero muchas veces se generan controladores web api para ser usados desde dentro de una aplicación web (p. ej. para soporte de llamadas Ajax). En este caso es muy útil que la misma cookie que autentique el usuario en la aplicación web, nos sirva para los servicios web api, ya que estos están pensados para ser llamados (via javascript) desde la propia aplicación web. En este caso, deberías añadir al menos algún mecanismo de seguridad adicional.

P. ej. podrías usar algo parecido a lo siguiente:

  1.   public class AntiCSRFHandler : DelegatingHandler
  2.   {
  3.       protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  4.       {
  5.  
  6.           var referrer = request.Headers.Referrer;
  7.           var uri = request.RequestUri;
  8.  
  9.             if (referrer != null && uri != null &&referrer.Port == uri.Port && referrer.Host == uri.Host)
  10.           {
  11.  
  12.               return base.SendAsync(request, cancellationToken);
  13.           }
  14.           var response = request.CreateResponse(HttpStatusCode.InternalServerError);
  15.           response.ReasonPhrase = «Invalid referrer»;
  16.           return Task.FromResult<HttpResponseMessage>(response);
  17.  
  18.       }
  19.   }

Esto crea un message handler que analiza todas las peticiones web a los servicios web api y si el referrer no es el mismo (port y host) que la url del servicio web, devuelve un HTTP 500.

Por supuesto tenemos que añadir el message handler a la configuración de Web Api:

  1. GlobalConfiguration.Configuration.MessageHandlers.Add(new AntiCSRFHandler());

Ahora si la página atacante intenta realizar el POST recibirá un HTTP 500:

image

Eso es porque el referrer contiene la URL que genera la petición (la url de la página Home/Attack de CSRFAttacker (en mi caso es http://localhost:35594/Home/Attack que es de distinto host y/o port que la URL del servicio web (http://localhost:35815/api/prueba).

Con eso tan simple ya estamos protegidos de una página atacante. Por supuesto, esa protección no es infalible, pero es relativamente segura: Mediante código HTML no se puede establecer el referrer de una página y mediante JavaScript (usando XMLHttpRequest) en principio tampoco. E incluso si se pudiese mediante XMLHttpRequest no sería un problema muy grave ya que entonces la llamada fallaría por CORS (a menos que habilites CORS en WebApi lo que no debes hacer nunca en el caso de un servicio web api que tan solo se utiliza desde la propia aplicación web).

Así pues recuerda: solo debes permitir autenticación por cookies en tus servicios web api si y solo si, son servicios web api pensados para ser usados tan solo desde la propia aplicación web. Y si lo haces asegúrate de establecer algún mecanismo adicional de seguridad para evitar CSRF.

Como siempre digo, debemos ser responsables de lo que hacemos y debemos entender lo que sucede. Es nuestro código y por lo tanto nuestra responsabilidad.

Un saludo!