
Análisis paso a paso de CVE-2022-46169: ejecución remota de código no autenticada en Cacti mediante omisión de autenticación e inyección de comandos, con configuración de laboratorio Docker y guía de explotación.
Cacti es una herramienta de monitoreo operativo de código abierto escrita en PHP, MySQL/MariaDB, que proporciona una interfaz amigable.
La vulnerabilidad fue encontrada en 2022 y afectó a todas las versiones anteriores a la 1.2.23. Este error requiere una cadena de omisión de autenticación e inyección de comandos para lograr RCE (Ejecución Remota de Código).
En este análisis de CVE, ejecutaré Cacti en Docker y usaré VSCode para el análisis de código. La configuración será bastante simple, primero necesitamos un archivo docker-compose.yaml para crear un nuevo entorno. A continuación se muestra el archivo docker-compose.yaml:
version: '2'
services:
cacti:
image: "smcline06/cacti"
container_name: cacti
domainname: example.com
hostname: localhost
ports:
- "8088:80"
environment:
- DB_NAME=cacti_master
- DB_USER=cactiuser
- DB_PASS=cactipassword
- DB_HOST=db
- DB_PORT=3306
- DB_ROOT_PASS=rootpassword
- INITIALIZE_DB=1
- TZ=America/Los_Angeles
volumes:
- cacti-data:/cacti
- cacti-spine:/spine
- cacti-backups:/backups
links:
- db
db:
image: "mariadb:10.3"
container_name: cacti_db
domainname: example.com
hostname: db
ports:
- "3307:3306" # Change host port to 3307
command:
- mysqld
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max_connections=200
- --max_heap_table_size=128M
- --max_allowed_packet=32M
- --tmp_table_size=128M
- --join_buffer_size=128M
- --innodb_buffer_pool_size=1G
- --innodb_doublewrite=ON
- --innodb_flush_log_at_timeout=3
- --innodb_read_io_threads=32
- --innodb_write_io_threads=16
- --innodb_buffer_pool_instances=9
- --innodb_file_format=Barracuda
- --innodb_large_prefix=1
- --innodb_io_capacity=5000
- --innodb_io_capacity_max=10000
environment:
- MYSQL_ROOT_PASSWORD=User@123
- TZ=America/Los_Angeles
volumes:
- cacti-db:/var/lib/mysql
volumes:
cacti-db:
cacti-data:
cacti-spine:
cacti-backups:
Después de crear el archivo, abre la línea de comandos y navega hasta el directorio del archivo, ejecuta el comando docker-compose up -d, abre el navegador y accede a localhost:8088. Primero verás una página de inicio de sesión:

La credencial predeterminada es admin/admin. El proceso de configuración se presentará en las siguientes imágenes:

Crear nueva contraseña

Después de finalizar la instalación, tendremos una pantalla de consola como esta:

Ahora comencemos a analizar la vulnerabilidad. Como sabemos, el archivo vulnerable es remote_agent.php, así que intentaremos acceder al archivo en el navegador:

Dice que no estamos autorizados para acceder al archivo. Es hora de ver el código fuente del archivo:

Verifica llamando a la función remote_client_authorized(). Profundicemos en esa función.

Primero, el servidor obtiene nuestra dirección IP a través de la función get_client_addr() y luego usa la función gethostbyaddr() para traducir nuestra IP a un nombre de host. Luego, el servidor obtiene todos los pollers disponibles en la tabla poller y compara el hostname de cada poller con tu hostname traducido desde la dirección IP. Hay una omisión aquí, dentro de get_client_addr():

Podemos ver que el servidor recuperará la dirección IP a través de uno de los siguientes encabezados:
- X-Forwarded-For
- X-Client-IP
- X-Real-IP
- X-ProxyUser-Ip
- CF-Connecting-IP
- True-Client-IP
- HTTP_X_FORWARDED
- HTTP_X_FORWARDED_FOR
- HTTP_X_CLUSTER_CLIENT_IP
- HTTP_FORWARDED_FOR
- HTTP_FORWARDED
- HTTP_CLIENT_IP
- REMOTE_ADDR
Esto nos permite controlar completamente el valor de nuestra dirección IP. En este caso, podemos usar el encabezado X-Forwarded-For para falsificar nuestra IP a una IP válida, lo que nos permite omitir la autorización. El encabezado X-Forwarded-For se usa a menudo para identificar la dirección IP original si hay un proxy o balanceador de carga entre el cliente y el servidor. Sin embargo, esto será una superficie de ataque para que los atacantes exploten. Debido a que ejecutamos cacti localmente, necesitamos especificar una dirección IP que se traduzca a localhost, que es 127.0.0.1.

Se ve bien ahora, ¿verdad? Sin embargo, ¡es solo el comienzo, colegas! Necesitamos más análisis de código para poder inyectar comandos y obtener ejecución remota de código. Después de completar la autenticación, el programa ejecutará este código:

El servidor obtendrá el parámetro action y entrará en un Switch/Case. Si el valor de action es pollerdata, el programa llamará a poll_for_data(). Esta función es vulnerable a la inyección de comandos, por lo que la analizaremos cuidadosamente.

La función tomará 3 parámetros $local_data_ids, $host_id, $poller_id recibidos de los parámetros de solicitud del usuario local_data_ids, host_id, poller_id. Observa la diferencia en la función para recuperar los parámetros: uno es get_filter_request_var y el otro es get_nfilter_request_var, hay una n adicional en la última función, hablaremos más sobre esto. Después de eso, el programa verificará si proporcionamos el parámetro local_data_ids y recorrerá cada uno para recuperar datos de la tabla poller_item basándose en local_data_ids y host_id. La consulta se guardará en $items.
