CVE-2023-2034 (published 2023-04-14T01:15Z)

Unrestricted Upload of File with Dangerous Type in GitHub repository froxlor/froxlor prior to 2.0.14.

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-434
cwe_name: Unrestricted Upload of File with Dangerous Type
description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
extended_description: 
            
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/froxlor/froxlor
commit_details: repo_url: https://github.com/froxlor/froxlor
author: Michael Kaufmann
author_date: 2023-03-08 09:33:30+01:00
msg: better validation for uploaded/imported image files

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: SImExporter.php
change_type: ModificationType.MODIFY
diff: @@ -28,6 +28,7 @@ namespace Froxlor;
 use Exception;
 use Froxlor\Database\Database;
 use Froxlor\UI\Form;
+use Froxlor\Validate\Validate;
 use PDO;
 
 /**
@@ -159,6 +160,9 @@ class SImExporter
 			// re-format the array-key for Form::processForm
 			foreach ($_data as $key => $value) {
 				$index_split = explode('.', $key, 3);
+				if (!isset($current_settings[$index_split[0]][$index_split[1]])) {
+					continue;
+				}
 				if (isset($index_split[2]) && $index_split[2] === 'image_data' && !empty($_data[$index_split[0] . '.' . $index_split[1]])) {
 					$image_data[$key] = $value;
 				} else {
@@ -190,42 +194,27 @@ class SImExporter
 							}
 						}
 
-						$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';
-						}
-						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");
+						if (Validate::validateBase64Image($value)) {
+							$img_data = base64_decode($value);
+							$img_filename = explode('?', $_data[$index_split[0] . '.' . $index_split[1]], 2)[0];
+
+							$spl = explode('.', $img_filename);
+							$file_extension = strtolower(array_pop($spl));
+							unset($spl);
+
+							if (!in_array($file_extension, [
+								'jpeg',
+								'jpg',
+								'png',
+								'gif'
+							])) {
+								throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
+							}
+							$img_filename = 'img/' . bin2hex(random_bytes(16)) . '.' . $file_extension;
+							file_put_contents(Froxlor::getInstallDir() . '/' . $img_filename, $img_data);
+							$img_index = $index_split[0].'.'.$index_split[1];
+							Settings::Set($img_index, $img_filename . '?v=' . time());
 						}
-
-						Settings::Set($index, $value);
 					}
 				}
 				// all good

programming_language: PHP
filename: Store.php
change_type: ModificationType.MODIFY
diff: @@ -36,6 +36,7 @@ use Froxlor\PhpHelper;
 use Froxlor\Settings;
 use Froxlor\System\Cronjob;
 use Froxlor\System\IPTools;
+use Froxlor\Validate\Validate;
 use PDO;
 
 class Store
@@ -415,40 +416,30 @@ class Store
 				}
 
 				// Make sure mime-type matches an image
-				if (function_exists('finfo_open')) {
-					$finfo = finfo_open(FILEINFO_MIME_TYPE);
-					$mimetype = finfo_file($finfo, $_FILES[$fieldname]['tmp_name']);
-					finfo_close($finfo);
-				} else {
-					$mimetype = mime_content_type($_FILES[$fieldname]['tmp_name']);
-				}
-				if (empty($mimetype)) {
-					$mimetype = 'application/octet-stream';
-				}
-				if (!in_array($mimetype, ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
-					throw new \Exception("Uploaded file is not a valid image");
-				}
-
-				// Determine file extension
-				$spl = explode('.', $_FILES[$fieldname]['name']);
-				$file_extension = strtolower(array_pop($spl));
-				unset($spl);
-
-				if (!in_array($file_extension, [
-					'jpeg',
-					'jpg',
-					'png',
-					'gif'
-				])) {
-					throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
-				}
-
-				// Move file
-				if (!move_uploaded_file($_FILES[$fieldname]['tmp_name'], $path . $fielddata['image_name'] . '.' . $file_extension)) {
-					throw new Exception("Unable to save image to img folder");
+				$image_content = file_get_contents($_FILES[$fieldname]['tmp_name']);
+				$value = base64_encode($image_content);
+				if (Validate::validateBase64Image($value)) {
+					$img_filename = $_FILES[$fieldname]['name'];
+
+					$spl = explode('.', $img_filename);
+					$file_extension = strtolower(array_pop($spl));
+					unset($spl);
+
+					if (!in_array($file_extension, [
+						'jpeg',
+						'jpg',
+						'png',
+						'gif'
+					])) {
+						throw new Exception("Invalid file-extension, use one of: jpeg, jpg, png, gif");
+					}
+					$filename = bin2hex(random_bytes(16)) . '.' . $file_extension;
+					// Move file
+					if (!move_uploaded_file($_FILES[$fieldname]['tmp_name'], $path . $filename)) {
+						throw new Exception("Unable to save image to img folder");
+					}
+					$save_to = 'img/' . $filename . '?v=' . time();
 				}
-
-				$save_to = 'img/' . $fielddata['image_name'] . '.' . $file_extension . '?v=' . time();
 			}
 
 			// Delete file?

programming_language: PHP
filename: Validate.php
change_type: ModificationType.MODIFY
diff: @@ -334,4 +334,40 @@ class Validate
 		}
 		return false;
 	}
+
+	/**
+	 * validates whether a given base64 string decodes to an image
+	 *
+	 * @param string $base64string
+	 * @return bool
+	 * @throws Exception
+	 */
+	public static function validateBase64Image(string $base64string) {
+
+		if (!extension_loaded('gd')) {
+			Response::standardError('phpgdextensionnotavailable', null, true);
+		}
+
+		// Decode the base64 string
+		$data = base64_decode($base64string);
+
+		// Create an image from the decoded data
+		$image = @imagecreatefromstring($data);
+
+		// Check if the image was created successfully
+		if (!$image) {
+			return false;
+		}
+
+		// Get the MIME type of the image
+		$mime = image_type_to_mime_type(getimagesizefromstring($data)[2]);
+
+		// Check if the MIME type is a valid image MIME type
+		if (strpos($mime, 'image/') !== 0) {
+			return false;
+		}
+
+		// If everything is okay, return true
+		return true;
+	}
 }

programming_language: PHP