En mis dos anteriores post (busca los enlaces al final de este post y leetelos antes si no lo has hecho) he estado hablando sobre la técnica de JSONP para consumo remoto de datos JSON entre dominios, algo a priori prohibido por el modelo de seguridad del navegador. Anteriormente comentaba que JSONP podría tener ciertos problemas de seguridad, y que para evitarlos en ASP.NET 3.5 se habían introducido cambios para paliarlos que rompían la compatibilidad con versiones anteriores. Voy a aclararlo ahora.

El problema de seguridad tiene que ver con la obtención de acceso no autorizado a información privada que se comparte con JSON.

Una de las técnicas habituales de securizar el acceso a los servicios, consiste en crear cookies encriptadas con una validez temporal limitada y asociadas a una sesión concreta. Esta técnica se utiliza de manera mayoritaria en los sitios Web actuales (como Facebook, Windows Live, o, como ya hemos visto, en la propia seguridad de ASP.NET). El problema de esta técnica es que la mayor parte de los navegadores actuales conservan las sesiones entre las diferentes pestañas abiertas (e incluso entre las diferentes ventanas), lo cual puede ser aprovechado por los atacantes para realizar ataques de phising y acceder a esa información privada en ciertos casos. Esto de las sesiones no tiene remedio fácil en navegadores como Firefox (al menos que yo sepa), pero en Internet Explorer 8.0 sí, como ya he explicado en su día.

Bien, supongamos que tenemos un servicio Web que devuelve información privada en formato JSON y que comprueba el acceso del usuario actual basándose en una cookie encriptada. El usuario tiene abierto el navegador con nuestra aplicación y recibe un correo electrónico de un atacante que lo "fuerza" a pulsar en un enlace aparentemente legal, pero que lo lleva a otra página controlada por el atacante. En esta página el pirata intenta una llamada a nuestro servicio JSON usando técnicas de JSONP, es decir, empleando etiquetas de tipo <script> para hacer la llamada. Al hacerlo, como las sesiones se conservan entre pestañas y ventanas, el servicio "se fía" y envía la información.

Lo que ocurre es que esta información generalmente será una matriz de datos JSON del estilo de la que vimos en el anterior post que devolvía Delicio.us. Al recibir en la página esta matriz, dentro de un <script>, aunque es válida realmente el atacante no puede hacer nada porque se recibe y al ser pura información, no asignada enningún sitio, simplemente se recibe y se descarta ¿no?

El problema es que en JavaScript es perfectamente lícito redefinir el constructor de una matriz, de modo que cuando se cree una de ellas se lleven a cabo algunas operaciones adicionales, por lo que el pirata puede escribir un código tan sencillo como este:

<script type="text/javascript">

var datos;

Array = function() {
  datos = this;
};

</script>

Lo que se hace es redefinir el constructor de las matrices en JavaScript, de modo que se asigna a una variable el contenido de ésta al ser creada. Este sencillo truco hace que al recibir el JSON desde el otro dominio, los datos se encuentren listos para ser usados por el pirata en el momento que lo desee, sin necesidad de estar de acuerdo con el otro servidor para envolver el JSON en una llamada a una función JavaScript, que sería lo normal y es lo que tendríamos que hacer normalmente. El pirata podría enviar a un tercer dominio los datos o hacer con ellos lo que quisiera.

El impacto de estas técnicas

¿Te parece un ataque muy rebuscado? Quizá lo sea, pero debes saber que GMail fue crackeado hace no tanto tiempo con una técnica similar para obtener acceso a los contactos de cualquier usuario. Además es, en definitiva, una técnica habitual de Cross Site Request Forgery (CSRF), las cuales tienen bastantes casos de éxito en todo tipo de Webs.

No obstante sí que hay algo que minimiza el posible impacto de esta técnica en la actualidad, y es que las últimas versiones de los navegadores están protegidas "de serie" contra ello. Así, ni Internet Explorer 7 u 8, ni Firefox 3 ni Chrome o Safari son vulnerables a la redifinición del constructor de la matriz o de la clase genérica Object. Firefox 2.x síque lo es, por ejemplo.

¿Quiere decir esto que estmao slibres de todo peligro?. En absoluto. para empezar tus usuarios pueden estar usando navegadores viejos. Además, lo cierto es que tus datos son accesibles desde otros dominios sin que esa fuera tu intención inicial, y hay otras posibles técnicas que se pueden utilizar (muchas de las cuales ni siquiera son conocidas hoy, pero pueden surgir). Por ejemplo, Twitter fue crackeado no hace mucho usando técnicas avanzadas de JavaScript y el acceso a los datos JSON remotos usando técnicas JSONP. Y otras similares pueden surgir en cualquier momento.

¿Qué se puede hacer para evitar este problema y otros del estilo que pueden aparecer?

Hay muchas recomendaciones que podemos seguir para evitar este problema.

· Primeramente podemos exigir que el acceso a nuestro servicio privado se haga siempre mediante POST y no usando el más habitual GET. El motivo es que, obviamente, la etiqueta <script> de HTML hace siempre la llamada usando un método GET, y por lo tanto este tipo de ataque no funcionaría.
· Otra técnica sencillña y efectiva es comprobar las cabeceras de la petición. Cuando la petición se hace con el objeto XmlHttpRequest generalmente las diferentes bibliotecas AJAX marcan la petición con una cabecera como esta: "X-Requested-With: XMLHttpRequest", para indicar que es una petición AJAX, y distinguirla de una normal. jQuery y ASP.NET AJAX lo hacen. Y si lo hacemos nosotros mismos es muy fácil incluirla también. Además generalmente si se hace una petición que espera JSON se suele indicar también el tipo de contenido esperado con una cabecera "Content-Type: application/json", cosa que tampoco hace la etiqueta <script>.

Otra cosa que hicieron los programadores de .NET para evitar el ataque descrito es incluir el famoso parámetro "d:" en todas las respuestas JSON de los servicios Web, tanto .asmx como WCF, expuesto como JSON.

En AJAX 1.0 para ASP.NET 2.0, cuando se devolvía un dato o colección de datos como JSON a través de un servicio Web, se obtenía un dato JSON normal y corriente, como el que acabamos de ver para Delicio.us, por ejemplo:

{"__type:": "Usuario", "Login":"usuario1", "Clave": "miclave"}

Sin embargo en la versión 3.5 de .NET (es decir, en realidad la versión de AJAX 1.0 específica para ser usada con Visual Studio 2008) y posteriores (ASP.NET 4.0), todas las respuestas JSON de servicios incluyen un parámetro adicional de nombre "d:", lo que convierte las respuestas en algo como esto:

{"d": {"__type:": "Usuario", "Login":"usuario1", "Clave": "miclave"} }

La diferencia en este caso es que siempre nos aseguramos de que lo que se devuelve es un objeto con un miembro de nombre "d" que es el que contiene los datos. En este ejemplo no se ve tanto la ventaja porque ya se devolvía un objeto, pero si devolviésemos una matriz de objetos (algo muy habitual) o un dato simple (como una cadena o un número) la cosa sí está más clara, ya que al devolverlo con forma de objeto y este miembro "d", conseguimos:

1.- Evitar que, aunque se redefina el constructor de la matriz, ello no tenga efecto sobre el acceso al JSON devuelto.
2.- El código no se pueda evaluar directamente en destino.

No tiene porqué ser una medida infalible porque pueden aparecer nuevos tipos de ataques que se salten la seguridad también, pero ayuda. Además, sigue siendo recomendable comprobar las cabeceras o permitir sólo POST para las peticiones de ciertos métodos de servicios, como ya he comentado.

Incompatibilidad

La principal consecuencia de añadir la "d" en las repsuestas JSON es que si tenemos código manual para leer estas respuestas y procesarlas, si cambiamos de versión de .NET (de la 2.0 a una superior) nos dejará de funcionar el código de JavaScript, debiendo, en tal caso hacer una de dos cosas:

1.- Leer las verdaderas propiedades de nuestros datos desde la propiedad "d" del objeto devuelto.
2.- Procesar previamente la respuesta antes de evaluarla para quitarle esa "d"

Si usamos ASP.NET AJAX no tendremos que preocuparnos pues ya hace esto último por nosotros,pero si el código es manual o usamos otras bibliotecas como jQuery, entonces tendremos que tenerlo en cuenta.

La mejor forma de verificarlo si tenemos que hacerlo de forma manual es usando la propiedad hasOwnProperty de la clase Object de JavaScript, soportado en todos los navegadores modernos (incluso IE6 y Firefox 2.0 lo soportan). Por ejemplo, si tenemos un servicio propio con las medidas de seguridad propuestas (sólo admite POST y comprueba que el tipo de datos solicitado sea JSON), si queremos llamarlo desde jQuery, con su función Ajax para llamadas a servicios, podríamos hacer esto y que funcionara con cualquier versión de .NET:

$.ajax({
  type: "POST",
  url: "miservicio.asmx/mimetodo",
  data: "{}",
  contentType: "application/json; charset=utf-8",
  dataType:"json",
  success: function(datos) {
    if (datos.hasOwnProperty("d"))
      ProcesarResultado(datos.d);
    else
      ProcesarResultado(datos);
  }
});
 
function ProcesarResultado(datos) {
  // aquí procesamos lo devuelto con la confianza de que nunca tendrá la "d"
}

Lo único que se hace es pre-procesar el resultado comprobando si tiene la propiedad "d" característica de ASP.NET 3.5+ o no, y si la tiene usar la propiedad para obtener los datos necesarios, o usar directamente lo devuelto en caso contrario.

Con cualquier otra biblioteca de AJAX o con código propio haríamos lo mismo o muy similar.

En resumen

La seguridad de los servicios basados en JSON puede estar comprometida entre dominios si no tenemos un poco de cuidado. ASP.NET AJAX a partir de .NET 3.5 incluye algunas medidas de protección, las cuales pueden producir algunas incompatibilidades. Si tenemos un poco de cuidado es fácil proteger a los usuarios de un uso indebido de su inforamción privada.

En un próximo post quiero comentar qué está preparando la W3C en las próximas versiones de HTML para facilitar nativamente las llamadas entre dominios y cómo incluso algunos navegadores hoy en día soportan estas características más seguras y estándar.

Las otras partes de este post:

· Parte 1: JSONP: llamadas AJAX a servidores remotos
· Parte 2: JSONP (II): Soporte desde ASP.NET AJAX 4.0

💪🏻 ¿Este post te ha ayudado?, ¿has aprendido algo nuevo?
Pues NO te pido que me invites a un café... Te pido algo más fácil y mucho mejor

Escrito por un humano, no por una IA