Hoy en día, muchos incidentes de web security implican automatización. Los ataques de raspado web, reutilización de contraseñas y fraude de clics son perpetrados por adversarios que intentan imitar a usuarios reales y, por lo tanto, intentarán aparentar que provienen de un navegador. Como propietario de un sitio web, desea asegurarse de brindar servicio a personas, y como proveedor de servicios web, desea que el acceso programático a su contenido pase por su API en lugar de rasparse a través de su interfaz web más pesada y menos estable.
Suponiendo que tienes controles básicos para visitantes tipo cURL, el siguiente paso razonable es asegurar que los visitantes estén usando navegadores reales controlados por UI, y no navegadores sin interfaz gráfica como PhantomJS y SlimerJS .
En este artículo, demostraremos algunas técnicas para identificar visitas mediante PhantomJS. Decidimos centrarnos en PhantomJS porque es el entorno de navegador sin cabeza más popular, pero muchos de los conceptos que cubriremos son aplicables a SlimerJS y otras herramientas.
Nota: Las técnicas presentadas en este artículo son aplicables tanto a PhantomJS 1.x como a 2.x, a menos que se mencione explícitamente. En primer lugar: ¿es posible detectar PhantomJS sin siquiera responderle?
Como usted ya sabrá, PhantomJS está construido sobre el marco Qt . La forma en que Qt implementa la pila HTTP lo distingue de otros navegadores modernos.
Primero, echemos un vistazo a Chrome, que envía los siguientes encabezados:
Sin embargo, en PhantomJS, la misma solicitud HTTP se ve así:
Notarás que los encabezados PhantomJS son distintos de los de Chrome (y, como se ve, de todos los demás navegadores modernos) en algunos aspectos sutiles:
Al verificar estas aberraciones del encabezado HTTP en el servidor, debería ser posible identificar un navegador PhantomJS.
Pero ¿es seguro creer en estos valores? Si un adversario usa un proxy para reescribir los encabezados delante del navegador sin cabeza, podría modificar esos encabezados para que parezcan un navegador moderno normal.
Parece que abordar este problema únicamente en el servidor no es la solución milagrosa. Ahora veamos qué se puede hacer en el cliente utilizando el entorno JavaScript de PhantomJS.
Es posible que no podamos confiar en el valor del agente de usuario entregado a través de HTTP, pero ¿qué sucede en el cliente?
Lamentablemente, es igualmente trivial cambiar el encabezado del agente de usuario y los valores de navigator.userAgent en PhantomJS, por lo que esto podría no ser suficiente.
navigator.plugins contiene una serie de complementos que están presentes en el navegador. Los valores típicos de los complementos incluyen Flash, ActiveX, compatibilidad con subprogramas Java y el " Ayudante de navegador predeterminado ", que es un complemento que indica si este navegador es el navegador predeterminado en OS X. En nuestra investigación, la mayoría de las instalaciones nuevas de navegadores comunes incluyen al menos un complemento predeterminado, incluso en dispositivos móviles.
Esto es diferente a PhantomJS, que no implementa ningún complemento ni proporciona una manera de agregar uno (usando la API PhantomJS ).
La siguiente comprobación podría ser útil entonces:
Por otro lado, es bastante trivial falsificar esta matriz de complementos modificando el entorno de JavaScript PhantomJS antes de que se cargue la página .
Tampoco es difícil imaginar una compilación personalizada de PhantomJS con complementos reales implementados. Esto es más fácil de lo que parece porque el marco Qt sobre el que se construye PhantomJS proporciona una API nativa para implementar complementos.
Otro punto de interés es cómo PhantomJS suprime los diálogos de JavaScript:
Después de realizar varias mediciones, parece que si el cuadro de diálogo de alerta se suprime en 15 milisegundos, es probable que el navegador no esté siendo controlado por un humano. Pero utilizar este enfoque significa molestar a los usuarios reales con una alerta que tendrán que cerrar manualmente.
PhantomJS 1.x expone dos propiedades en el objeto global:
Sin embargo, estas propiedades son parte de una característica experimental y pueden cambiar en el futuro.
PhantomJS 1.x y 2.x actualmente utilizan motores WebKit obsoletos, lo que significa que hay características del navegador que existen en navegadores más nuevos y que no existen en PhantomJS. Esto se extiende al motor de JavaScript, donde algunas propiedades y métodos nativos son diferentes o están ausentes en PhantomJS.
Uno de estos métodos es Function.prototype.bind, que falta en PhantomJS 1.x y versiones anteriores. El siguiente ejemplo verifica si el enlace está presente y que no haya sido falsificado en el entorno de ejecución.
Este código es un poco complicado para explicarlo en detalle aquí, pero puedes encontrar más información en nuestra presentación .
Los errores arrojados por el código JavaScript evaluado por PhantomJS a través del comando de evaluación contienen un seguimiento de pila identificable de forma única, a partir del cual podemos identificar el navegador sin cabeza.
Por ejemplo, supongamos que PhantomJS llama a evaluar en el siguiente código:
Tenga en cuenta que este ejemplo utiliza una función indexOfString() personalizada, que se deja como ejercicio para el lector, ya que PhantomJS puede falsificar la función nativa String.prototype.indexOf para que siempre devuelva un resultado negativo.
Ahora bien, ¿cómo se consigue que un script PhantomJS evalúe este código? Una técnica es anular algunas funciones de la API DOM que se utilizan con frecuencia y que probablemente se llamarán. Por ejemplo, el código siguiente anula document.querySelectorAll para inspeccionar el seguimiento de la pila del navegador:
En este artículo, analizamos siete técnicas diferentes para identificar PhantomJS, tanto en el servidor como mediante la ejecución de código en el entorno JavaScript del cliente de PhantomJS. Al combinar los resultados de la detección con un mecanismo de retroalimentación fuerte (por ejemplo, hacer que una página dinámica quede inerte o invalidar la cookie de la sesión actual), puede introducir un obstáculo sólido para los visitantes de PhantomJS. Sin embargo, tenga siempre presente que estas técnicas no son infalibles y que un adversario sofisticado eventualmente logrará triunfar.
Para obtener más información, le recomendamos ver esta grabación de nuestra presentación de AppSec USA 2014 ( diapositivas ). También hemos creado un repositorio en GitHub con ejemplos de implementaciones (y posibles soluciones) de las técnicas presentadas aquí.
Gracias por leer y feliz caza.
Sergey Shekyan – @sshekyan
Ben Vinagre – @bentlegen
Bei Zhang – @ikarienator