
Analisi passo-passo di CVE-2022-46169: esecuzione remota di codice non autenticata in Cacti tramite bypass dell'autenticazione e injection di comandi, con configurazione lab Docker e walkthrough di sfruttamento.
Cacti è uno strumento di monitoraggio operativo open-source scritto in PHP, che utilizza MySQL/MariaDB e fornisce un'interfaccia intuitiva.
La vulnerabilità è stata scoperta nel 2022 e ha interessato tutte le versioni precedenti alla 1.2.23. Questo bug richiede una catena di bypass dell'autenticazione e di iniezione di comandi per ottenere RCE (Esecuzione Remota di Codice).
In questa analisi CVE, eseguirò Cacti in Docker e userò VSCode per l'analisi del codice. La configurazione sarà piuttosto semplice: prima di tutto serve un file docker-compose.yaml per creare un nuovo ambiente. Di seguito è riportato il file 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:
Dopo aver creato il file, apri la riga di comando e spostati nella directory del file, esegui il comando docker-compose up -d, apri il browser e accedi a localhost:8088. All'inizio vedrai una pagina di login:

Le credenziali predefinite sono admin/admin. Il processo di configurazione verrà mostrato nelle foto qui sotto:

Crea una nuova password

Dopo aver completato l'installazione, avremo una schermata console come questa

Ora iniziamo ad analizzare la vulnerabilità. Come sappiamo, il file vulnerabile è remote_agent.php, quindi proveremo ad accedervi dal browser

Ci dice che non siamo autorizzati ad accedere al file. È il momento di vedere il codice sorgente del file

Il controllo avviene chiamando la funzione remote_client_authorized(). Analizziamo a fondo questa funzione.

Innanzitutto, il server ottiene il nostro indirizzo IP tramite la funzione get_client_addr() e poi usa la funzione gethostbyaddr() per convertire il nostro IP in hostname. Il server recupera quindi tutti i poller disponibili nella tabella poller e confronta l'hostname di ciascun poller con il tuo hostname ottenuto dall'indirizzo IP. C'è un bypass qui, all'interno di get_client_addr():

Possiamo vedere che il server recupera l'indirizzo IP tramite uno dei seguenti header:
- 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
Questo ci consente di controllare completamente il valore del nostro indirizzo IP. In questo caso, possiamo usare l'header X-Forwarded-For per falsificare il nostro IP con un IP valido, il che ci consente di bypassare l'autorizzazione. L'header X-Forwarded-For viene spesso usato per identificare l'indirizzo IP originale quando tra il client e il server c'è un proxy o un load balancer. Tuttavia, questa è una superficie di attacco che gli attaccanti possono sfruttare. Poiché eseguiamo Cacti localmente, dobbiamo specificare un indirizzo IP che verrà tradotto in localhost, cioè 127.0.0.1.

Ora sembra a posto, vero? Tuttavia, è solo l'inizio, ragazzi!!! Servono ulteriori analisi del codice per iniettare con successo il comando e ottenere l'esecuzione remota del codice. Dopo aver completato l'autenticazione, il programma eseguirà questo codice

Il server recupera il parametro action ed entra in uno Switch/Case. Se il valore di action è pollerdata, il programma chiamerà poll_for_data(). Questa funzione è vulnerabile all'iniezione di comandi, quindi la analizzeremo attentamente.

La funzione prenderà 3 parametri: $local_data_ids, $host_id, $poller_id, ricevuti dai parametri della richiesta utente local_data_ids, host_id, poller_id. Nota la differenza nelle funzioni di recupero dei parametri: una è get_filter_request_var, l'altra è get_nfilter_request_var, c'è una n in più nell'ultima. Ne parleremo meglio in seguito. Dopodiché, il programma verificherà se abbiamo fornito il parametro local_data_ids e itererà su ciascuno per recuperare i dati dalla tabella poller_item in base a local_data_ids e host_id. La query verrà salvata in $items.
