CVE-2023-0877 (published 2023-02-17T01:15Z)

Code Injection in GitHub repository froxlor/froxlor prior to 2.0.11.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-94
cwe_name: Improper Control of Generation of Code ('Code Injection')
description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
extended_description: 
            When a product allows a user's input to contain code syntax, it might be possible for an attacker to craft the code in such a way that it will alter the intended control flow of the product. Such an alteration could lead to arbitrary code execution.
            Injection problems encompass a wide variety of issues -- all mitigated in very different ways. For this reason, the most effective way to discuss these weaknesses is to note the distinct features which classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed. The most classic instantiations of this category of weakness are SQL injection and format string vulnerabilities.
         
         

Fix information:
repo_url: https://github.com/froxlor/froxlor
commit_details: repo_url: https://github.com/froxlor/froxlor
author: Michael Kaufmann
author_date: 2023-02-07 13:02:11+01:00
msg: run Form::processForm() when importing settings so the same validations apply if the import file has malicious content

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
repository: repo_url: https://github.com/froxlor/froxlor
repo_name: froxlor/Froxlor
description: The server administration software for your needs - The official Froxlor development Git repository
repo_language: PHP
owner: froxlor
file_changes: filename: Froxlor.php
change_type: ModificationType.MODIFY
diff: @@ -89,7 +89,7 @@ final class Froxlor
 	 *
 	 * @return bool true if version to check does not match, else false
 	 */
-	public static function hasUpdates(string $to_check): bool
+	public static function hasUpdates(string $to_check = ''): bool
 	{
 		if (empty($to_check)) {
 			$to_check = self::VERSION;
@@ -109,7 +109,7 @@ final class Froxlor
 	 *
 	 * @return bool true if version to check does not match, else false
 	 */
-	public static function hasDbUpdates(string $to_check): bool
+	public static function hasDbUpdates(string $to_check = ''): bool
 	{
 		if (empty($to_check)) {
 			$to_check = self::DBVERSION;

programming_language: PHP
filename: SImExporter.php
change_type: ModificationType.MODIFY
diff: @@ -27,6 +27,7 @@ namespace Froxlor;
 
 use Exception;
 use Froxlor\Database\Database;
+use Froxlor\UI\Form;
 use PDO;
 
 /**
@@ -79,14 +80,16 @@ class SImExporter
 				$_data[$index] = $row['value'];
 			}
 
-			if (array_key_exists($row['settinggroup'], $settings_definitions) && array_key_exists($row['varname'], $settings_definitions[$row['settinggroup']])) {
+			if (array_key_exists($row['settinggroup'], $settings_definitions) && array_key_exists($row['varname'],
+					$settings_definitions[$row['settinggroup']])) {
 				// Export image file
 				if ($settings_definitions[$row['settinggroup']][$row['varname']]['type'] === "image") {
 					if ($row['value'] === "") {
 						continue;
 					}
 
-					$_data[$index . '.image_data'] = base64_encode(file_get_contents(explode('?', $row['value'], 2)[0]));
+					$_data[$index . '.image_data'] = base64_encode(file_get_contents(explode('?', $row['value'],
+						2)[0]));
 				}
 			}
 		}
@@ -140,66 +143,35 @@ class SImExporter
 					$_data['system.le_froxlor_redirect'] = 0;
 				}
 			}
-			// store new data
-			foreach ($_data as $index => $value) {
-				$index_split = explode('.', $index, 3);
-
-				// Catch image_data and save it
-				if (isset($index_split[2]) && $index_split[2] === 'image_data' && !empty($_data[$index_split[0] . '.' . $index_split[1]])) {
-					$path = Froxlor::getInstallDir() . '/img/';
-					if (!is_dir($path) && !mkdir($path, 0775)) {
-						throw new Exception("img directory does not exist and cannot be created");
-					}
-
-					// Make sure we can write to the upload directory
-					if (!is_writable($path)) {
-						if (!chmod($path, 0775)) {
-							throw new Exception("Cannot write to img directory");
-						}
-					}
-
-					$img_data = base64_decode($value);
-					$img_filename = Froxlor::getInstallDir() . '/' . str_replace('../', '', explode('?', $_data[$index_split[0] . '.' . $index_split[1]], 2)[0]);
 
-					file_put_contents($img_filename, $img_data);
-
-					if (function_exists('finfo_open')) {
-						$finfo = finfo_open(FILEINFO_MIME_TYPE);
-						$mimetype = finfo_file($finfo, $img_filename);
-						finfo_close($finfo);
-					} else {
-						$mimetype = mime_content_type($img_filename);
-					}
-					if (empty($mimetype)) {
-						$mimetype = 'application/octet-stream';
+			$form_data = [];
+			// read in all current settings
+			$current_settings = Settings::getAll();
+			foreach ($current_settings as $setting_group => $setting) {
+				foreach ($setting as $varname => $value) {
+					// set all group/varname:values which are not in the import file
+					if (!isset($_data[$setting_group.'.'.$varname])) {
+						$_data[$setting_group.'.'.$varname] = $value;
 					}
-					if (!in_array($mimetype, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
-						@unlink($img_filename);
-						throw new Exception("Uploaded file is not a valid image");
-					}
-
-					$spl = explode('.', $img_filename);
-					$file_extension = strtolower(array_pop($spl));
-					unset($spl);
-
-					if (!in_array($file_extension, [
-						'jpeg',
-						'jpg',
-						'png',
-						'gif'
-					])) {
-						@unlink($img_filename);
-						throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
-					}
-					continue;
 				}
+			}
+			// re-format the array-key for Form::processForm
+			foreach ($_data as $key => $value) {
+				$form_data[str_replace(".", "_", $key)] = $value;
+			}
 
-				Settings::Set($index, $value);
+			// store new data
+			$settings_data = PhpHelper::loadConfigArrayDir(Froxlor::getInstallDir() . '/actions/admin/settings/');
+			Settings::loadSettingsInto($settings_data);
+
+			if (Form::processForm($settings_data, $form_data, [], null, true)) {
+				// save to DB
+				Settings::Flush();
+				// all good
+				return true;
+			} else {
+				throw new Exception("Importing settings failed");
 			}
-			// save to DB
-			Settings::Flush();
-			// all good
-			return true;
 		}
 		throw new Exception("Invalid JSON data: " . json_last_error_msg());
 	}

programming_language: PHP
filename: Settings.php
change_type: ModificationType.MODIFY
diff: @@ -329,6 +329,12 @@ class Settings
 		}
 	}
 
+	public static function getAll() : array
+	{
+		self::init();
+		return self::$data;
+	}
+
 	/**
 	 * get value from config by identifier
 	 */

programming_language: PHP
filename: Form.php
change_type: ModificationType.MODIFY
diff: @@ -203,7 +203,7 @@ class Form
 		return $returnvalue;
 	}
 
-	public static function processForm(&$form, &$input, $url_params = [], $part = null, $settings_all = [], $settings_part = null, $only_enabledisable = false)
+	public static function processForm(&$form, &$input, $url_params = [], $part = null, bool $settings_all = false, $settings_part = null, bool $only_enabledisable = false)
 	{
 		if (\Froxlor\Validate\Form::validateFormDefinition($form)) {
 			$submitted_fields = [];

programming_language: PHP