En Shape nos encontramos con muchos fragmentos sospechosos de JavaScript. Encontramos scripts que se han inyectado de forma maliciosa en páginas; puede que un cliente nos los envíe para pedirnos asesoramiento o que nuestros equipos de seguridad encuentren en la web algún recurso que parezca hacer referencia específica a ciertos aspectos de nuestro servicio. Como parte de nuestra rutina diaria, nos sumergimos de lleno en los scripts para entender qué hacen y cómo funcionan. Por lo general, están minificados, a menudo ofuscados y siempre requieren varios niveles de modificación antes de estar realmente listos para un análisis exhaustivo.
Hasta hace poco, la forma más sencilla de realizar este análisis consistía en utilizar configuraciones almacenadas en caché localmente que permitieran la edición manual o bien proxies para reescribir el contenido sobre la marcha. La solución local es la más práctica, pero los sitios web no siempre se trasladan perfectamente a otros entornos, lo que a menudo lleva a perder mucho tiempo resolviendo problemas antes de poder empezar a trabajar. Los proxies son extremadamente flexibles, pero suelen ser engorrosos y poco portátiles: cada persona tiene una configuración personalizada para su entorno y algunas están más familiarizadas con un proxy que con otro. He empezado a utilizar Chrome y su protocolo DevTools para interceptar las solicitudes y respuestas a medida que se producen y modificarlas sobre la marcha. Esta solución es compatible con cualquier plataforma que disponga de Chrome, evita multitud de problemas y se integra bien con las herramientas habituales de JavaScript. En esta publicación explicaré cómo interceptar y modificar JavaScript sobre la marcha mediante el protocolo DevTools de Chrome.
Usaremos Node, pero gran parte del contenido se puede adaptar al lenguaje que prefieras, siempre que puedas acceder fácilmente a los hooks de DevTools.
En primer lugar, si nunca has probado a automatizar Chrome mediante scripts, Eric Bidelman escribió una excelente guía de introducción a Chrome sin interfaz gráfica. Los consejos que contiene son aplicables tanto a Chrome sin interfaz gráfica como a Chrome con interfaz gráfica (con una particularidad que abordaré en la siguiente sección).
Iniciar Chrome
Usaremos la biblioteca chrome-launcher de npm para facilitar esta tarea.
chrome-launcher hace exactamente lo que imaginas y permite usar, sin cambios, las mismas opciones de línea de comandos que utilizas habitualmente desde el terminal ( aquí se mantiene una lista muy completa). Usaremos las siguientes opciones:
–window-size=1200,800
- Establece automáticamente el tamaño de la ventana en unas dimensiones razonables.
–auto-open-devtools-for-tabs
- Abre automáticamente las herramientas de desarrollo porque las usamos con frecuencia.
–user-data-dir=/tmp/chrome-testing
- Establece un directorio de datos de usuario fijo. (Lo ideal sería no necesitarlo, pero el modo no headless en Mac OSX no parece permitir interceptar solicitudes sin esta opción. Si conoces una forma mejor, házmelo saber a través de Twitter).

Prueba a ejecutar el script para asegurarte de que puedes abrir Chrome. Deberías ver algo parecido a esto:

Uso del protocolo de Chrome DevTools
También se conoce como «protocolo del depurador de Chrome», y ambos términos parecen utilizarse indistintamente en la documentación de Google. Primero, instala el paquete chrome-remote-interface mediante npm, que nos proporciona métodos prácticos para interactuar con el protocolo de DevTools. Asegúrate de tener a mano la documentación del protocolo si quieres profundizar más.
Para usar el CDP, debes conectarte al puerto del depurador y, como estamos usando la biblioteca chrome-launcher, puedes acceder fácilmente a él mediante chrome.port.
Muchos de los dominios del protocolo deben habilitarse primero, y vamos a empezar por el dominio Runtime para poder conectarnos a la API de la consola y enviar a la línea de comandos cualquier llamada a la consola que se realice en el navegador.

Ahora, cuando ejecutas el script, se abre una ventana de Chrome totalmente funcional que, además, muestra todos los mensajes de la consola en el terminal. Esto ya es fantástico de por sí, sobre todo para realizar pruebas.
Interceptar solicitudes
En primer lugar, tendremos que registrar lo que queremos interceptar enviando una lista de RequestPatterns a setRequestInterception. Se puede interceptar en la fase «Request» o en la fase «HeadersReceived» y, para modificar realmente una respuesta, tendremos que esperar a «HeadersReceived». El tipo de recurso se corresponde con los tipos que suelen aparecer en el panel de red de las herramientas para desarrolladores.
No olvides habilitar el dominio Network como hiciste anteriormente con Runtime, añadiendo Network.enable() al mismo array.

Registrar el controlador de eventos es relativamente sencillo, y cada solicitud interceptada incluye un interceptionId que puede utilizarse para consultar información sobre la solicitud o, en última instancia, permitir que continúe. En este caso, simplemente intervenimos y registramos en el terminal todas las solicitudes que interceptamos.

Modificación de solicitudes
Para modificar las solicitudes, tendremos que instalar algunas bibliotecas auxiliares que codifiquen y decodifiquen cadenas en base64. Hay muchísimas bibliotecas disponibles; puedes elegir la que prefieras. Nosotros usaremos atob y btoa.
La API para gestionar las respuestas resulta un poco engorrosa. Para procesarlas, debes incluir toda la lógica de respuesta en la interceptación de la solicitud (en lugar de limitarte, por ejemplo, a interceptar una respuesta) y, después, consultar el cuerpo mediante el ID de interceptación. Esto se debe a que es posible que el cuerpo aún no esté disponible cuando se invoque tu controlador; de este modo, puedes esperar explícitamente justo aquello que buscas. El cuerpo también puede venir codificado en base64, por lo que conviene comprobarlo y descodificarlo antes de pasarlo sin más.

Llegados a este punto, puedes dar rienda suelta a tu creatividad con JavaScript. Tu código se sitúa en mitad de una respuesta, lo que te permite acceder tanto al código JavaScript completo solicitado como devolver la respuesta modificada. ¡Genial! Solo retocaremos el JS añadiendo un console.log al final para que nuestro terminal reciba un mensaje cuando el código modificado se ejecute en el navegador.
No podemos limitarnos a enviar únicamente un cuerpo modificado, ya que el contenido podría entrar en conflicto con las cabeceras que se enviaron con el recurso original. Como estás realizando pruebas y ajustes activamente, probablemente te convenga empezar por lo básico antes de preocuparte demasiado por cualquier otra información de cabecera que necesites transmitir. Si es necesario, puedes acceder a las cabeceras de respuesta mediante responseHeaders que se pasa al controlador de eventos, pero por ahora crearemos nuestro propio conjunto mínimo en un array para poder manipularlo y editarlo fácilmente más adelante.

Para enviar la nueva respuesta, es necesario crear una respuesta HTTP completa codificada en base64 (incluida la línea de estado HTTP) y enviarla mediante una propiedad rawResponse del objeto que se pasa a continueInterceptedRequest.

Ahora, cuando ejecutes el script y navegues por Internet, verás algo parecido a lo siguiente en el terminal a medida que el script intercepte JavaScript y también cuando el JavaScript modificado se ejecute en el navegador y los console.log() se propaguen a través del hook que creamos al principio del tutorial.

El código completo y funcional del ejemplo básico está disponible aquí:

Próximos pasos
Puedes empezar aplicando un formato legible al código fuente, algo que siempre resulta útil al iniciar un proceso de ingeniería inversa. Sí, por supuesto, puedes hacerlo en la mayoría de los navegadores modernos, pero te conviene controlar personalmente cada paso de la modificación para mantener la coherencia entre distintos navegadores y versiones, y poder relacionar los distintos elementos a medida que analizas el código fuente. Cuando investigo código ajeno y ofuscado, me gusta cambiar el nombre de las variables y las funciones conforme empiezo a comprender su finalidad. Modificar JavaScript de forma segura no es nada sencillo y daría para una entrada de blog aparte, pero, por ahora, podrías utilizar algo como unminify para revertir técnicas habituales de minificación y ofuscación.
Puedes instalar unminify mediante npm y envolver el cuerpo de tu nuevo código JavaScript con una llamada a unminify para verlo en acción:

Profundizaremos en las transformaciones en la próxima publicación. Si tienes alguna pregunta, comentario u otro truco interesante, ponte en contacto conmigo a través de Twitter.
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.