
Análise passo a passo do CVE-2022-46169: execução remota de código não autenticada no Cacti via bypass de autenticação e injeção de comandos, com configuração de laboratório Docker e passo a passo de exploração.
O Cacti é uma ferramenta de monitoramento operacional de código aberto escrita em PHP, MySQL/MariaDB, que fornece uma interface amigável.
A vulnerabilidade foi encontrada em 2022 e afetou todas as versões anteriores à 1.2.23. Esse bug requer uma cadeia de bypass de autenticação e injeção de comandos para alcançar RCE (Execução Remota de Código).
Nesta análise do CVE, executarei o Cacti no Docker e usarei o VSCode para análise de código. A configuração será bastante simples; primeiro precisamos de um arquivo docker-compose.yaml para criar um novo ambiente. Abaixo está o arquivo 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:
Após criar o arquivo, abra a linha de comando e navegue até o diretório do arquivo, execute o comando docker-compose up -d, abra o navegador e acesse localhost:8088. Primeiro você verá uma página de login:

A credencial padrão é admin/admin. O processo de configuração será apresentado pelas fotos abaixo:

Criar nova senha

Após terminar a instalação, teremos uma tela de console como esta

Agora vamos começar a analisar a vulnerabilidade. Como sabemos, o arquivo vulnerável é remote_agent.php, então tentaremos acessar o arquivo no navegador

Ele diz que não estamos autorizados a acessar o arquivo. É hora de ver o código-fonte do arquivo

Ele verifica chamando a função remote_client_authorized(). Vamos nos aprofundar nessa função.

Primeiro, o servidor obtém nosso endereço IP através da função get_client_addr() e depois usa a função gethostbyaddr() para traduzir nosso IP em hostname. O servidor então busca todos os pollers disponíveis na tabela poller e compara o hostname de cada poller com o seu hostname traduzido a partir do endereço IP. Há um bypass aqui, dentro de get_client_addr():

Podemos ver que o servidor recuperará o endereço IP através de um dos cabeçalhos:
- 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
Isso nos permite controlar totalmente o valor do nosso endereço IP. Nesse caso, podemos usar o cabeçalho X-Forwarded-For para falsificar nosso IP para um IP válido, o que nos permite contornar a autorização. O cabeçalho X-Forwarded-For é frequentemente usado para identificar o endereço IP original se houver um proxy ou balanceador de carga entre o cliente e o servidor. No entanto, isso será uma superfície de ataque para os atacantes explorarem. Como executamos o Cacti localmente, precisamos especificar um endereço IP que será traduzido para localhost, que é 127.0.0.1.

Parece bom agora, certo? Porém, isso é apenas o começo, pessoal!!! Precisamos de mais análise de código para injetar comandos com sucesso e obter execução remota de código. Após terminar a autenticação, o programa executará este código

O servidor obterá o parâmetro action e entrará em uma estrutura Switch/Case. Se o valor de action for pollerdata, o programa chamará a função poll_for_data(). Essa função é vulnerável a injeção de comandos, então vamos analisá-la cuidadosamente.

A função receberá 3 parâmetros $local_data_ids, $host_id, $poller_id recebidos dos parâmetros de solicitação do usuário local_data_ids, host_id, poller_id. Observe a diferença na função para recuperar os parâmetros: uma é get_filter_request_var e a outra é get_nfilter_request_var; há um n a mais na última função, discutiremos mais sobre isso. Depois disso, o programa verificará se fornecemos o parâmetro local_data_ids e fará um loop em cada um para recuperar dados da tabela poller_item com base em local_data_ids e host_id. A consulta será salva em $items.
