Hoy en día, muchos incidentes de seguridad web implican procesos automatizados. Los ataques de extracción de datos web, reutilización de contraseñas y fraude de clics son perpetrados por adversarios que intentan imitar a usuarios reales y, por tanto, procuran aparentar que proceden de un navegador. Como propietario de un sitio web, quiere asegurarse de atender a personas reales; y, como proveedor de servicios web, quiere que el acceso programático a su contenido se realice a través de su API, en lugar de extraerlo mediante su interfaz web, que consume más recursos y es menos estable.
Suponiendo que disponga de comprobaciones básicas para visitantes similares a cURL, el siguiente paso lógico es asegurarse de que los visitantes utilicen navegadores reales con interfaz de usuario, y no navegadores sin interfaz gráfica como PhantomJS y SlimerJS.
En este artículo, vamos a mostrar algunas técnicas para identificar las visitas realizadas mediante PhantomJS. Hemos decidido centrarnos en PhantomJS porque es el entorno de navegador sin interfaz gráfica más popular, aunque muchos de los conceptos que abordaremos también 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, salvo que se indique expresamente lo contrario. Para empezar: ¿es posible detectar PhantomJS sin siquiera responderle?
Pila HTTP
Como quizá ya sepa, PhantomJS está basado en el framework Qt. La forma en que Qt implementa la pila HTTP hace que se diferencie de otros navegadores modernos.
En primer lugar, veamos Chrome, que envía las siguientes cabeceras:

Sin embargo, en PhantomJS, la misma solicitud HTTP tiene este aspecto:

Observarás que las cabeceras de PhantomJS difieren de las de Chrome (y, de hecho, de las de todos los demás navegadores modernos) en algunos pequeños detalles:
- La cabecera Host aparece en último lugar
- El valor de la cabecera Connection combina mayúsculas y minúsculas
- El único valor de Accept-Encoding es gzip
- El User-Agent contiene «PhantomJS»
Al comprobar estas anomalías en las cabeceras HTTP del servidor, debería ser posible identificar un navegador PhantomJS.
Pero ¿es seguro fiarse de estos valores? Si un adversario utiliza un proxy para reescribir las cabeceras antes de que lleguen al navegador sin interfaz gráfica, podría modificarlas para que parezcan las de un navegador moderno normal.
Parece que abordar este problema únicamente desde el servidor no es la solución definitiva. Así que veamos qué se puede hacer en el cliente utilizando el entorno JavaScript de PhantomJS.
Comprobación del agente de usuario en el cliente
Puede que no podamos confiar en el valor de User-Agent recibido a través de HTTP, pero ¿qué ocurre en el lado del cliente?

Por desgracia, cambiar la cabecera user-agent y los valores de navigator.userAgent en PhantomJS es igual de sencillo, por lo que puede que esto no sea suficiente.
Complementos
navigator.plugins contiene una matriz de plugins presentes en el navegador. Entre los plugins habituales se incluyen Flash, ActiveX, la compatibilidad con applets de Java y «Default Browser Helper», un plugin que indica si este es el navegador predeterminado en OS X. Según nuestra investigación, la mayoría de las instalaciones nuevas de los navegadores más comunes incluyen al menos un plugin predeterminado, incluso en dispositivos móviles.
Esto contrasta con PhantomJS, que no implementa ningún complemento ni ofrece una forma de añadirlo (mediante la API de PhantomJS).
En ese caso, la siguiente comprobación podría resultar útil:

Por otro lado, es bastante sencillo falsificar esta matriz de plugins modificando el entorno JavaScript de PhantomJS antes de que se cargue la página.
Tampoco es difícil imaginar una versión personalizada de PhantomJS con plugins reales y funcionales. Esto es más fácil de lo que parece, porque el framework Qt en el que se basa PhantomJS proporciona una API nativa para implementar plugins.
Temporización
Otro aspecto interesante es cómo PhantomJS suprime los cuadros de diálogo de JavaScript:

Tras realizar varias mediciones, parece que, si el cuadro de diálogo de alerta se cierra en menos de 15 milisegundos, probablemente el navegador no esté siendo controlado por una persona. Sin embargo, este enfoque implica molestar a los usuarios reales con una alerta que tendrán que cerrar manualmente.
Propiedades globales
PhantomJS 1.x expone dos propiedades en el objeto global:

Sin embargo, estas propiedades forman parte de una función experimental y podrían cambiar en el futuro.
Falta de funcionalidades del motor de JavaScript
PhantomJS 1.x y 2.x utilizan actualmente motores WebKit obsoletos, lo que significa que algunas funciones disponibles en los navegadores más recientes no existen en PhantomJS. Esto también afecta al motor de JavaScript, ya que algunas propiedades y métodos nativos son diferentes o no están disponibles en PhantomJS.
Uno de estos métodos es Function.prototype.bind, que no está disponible en PhantomJS 1.x ni en versiones anteriores. El siguiente ejemplo comprueba si bind está presente y si no ha sido suplantado en el entorno de ejecución.

Este código es un poco demasiado complejo para explicarlo aquí en detalle, pero puedes obtener más información en nuestra presentación.
Trazas de pila
Los errores generados por el código JavaScript evaluado por PhantomJS mediante el comando evaluate contienen una traza de pila que permite identificar de forma inequívoca el navegador sin interfaz gráfica.
Por ejemplo, supongamos que PhantomJS llama a evaluate con el siguiente código:

Tenga en cuenta que este ejemplo utiliza una función personalizada indexOfString(), cuya implementación se deja como ejercicio para el lector, ya que PhantomJS puede falsear el resultado de la función nativa String.prototype.indexOf para que siempre devuelva un valor negativo.
Ahora bien, ¿cómo se consigue que un script de PhantomJS evalúe este código? Una técnica consiste en sobrescribir algunas funciones de la API del DOM de uso frecuente que probablemente se invoquen. Por ejemplo, el siguiente código sobrescribe document.querySelectorAll para inspeccionar la traza de la pila del navegador:

Resumen
En este artículo hemos analizado 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 respuesta contundente —por ejemplo, inutilizar una página dinámica o invalidar la cookie de la sesión actual—, puedes crear un obstáculo considerable para los visitantes que utilicen PhantomJS. No obstante, ten siempre presente que estas técnicas no son infalibles y que un adversario sofisticado acabará superándolas.
Para obtener más información, recomendamos ver esta grabación de nuestra presentación en AppSec USA 2014 (diapositivas). También hemos creado un repositorio de GitHub con ejemplos de implementación —y posibles formas de eludir— las técnicas presentadas aquí.
Gracias por leernos y ¡buena caza!
Colaboradores
Sergey Shekyan – @sshekyan
Ben Vinegar – @bentlegen
Bei Zhang – @ikarienator
Sobre el autor
Artículos relacionados del blog

Cree servicios públicos seguros, resilientes y preparados para la IA con F5 y AWS
Descubre cómo las organizaciones del sector público pueden modernizar de forma segura sus aplicaciones y soluciones de IA, al tiempo que gestionan los sistemas heredados, el cumplimiento normativo y el riesgo operativo.
Protección de los sistemas de IA: por qué la seguridad debe avanzar al ritmo de la innovación
Desde acelerar avances revolucionarios en medicina y educación hasta transformar la vida cotidiana, la IA está cambiando nuestra forma de vivir y trabajar. Pero también ofrece grandes oportunidades a los ciberdelincuentes.
Avanzar hacia la optimización digital tras la transformación
Descubre estrategias de optimización digital para garantizar políticas de seguridad coherentes, impulsar la madurez de la transformación digital y superar los retos de la transformación.
Proyecto de código abierto destacado: mayor observabilidad con F5 Cloud Logger iApp
En el punto de mira del código abierto: cómo aumentar la observabilidad. Lee el blog de F5 para descubrir cómo exportar fácilmente la telemetría de BIG-IP a plataformas modernas de análisis en la nube.
El estado de la entrega de aplicaciones en 2018: la transformación digital impulsa la automatización y la multinube pese a las preocupaciones de seguridad
Descubre las principales conclusiones del cuarto informe anual de F5 sobre el estado de la entrega de aplicaciones.
Estado de la entrega de aplicaciones en 2017: automatización, API y DevOps
Descubre el informe sobre el estado de la entrega de aplicaciones de 2017. Lee el blog retrospectivo de F5 sobre los inicios del auge de las redes declarativas y los flujos de DevOps.