CVE-2023-2034 (PHP, published 2023-04-14T01:15Z)
Description: value: Unrestricted Upload of File with Dangerous Type in GitHub repository froxlor/froxlor prior to 2.0.14.
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-434 \u2014 Unrestricted Upload of File with Dangerous Type
The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
Repository: https://github.com/froxlor/froxlor
Files changed:
lib/Froxlor/SImExporter.php
lib/Froxlor/Settings/Store.php
lib/Froxlor/Validate/Validate.php
Commit message: better validation for uploaded/imported image files

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>


Vulnerable code:
						$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");

						Settings::Set($index, $value);
				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");

				$save_to = 'img/' . $fielddata['image_name'] . '.' . $file_extension . '?v=' . time();

Fixed code:
use Froxlor\Validate\Validate;
				if (!isset($current_settings[$index_split[0]][$index_split[1]])) {
					continue;
				}
						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());
use Froxlor\Validate\Validate;
				$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();

	/**
	 * 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;
	}