From f36bc61fc74c85a21c8d31448198b11f96eb3bc6 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Wed, 8 Mar 2023 09:33:30 +0100 Subject: [PATCH] better validation for uploaded/imported image files Signed-off-by: Michael Kaufmann --- lib/Froxlor/SImExporter.php | 55 +++++++++++++------------------ lib/Froxlor/Settings/Store.php | 53 +++++++++++++---------------- lib/Froxlor/Validate/Validate.php | 36 ++++++++++++++++++++ 3 files changed, 80 insertions(+), 64 deletions(-) diff --git a/lib/Froxlor/SImExporter.php b/lib/Froxlor/SImExporter.php index 6cb049b4..a40e24bb 100644 --- a/lib/Froxlor/SImExporter.php +++ b/lib/Froxlor/SImExporter.php @@ -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]); + if (Validate::validateBase64Image($value)) { + $img_data = base64_decode($value); + $img_filename = explode('?', $_data[$index_split[0] . '.' . $index_split[1]], 2)[0]; - file_put_contents($img_filename, $img_data); + $spl = explode('.', $img_filename); + $file_extension = strtolower(array_pop($spl)); + unset($spl); - 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 (!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()); } - 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); } } // all good diff --git a/lib/Froxlor/Settings/Store.php b/lib/Froxlor/Settings/Store.php index 451df1aa..8c949d79 100644 --- a/lib/Froxlor/Settings/Store.php +++ b/lib/Froxlor/Settings/Store.php @@ -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"); - } + $image_content = file_get_contents($_FILES[$fieldname]['tmp_name']); + $value = base64_encode($image_content); + if (Validate::validateBase64Image($value)) { + $img_filename = $_FILES[$fieldname]['name']; - // Determine file extension - $spl = explode('.', $_FILES[$fieldname]['name']); - $file_extension = strtolower(array_pop($spl)); - unset($spl); + $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"); + 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(); } - - // 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(); } // Delete file? diff --git a/lib/Froxlor/Validate/Validate.php b/lib/Froxlor/Validate/Validate.php index a3d83ca5..ae57e5ab 100644 --- a/lib/Froxlor/Validate/Validate.php +++ b/lib/Froxlor/Validate/Validate.php @@ -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; + } }