Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2023-39361 — Schritt-für-Schritt-Analyse und Laboraufbau für CVE-2023-39361, eine nicht authentifizierte SQL-Injection in Cacti v1.2.24, mit Anleitung zur Ausnutzung und Eindämmung. | Kitploit
Tools/GitHubGitHub/hpt-intern-task-submission/cve-2023-39361
SchwachstellenanalyseExploitationWebanwendungs-ExploitationLernen & BildungLabs & Praxis
GitHubhpt-intern-task-submission/cve-2023-39361

CVE-2023-39361

Schritt-für-Schritt-Analyse und Laboraufbau für CVE-2023-39361, eine nicht authentifizierte SQL-Injection in Cacti v1.2.24, mit Anleitung zur Ausnutzung und Eindämmung.

Repository anzeigen
3vor 2 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

[CVE-2023-39361] Nicht authentifizierte SQL-Injection in Cacti v1.2.24

Übersicht

Cacti ist ein quelloffenes Betriebsüberwachungstool, das in PHP, MySQL/MariaDB geschrieben ist und eine benutzerfreundliche Oberfläche bietet. Die Schwachstelle wurde im Jahr 2023 gefunden und betrifft alle Versionen vor 1.2.24. Dieser Sicherheitsfehler liegt in der unsachgemäßen Implementierung beim Einfügen von Werten in eine SQL-Abfrage. Es handelt sich um eine kritische SQL-Injection-Schwachstelle, die es Angreifern ermöglicht, die Datenbank zu ändern und Code aus der Ferne auszuführen.

Laboraufbau:

In dieser Analyse werde ich Cacti der Einfachheit halber in Docker ausführen. Zuerst erstellen wir die Datei docker-compose.yml wie unten gezeigt und führen den Befehl docker-compose up -d aus.

root@kitploit:~
version: '3.5'

services:

  
  

cacti:

image: "smcline06/cacti"

container_name: CVE-2023-39361

domainname: example.com

hostname: cacti

ports:

- "80:80"

- "443:443"

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: CVE-2023-39361_db

domainname: example.com

hostname: db

ports:

- "3306:3306"

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=rootpassword

- TZ=America/Los_Angeles

volumes:

- cacti-db:/var/lib/mysql

  
  

volumes:

cacti-db:

cacti-data:

cacti-spine:

cacti-backups:

Nach dem Erstellen des Containers können wir auf Cacti zugreifen, indem wir http://localhost:80 aufrufen. Der nächste Schritt ist, Cacti auf Version 1.2.24 zu aktualisieren. Sie können das Upgrade-Skript hier herunterladen: https://pastebin.com/NfRiHLjR. Speichern Sie die Datei im selben Verzeichnis wie die docker-compose.yml-Datei und benennen Sie sie in upgrade_cacti.sh um. Führen Sie dann diese beiden Befehle aus:

root@kitploit:~
docker cp upgrade_cacti.sh CVE-2023-39361:/tmp/upgrade_cacti.sh && docker exec -it CVE-2023-39361 bash -c "bash /tmp/upgrade_cacti.sh"

Jetzt können wir loslegen. Tauchen wir in den Code ein, um zu sehen, was passiert ist, das uns einen SQL-Injection-Angriff ermöglicht. Die anfällige Datei ist graph_view.php und wir benötigen nur einen Gast-Benutzer, um auf diese Datei zuzugreifen, was es jedem ermöglicht, diese Schwachstelle auszunutzen. In dieser Datei ist die unsichere Funktion grow_right_pane_tree().

grow_right_pane_tree

Bevor wir tiefer in diese Funktion eintauchen, müssen wir rückwärts verfolgen, wie die Funktion ausgeführt wird.

root@kitploit:~
<?php
switch (get_nfilter_request_var('action')) {
//.... 
// Viele andere Fälle
case  'tree_content':
	//..... Etwas Code hier
	// ---------
	if (isset_request_var('node')) {
		$parts = explode('-', sanitize_search_string(get_request_var('node')));
		// Prüfen auf Baumanker
		if (strpos(get_nfilter_request_var('node'), 'tree_anchor') !== false) {
			$tree_id = $parts[1];
			$node_id = 0;
		} 
	//..... Etwas Code hier

	if ($tree_id > 0) {
		if (!is_tree_allowed($tree_id)) {
			header('Location: permission_denied.php');
			exit;
		}
		// Anfällige Funktion wird hier aufgerufen
		grow_right_pane_tree($tree_id, $node_id, $hgdata);
	}

Aus dem obigen Code-Snippet können wir sehen, dass die Funktion ausgeführt wird, wenn $tree_id > 0 ist. Alle Schritte können wie folgt erklärt werden:

  • Zuerst nimmt das Programm den Wert des Parameters action aus der Benutzereingabe und tritt in eine switch/case-Anweisung ein.

  • Im Fall tree_content holt der Code den Parameter node aus der Anfrage des Benutzers. Als nächstes wird die Eingabe durch das Zeichen - geteilt, in $part gespeichert und schließlich geprüft, ob tree_anchor in node vorkommt. Wenn alle Bedingungen zutreffen, wird $tree_id als zweites Element in $part zugewiesen. Zum Beispiel sind tree_content-1 oder 1-2-tree_content gültig und der Wert von $tree_id wird entsprechend gesetzt.

Ab diesem Punkt wird unsere URL so aussehen: http://localhost:80?action=tree_content&node=tree_anchor-1. Jetzt ist es an der Zeit, die Funktion grow_right_pane_tree() zu analysieren, um den Fehler auszunutzen.

grow_right_pane_tree_analyze

Der anfällige Parameter ist rfilter, der direkt in den RLIKE-Operanden innerhalb der WHERE-Klausel übergeben wird. Was ihn anfällig macht, ist, dass rfilter in doppelte Anführungszeichen " eingeschlossen ist, der Parameter jedoch von der Funktion html_validate_tree_vars() überprüft wird, die wie folgt aussieht:

root@kitploit:~
function  html_validate_tree_vars() {
/* ================= Eingabevalidierung und Sitzungsspeicherung ================= */
$filters = array(
	// .......
	'rfilter' => array(
			'filter' => FILTER_VALIDATE_IS_REGEX,
			'pageset' => true,
			'default' => '',
			),
	// ..........
	);

	validate_store_request_vars($filters, 'sess_grt');
	/* ================= Eingabevalidierung ================= */
	// ........
}

Der Filtertyp von rfilter ist auf FILTER_VALIDATE_IS_REGEX gesetzt und in $filters gespeichert, dann wird $filter an die Funktion validate_store_request_vars() übergeben. Tauchen wir in diese Funktion ein, das wird den Rest unserer Analyse offenbaren:

root@kitploit:~
function validate_store_request_vars($filters, $sess_prefix = '') {
	// .......
	if (cacti_sizeof($filters)) {
		foreach($filters as $variable => $options) {
		// ....
		elseif ($options['filter'] == FILTER_VALIDATE_IS_REGEX) {
					if (is_base64_encoded($_REQUEST[$variable])) {
						$_REQUEST[$variable] = base64_decode($_REQUEST[$variable]);
					}
					$valid = validate_is_regex($_REQUEST[$variable]);
					if ($valid === true) {
						$value = $_REQUEST[$variable];
					} else {
						$value = false;
						$custom_error = $valid;
					}
				}
// ........
function validate_is_regex($regex) {
// ........
    if (@preg_match("'" . $regex . "'", NULL) !== false) {
		ini_set('track_errors', $track_errors);
		return true;
	}

Die Funktion validate_store_request_vars() ist für die Validierung der Benutzereingabe verantwortlich. Wenn der filter-Typ FILTER_VALIDATE_IS_REGEX ist, ruft die Funktion validate_is_regex() mit der preg_match-Funktion auf, um zu überprüfen, ob unsere Eingabe gültig ist. Laut PHP-Dokumentation:

preg_match() gibt 1 zurück, wenn das pattern mit dem angegebenen subject übereinstimmt, 0, wenn nicht, oder false bei Fehler.

Die if-Anweisung verwendet einen strikten Vergleich, was bedeutet, dass wir nur dann in die if-Anweisung eintreten, wenn ein Fehler auftritt. Das $regex, also unsere Eingabe, wird zwischen zwei einfache Anführungszeichen ' eingeschlossen. Die Absicht der Entwickler ist es, zu verhindern, dass Benutzer das einfache Anführungszeichen ' injizieren, das oft verwendet wird, um einen SQL-Injection-Angriff zu starten. Das scheint eine robuste Sicherheitsimplementierung zu sein, aber schauen wir uns noch einmal an, wie rfilter in die WHERE-Klausel übergeben wird:

root@kitploit:~
$sql_where  .=  ' (gtg.title_cache RLIKE "'  .  get_request_var('rfilter') .  '" OR gtg.title RLIKE "'  .  get_request_var('rfilter') .  '")';

Aha, die Mühe der Validierung der Benutzereingabe wird bedeutungslos, da rfilter in doppelte Anführungszeichen " eingeschlossen ist, was es Angreifern erlaubt, einfach " zu injizieren, um die Anweisung zu verlassen.

Nachdem wir verstanden haben, warum es anfällig ist, werden wir nun tiefer in die Abfrage eintauchen, in die $sql_where übergeben wird.

grow_right_pane_tree_analyze

Nachdem $sql_where zugewiesen wurde, ruft das Programm die Funktion get_allowed_tree_header_graphs() auf. Die Funktion sieht so aus:

root@kitploit:~
function  get_allowed_tree_header_graphs($tree_id, $leaf_id = 0, $sql_where = '', $sql_order = 'gti.position', $sql_limit = '', &$total_rows = 0, $user_id = 0) {
	// ......
	if ($sql_where != '') {
		$sql_where = " AND ($sql_where)";
}
	$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;
	
	$graphs = db_fetch_assoc("SELECT gti.id, gti.title, gtg.local_graph_id, h.description, gt.name AS template_name, gtg.title_cache, gtg.width, gtg.height, gl.snmp_index, gl.snmp_query_id
	FROM graph_templates_graph AS gtg
	INNER JOIN graph_local AS gl
	ON gl.id = gtg.local_graph_id
	INNER JOIN graph_tree_items AS gti
	ON gti.local_graph_id = gl.id
	LEFT JOIN graph_templates AS gt
	ON gt.id = gl.graph_template_id
	LEFT JOIN host AS h
	ON h.id = gl.host_id
	$sql_where
	$sql_order
	$sql_limit");

	$sql = "SELECT  COUNT(*)
	FROM graph_templates_graph AS gtg
	INNER JOIN graph_local AS gl
	ON gl.id=gtg.local_graph_id
	INNER JOIN graph_tree_items AS gti
	ON gti.local_graph_id=gl.id
	LEFT JOIN graph_templates AS gt
	ON gt.id=gl.graph_template_id
	LEFT JOIN host AS h
	ON h.id=gl.host_id
	$sql_where";
	$total_rows = get_total_row_data($user_id, $sql, array(), 'graph');
	return  $graphs;
}

Hier wird $sql_where in eine Abfrage übergeben. Beachten Sie, dass die Variable vor der Übergabe noch zwei weitere Male zugewiesen wird, nämlich $sql_where = " AND ($sql_where)"; und $sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;. Kombiniert mit der ersten Zuweisung ergibt sich die endgültige Form von $sql_where:

root@kitploit:~
$sql_where  .=  ' (gtg.title_cache RLIKE "'  .  get_request_var('rfilter') .  '" OR gtg.title RLIKE "'  .  get_request_var('rfilter') .  '")';
$sql_where = " AND ($sql_where)";
$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id)" . $sql_where;

Nach diesen 3 Zuweisungen wird $sql_where so aussehen:

root@kitploit:~
$sql_where = "WHERE (gti.graph_tree_id=$tree_id AND gti.parent=$leaf_id) AND ((gtg.title_cache RLIKE "' . get_request_var('rfilter') . '" OR gtg.title RLIKE "' . get_request_var('rfilter') . '"))";

Die Abfrage, in die $sql_where übergeben wird, sieht überwältigend aus, aber wir können eine andere einfache Abfrage mit ähnlicher Struktur erstellen:

select * from users WHERE (TRUE) AND ((username RLIKE "' get_request_var('rfilter') '" -- etwas dahinter, das nicht notwendig ist))

Um unsere Payload erfolgreich zu injizieren, müssen wir die " und )) verlassen. Daher könnte diese Payload funktionieren "));SELECT SLEEP(5) --. Unsere Payload wird so aussehen: select * from users WHERE (TRUE) AND **((username RLIKE "'"));SELECT SLEEP(5) --. Sieht gut aus! Probieren wir es auf Cacti aus.

failed_injection_1

Der Grund, warum der Server nicht für 2 Sekunden schläft, ist, dass wir einen Fehler in der preg_match()-Funktion verursacht haben. Schauen wir uns die Funktion noch einmal an.

root@kitploit:~
if (@preg_match("'"  .  $regex  .  "'", NULL) !== false) {
ini_set('track_errors', $track_errors);
return  true;
}

Diese Funktion nimmt unsere Eingabe als regular expression. Und das ) wird verwendet, um eine Gruppe von Zeichen zu gruppieren. Da es nicht geschlossen ist, hat die Funktion einen Fehler zurückgegeben. Wenn wir andererseits "(());SELECT SLEEP(5) -- versuchen, können wir nicht aus dem (( in der SQL-Abfrage ausbrechen. Es gibt jedoch einen Trick, und zwar "OR"(("));SELECT SLEEP(5) --. Lassen Sie mich das erklären: Ich schließe (( in doppelte Anführungszeichen ein, um es zu einem String in der SQL-Abfrage zu machen, und verwende OR, um es mit dem username davor abzugleichen. Das doppelte Anführungszeichen " in regulären Ausdrücken wird jedoch nur als normales Zeichen betrachtet, daher können wir diese Funktion ausnutzen, um aus der SQL-Abfrage auszubrechen.

successful_injection

Basierend auf der Antwortzeit können wir sicher sein, dass wir erfolgreich SQL in die Datenbank injiziert haben. Dies ist eine kritische SQL-Injection-Schwachstelle. Der Angreifer kann diesen Fehler ausnutzen, um ein Administratorkonto zu übernehmen oder sogar remote Befehle auszuführen.

Behebung

Wie üblich treten Injection-Schwachstellen im Allgemeinen aufgrund fehlender Eingabevalidierung auf. In diesem Fall haben die Entwickler jedoch Validierungen angewendet, aber unsachgemäß. Um dies zu beheben, müssen wir den Parameter rfilter einfach in einfache Anführungszeichen ' anstelle von doppelten Anführungszeichen " einschließen.

fix

Mit dieser einfachen Korrektur wird die Mühe der Validierung der Benutzereingabe nun ordnungsgemäß funktionieren. Versuchen wir die Payload erneut, um zu sehen, ob sie immer noch anfällig ist.

fixed_request

Mit derselben Payload, aber jetzt ist die Antwort sehr schnell, was bedeutet, dass wir keinen SQL-Befehl mehr injizieren können.


Aus dieser CVE-Analyse können wir lernen, dass selbst ein kleiner Fehler zu einem katastrophalen Ergebnis führen kann. Wir sollten den Code sorgfältig überprüfen und unsere Website häufig testen, um sie immer sicherer zu machen. Dies ist das Ende der Analyse, ich hoffe, Sie haben heute etwas Nützliches gelernt. Happy Hacking!

Tool herunterladen