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!

3 comentarios en “Sobre WebApi, cookies y CSRF”

  1. Pero y porque no desvelar nuestros propósitos con esta trilogía y más cuando sabemos que más que perjudicar beneficiamos a muchos.

    Vamos que porque no contar nuestra conversación privada de Skype después de tu osadía:)

    [10:20:25] Pedro Hurtado Candel: Eres un osado y te voy a responder con un post que te lo mereces:)
    [10:21:34] Eduard Tomàs i Avellana: xD
    [10:21:52] Pedro Hurtado Candel: Muy buen post, hacemos HO?
    [10:22:04] Eduard Tomàs i Avellana: HO??? que significa eso??? xDDDD
    [10:23:02] Pedro Hurtado Candel: Voy a ver si encuentro lo que te comente de los filtros svg.
    [10:23:42] Eduard Tomàs i Avellana: Podemos intentar montar un Hang, pero como quieres enfocarlo???
    [10:24:17] Pedro Hurtado Candel: Lo que hemos escrito, decirlo. Repartir cada uno una parte y que la gente se entere. Para mi eso es lo primordial.
    [10:24:33] Pedro Hurtado Candel: Que si es responsabilidad nuestra, pero vas a ver en el post de que me quejo.
    [10:24:54] Pedro Hurtado Candel: Que las templates de Visual Studio están hechas para tontos y la gente se las come sin más.
    [10:25:22] Pedro Hurtado Candel: Incluso nosotros a veces nos las comemos y no nos damos cuenta, con lo cual piensa en el resto.
    [10:27:27] Eduard Tomàs i Avellana: En eso te doy la razón… Las facilidades de las herramientas nos engañan hoy en día
    [10:27:47] Eduard Tomàs i Avellana: pero es nuestra responsabilidad saber lo que hacemos…
    [10:28:12] Pedro Hurtado Candel: Eso está claro Edu, aquí el único culpable eres tu si te lo comes.
    [10:28:31] Eduard Tomàs i Avellana: Para mi, en este caso, no es tanto una crítica a WebApi en sí, si no quizá más a la cultura del “copia eso” next->next->y pa’lante
    [10:29:21] Pedro Hurtado Candel: Efectivamente y en eso es lo que me voy a centrar en la respuesta “Desproposito II parte”.
    [10:29:52] Pedro Hurtado Candel: Fijate qye en 4.0 sino mal recuerdo el Oauth tenía unas lineas comentadas para twitter,faceboock y Google,
    [10:30:12] Pedro Hurtado Candel: Porque no hacen lo mismo con los MediaTypeFormatter.
    [10:30:33] Pedro Hurtado Candel: Piensalo y si lo ves factible hacemos ese Hangout.
    [10:37:38] Eduard Tomàs i Avellana: sips… eran para añadir los proveedores creo
    [10:37:49] Eduard Tomàs i Avellana: Pero el problema no es con los MediaTypeFormatter
    [10:37:58] Eduard Tomàs i Avellana: si los quitas tienes EL MISMO problema
    [10:38:11] Eduard Tomàs i Avellana: te puedo hacer un post con content-type:application/json y te lo comerías igual
    [10:38:18] Eduard Tomàs i Avellana: el problema es la autenticación por cookies
    [10:38:49] Pedro Hurtado Candel: Lo tienes más complicado con “application/json” en web api
    [10:39:00] Eduard Tomàs i Avellana: why?
    [10:39:08] Pedro Hurtado Candel: O me equivoco no no se puede enviar desde un Form.
    [10:39:27] Pedro Hurtado Candel: Yo te voy a enseñar mis armas
    [10:39:29] Pedro Hurtado Candel: http://webstersprodigy.net/2013/02/01/stripping-the-referer-in-a-cross-domain-post-request/
    [10:39:39] Pedro Hurtado Candel: Que tu si te lo vas a comer xDDDDD
    [10:41:24] Eduard Tomàs i Avellana: Post without referrer mi código no se lo come 😛
    [10:41:41] Eduard Tomàs i Avellana: Fíjate lo que dice él:
    CSRF where the referer had to be from the same origin or be absent completely
    [10:41:53] Eduard Tomàs i Avellana: el “be absent completely” es lo que le permite su inyección de CSRF.
    [10:42:19] Eduard Tomàs i Avellana: En mi caso mi código mi código obliga a que exista referrer

  2. Fue tan larga la conversación que solo he copiado parte, venga el final lo tenéis disponible.

    [10:42:35] Eduard Tomàs i Avellana: no digo que no sea posible, ya digo que la protección no es infalible (nada en la web lo es)
    [10:43:40] Eduard Tomàs i Avellana: Aunque tienes razón en lo de que con un

    no puedes mandar content-type application/json
    [10:43:45] Eduard Tomàs i Avellana: pero eso solo mitiga el problema
    [10:43:58] Pedro Hurtado Candel: Tu código está claro que no se lo come, pero entonces no funciona otro tipo de llamadas si lo tratas como DelegatingHandler
    [10:44:12] Eduard Tomàs i Avellana: cuales no funcionan?
    [10:44:21] Pedro Hurtado Candel: Animate y hacemos ese Hangount
    [10:44:30] Eduard Tomàs i Avellana: si, si… tengo que encontrar fecha
    [10:45:29] Eduard Tomàs i Avellana: Por cierto: http://darobin.github.io/formic/specs/json/
    [10:45:31] Pedro Hurtado Candel: Deja que lo compruebe, que estaba buscando documentación de Referrer, pero ya sabes o te hundo o te pongo en un pedestal xDDDDD
    [10:45:40] Eduard Tomàs i Avellana: lo acabo de ver ahora
    [10:45:48] Eduard Tomàs i Avellana: ya ves…
    [10:46:21] Pedro Hurtado Candel: Es una pensa que esta conversación la tengamos en privado y que el twitter sea lo que es:(
    [10:46:30] Pedro Hurtado Candel: La publicamos ?
    [10:47:21] Eduard Tomàs i Avellana: vemos como… por twitter vamos a marear a todos 😀
    [10:47:29] Eduard Tomàs i Avellana: Si quieres como comentarios en el blog…
    [10:48:23] Pedro Hurtado Candel: Yo voy a escriir un post, si de verdad podemos hacer un Hangout sería de puta madre copiarla en el post, me da lo mismo en el tuyo o el mio.
    [10:48:26] Pedro Hurtado Candel: Te parece?
    [10:48:37] Eduard Tomàs i Avellana: ok
    [10:49:55] Pedro Hurtado Candel: De todas formas lo que me has pasado lo probe y no funciona hasta donde yo se en Chrome, no lo he probado en el resto.
    [10:50:12] Pedro Hurtado Candel: Esta es otra que si se puede hacer en todos los exploradores
    [10:50:23] Pedro Hurtado Candel: pero que no soporta WebApi gracias a dios.
    [10:50:24] Pedro Hurtado Candel:


    Eduard Tomàs i Avellana

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *