CVE-2024-25641: Insufficient Information (NVD-CWE-noinfo)
value: Cacti provides an operational monitoring and fault management framework. Prior to version 1.2.27, an arbitrary file write vulnerability, exploitable through the "Package Import" feature, allows authenticated users having the "Import Templates" permission to execute arbitrary PHP code on the web server. The vulnerability is located within the `import_package()` function defined into the `/lib/import.php` script. The function blindly trusts the filename and file content provided within the XML data, and writes such files into the Cacti base path (or even outside, since path traversal sequences are not filtered). This can be exploited to write or overwrite arbitrary files on the web server, leading to execution of arbitrary PHP code or other security impacts. Version 1.2.27 contains a patch for this issue.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-05-14T15:05Z
Repository: https://github.com/Cacti/cacti
Language: PHP
Files changed: lib/import.php, package_import.php
Commit date: 2024-04-07 15:25:58 +0100
Commit message: Merge pull request from GHSA-7cmj-g5qc-pj88


Diff: diff --git a/lib/import.php b/lib/import.php
index 044fd317ce..efb15a242f 100644
--- a/lib/import.php
+++ b/lib/import.php
@@ -521,18 +521,26 @@ function import_package($xmlfile, $profile_id = 1, $remove_orphans = false, $rep
 
 	foreach ($data['files']['file'] as $f) {
 		$fdata = base64_decode($f['data'], true);
 		$name  = $f['name'];
 
+		/* The xml file without path is the main template of the package. It is processed below using $debug_data */
 		if (strpos($name, 'scripts/') !== false || strpos($name, 'resource/') !== false) {
+
 			$filename = CACTI_PATH_BASE . "/$name";
 
 			if (!$preview) {
 				if (!cacti_sizeof($import_files) || in_array($name, $import_files, true)) {
-					cacti_log('Writing file: ' . $filename, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);
+					cacti_log('Checking filepath: ' . $filename, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);
+
+					if (!preg_match('/^(scripts|resource)[a-zA-Z0-9_\-\/]*$/', dirname($name))) {
+						cacti_log('FATAL: Incorrect path: ' . $filename, true, 'IMPORT', POLLER_VERBOSITY_LOW);
+
+						$filestatus[$filename] = __('incorrect path, file not saved');
+					} elseif ((is_writeable(dirname($filename)) && !file_exists($filename)) || is_writable($filename)) {
+						cacti_log('Writing file: ' . $filename, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);
 
-					if ((is_writeable(dirname($filename)) && !file_exists($filename)) || is_writable($filename)) {
 						$file = fopen($filename, 'wb');
 
 						if (is_resource($file)) {
 							fwrite($file , $fdata, strlen($fdata));
 							fclose($file);
@@ -560,11 +568,13 @@ function import_package($xmlfile, $profile_id = 1, $remove_orphans = false, $rep
 
 				if (file_exists($filename)) {
 					$existing = md5_file($filename);
 				}
 
-				if (is_writeable(dirname($filename))) {
+				if (!preg_match('/^(scripts|resource)[a-zA-Z0-9_\-\/]*$/', dirname($name))) {
+					$filestatus[$filename] = 'incorrect path';
+				} elseif (is_writeable(dirname($filename))) {
 					if (file_exists($filename) && is_writable($filename)) {
 						if ($new == $existing) {
 							$filestatus[$filename] = 'writable, identical';
 						} else {
 							$filestatus[$filename] = 'writable, differences';
@@ -587,10 +597,11 @@ function import_package($xmlfile, $profile_id = 1, $remove_orphans = false, $rep
 						$filestatus[$filename] = 'not writable, differences';
 					}
 				} else {
 					$filestatus[$filename] = 'not writable, new';
 				}
+
 			}
 		} else {
 			if (!$preview) {
 				cacti_log('Importing XML Data for ' . $name, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);
 			} else {
diff --git a/package_import.php b/package_import.php
index 89432bb0f0..5396f8a538 100644
--- a/package_import.php
+++ b/package_import.php
@@ -556,10 +556,12 @@ function import_display_package_data($templates, $files, $package_name, $xmlfile
 							$nstatus .= ($nstatus != '' ? ', ':'') . __('Not Writable');
 						} elseif ($s == 'writable') {
 							$nstatus .= ($nstatus != '' ? ', ':'') . __('Writable');
 						} elseif ($s == 'new') {
 							$nstatus .= ($nstatus != '' ? ', ':'') . __('New');
+						} elseif ($s == 'incorrect path') {
+							$nstatus .= ($nstatus != '' ? ', ':'') . __('Incorrect path, will not be imported');
 						} else {
 							$nstatus .= ($nstatus != '' ? ', ':'') . __('Unknown');
 						}
 					}
 

Vulnerable code: 					cacti_log('Writing file: ' . $filename, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);
					if ((is_writeable(dirname($filename)) && !file_exists($filename)) || is_writable($filename)) {
				if (is_writeable(dirname($filename))) {
Fixed code: 		/* The xml file without path is the main template of the package. It is processed below using $debug_data */

					cacti_log('Checking filepath: ' . $filename, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);

					if (!preg_match('/^(scripts|resource)[a-zA-Z0-9_\-\/]*$/', dirname($name))) {
						cacti_log('FATAL: Incorrect path: ' . $filename, true, 'IMPORT', POLLER_VERBOSITY_LOW);

						$filestatus[$filename] = __('incorrect path, file not saved');
					} elseif ((is_writeable(dirname($filename)) && !file_exists($filename)) || is_writable($filename)) {
						cacti_log('Writing file: ' . $filename, false, 'IMPORT', POLLER_VERBOSITY_MEDIUM);
				if (!preg_match('/^(scripts|resource)[a-zA-Z0-9_\-\/]*$/', dirname($name))) {
					$filestatus[$filename] = 'incorrect path';
				} elseif (is_writeable(dirname($filename))) {

						} elseif ($s == 'incorrect path') {
							$nstatus .= ($nstatus != '' ? ', ':'') . __('Incorrect path, will not be imported');