Analyse étape par étape de CVE-2022-46169 : exécution de code à distance non authentifiée dans Cacti via contournement d'authentification et injection de commandes, avec configuration du laboratoire Docker et guide d'exploitation.
Cacti est un outil de surveillance opérationnelle open-source écrit en PHP, MySQL/MariaDB, offrant une interface conviviale.
La vulnérabilité a été découverte en 2022 et affecte toutes les versions antérieures à la 1.2.23. Ce bogue nécessite une chaîne de contournement d'authentification et d'injection de commandes pour réaliser une RCE (Exécution de Code à Distance).
Dans cette analyse CVE, je vais exécuter Cacti dans Docker et utiliser VSCode pour l'analyse du code. La configuration sera assez simple, nous avons d'abord besoin d'un fichier docker-compose.yaml pour créer un nouvel environnement. Voici le fichier 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" # Changer le port hôte en 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:
Après avoir créé le fichier, ouvrez la ligne de commande et naviguez jusqu'au répertoire du fichier, exécutez la commande docker-compose up -d, ouvrez le navigateur et accédez à localhost:8088. Vous verrez d'abord une page de connexion :

Les identifiants par défaut sont admin/admin. Le processus de configuration sera présenté par les photos ci-dessous :

Créer un nouveau mot de passe

Après avoir terminé l'installation, nous aurons un écran de console comme celui-ci

Commençons maintenant à analyser la vulnérabilité. Comme nous le savons, le fichier vulnérable est remote_agent.php, nous allons donc essayer d'accéder au fichier dans le navigateur

Il dit que nous ne sommes pas autorisés à accéder au fichier. Il est temps de voir le code source du fichier

Il vérifie en appelant la fonction remote_client_authorized(). Plongeons profondément dans cette fonction.

Premièrement, le serveur obtient notre adresse IP via la fonction get_client_addr() puis utilise la fonction gethostbyaddr() pour traduire notre IP en nom d'hôte. Le serveur récupère ensuite tous les pollers disponibles dans la table poller et compare le hostname de chaque poller avec votre hostname traduit à partir de l'adresse IP. Il y a un contournement ici, à l'intérieur de get_client_addr() :

Nous pouvons voir que le serveur récupérera l'adresse IP via l'un des en-têtes suivants :
- 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
Cela nous permet de contrôler complètement la valeur de notre adresse IP. Dans ce cas, nous pouvons utiliser l'en-tête X-Forwarded-For pour usurper notre IP en une IP valide, ce qui nous permet de contourner l'autorisation. L'en-tête X-Forwarded-For est souvent utilisé pour identifier l'adresse IP d'origine s'il y a un proxy ou un équilibreur de charge entre le client et le serveur. Cependant, cela constituera une surface d'attaque pour les attaquants. Comme nous exécutons Cacti localement, nous devons spécifier une adresse IP qui sera traduite en localhost, c'est-à-dire 127.0.0.1.

Ça a l'air bien maintenant, non ? Cependant, ce n'est que le début, les amis !!! Nous avons besoin de plus d'analyse de code pour injecter avec succès une commande et obtenir une exécution de code à distance. Après avoir terminé l'authentification, le programme exécutera ce code

Le serveur récupère le paramètre action et entre dans une structure Switch/Case. Si la valeur de action est pollerdata, le programme appelle poll_for_data(). Cette fonction est vulnérable à l'injection de commandes, nous allons donc l'analyser attentivement.

La fonction prend 3 paramètres $local_data_ids, $host_id, $poller_id reçus des paramètres de la requête utilisateur local_data_ids, host_id, poller_id. Remarquez la différence dans la fonction pour récupérer les paramètres, l'une est get_filter_request_var et l'autre est get_nfilter_request_var, il y a un n supplémentaire dans la dernière fonction, nous en discuterons plus en détail. Ensuite, le programme vérifie si nous avons fourni le paramètre local_data_ids et boucle sur chacun pour récupérer les données de la table poller_item en fonction de local_data_ids et host_id. La requête sera sauvegardée dans $items.