CVE-2024-29272 (published 2024-03-22T04:15Z)

Arbitrary File Upload vulnerability in VvvebJs before version 1.7.5, allows unauthenticated remote attackers to execute arbitrary code and obtain sensitive information via the sanitizeFileName parameter in save.php.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/givanz/VvvebJs
commit_details: repo_url: https://github.com/givanz/VvvebJs
author: givanz
author_date: 2024-03-02 02:15:52+02:00
msg: Fixed vulnerabilities reported by João Victor https://github.com/givanz/VvvebJs/issues/339
repository: repo_url: https://github.com/givanz/VvvebJs
repo_name: givanz/VvvebJs
description: Drag and drop page builder library written in vanilla javascript without dependencies or build tools.
repo_language: JavaScript
owner: givanz
file_changes: filename: save.php
change_type: ModificationType.MODIFY
diff: @@ -18,10 +18,29 @@ https://github.com/givanz/VvvebJs
 */
 
 define('MAX_FILE_LIMIT', 1024 * 1024 * 2);//2 Megabytes max html file size
+define('ALLOW_PHP', false);//check if saved html contains php tag and don't save if not allowed
+define('ALLOWED_OEMBED_DOMAINS', [
+	'https://www.youtube.com/', 
+	'https://www.vimeo.com/', 
+	'https://www.twitter.com/'
+]);//load urls only from allowed websites for oembed
 
 function sanitizeFileName($file, $allowedExtension = 'html') {
+	$basename = basename($file);
+	$disallow = ['.htaccess', 'passwd'];
+	if (in_array($basename, $disallow)) {
+		showError('Filename not allowed!');
+		return '';
+	}
+	
 	//sanitize, remove double dot .. and remove get parameters if any
-	$file = __DIR__ . '/' . preg_replace('@\?.*$@' , '', preg_replace('@\.{2,}@' , '', preg_replace('@[^\/\\a-zA-Z0-9\-\._]@', '', $file)));
+	$file = preg_replace('@\?.*$@' , '', preg_replace('@\.{2,}@' , '', preg_replace('@[^\/\\a-zA-Z0-9\-\._]@', '', $file)));
+	
+	if ($file) {
+		$file = __DIR__ . DIRECTORY_SEPARATOR . $file;
+	} else {
+		return '';
+	}
 	
 	//allow only .html extension
 	if ($allowedExtension) {
@@ -35,19 +54,38 @@ function showError($error) {
 	die($error);
 }
 
+function validOembedUrl($url) {
+	foreach (ALLOWED_OEMBED_DOMAINS as $domain) {
+		if (strpos($url, $domain) === 0) {
+			return true;
+		}
+	}
+	
+	return false;
+}
+
 $html   = '';
 $file   = '';
 $action = '';
 
 if (isset($_POST['startTemplateUrl']) && !empty($_POST['startTemplateUrl'])) {
 	$startTemplateUrl = sanitizeFileName($_POST['startTemplateUrl']);
-	$html = file_get_contents($startTemplateUrl);
+	$html = '';
+	if ($startTemplateUrl) {
+		$html = file_get_contents($startTemplateUrl);
+	}
 } else if (isset($_POST['html'])){
 	$html = substr($_POST['html'], 0, MAX_FILE_LIMIT);
+	if (!ALLOW_PHP) {
+		//if (strpos($html, '<?php') !== false) {
+		if (preg_match('@<\?php|<\? |<\?=|<\s*script\s*language\s*=\s*"\s*php\s*"\s*>@', $html)) {
+			showError('PHP not allowed!');
+		}
+	}
 }
 
 if (isset($_POST['file'])) {
-	$file = sanitizeFileName($_POST['file'], false);
+	$file = sanitizeFileName($_POST['file']);
 }
 
 if (isset($_GET['action'])) {
@@ -58,7 +96,7 @@ if ($action) {
 	//file manager actions, delete and rename
 	switch ($action) {
 		case 'rename':
-			$newfile = sanitizeFileName($_POST['newfile'], false);
+			$newfile = sanitizeFileName($_POST['newfile']);
 			if ($file && $newfile) {
 				if (rename($file, $newfile)) {
 					echo "File '$file' renamed to '$newfile'";
@@ -85,28 +123,37 @@ if ($action) {
 			if ($type && $name && $html) {
 				
 				$file = sanitizeFileName("$type/$name");
-				$dir = dirname($file);
-				if (!is_dir($dir)) {
-					echo "$dir folder does not exist\n";
-					if (mkdir($dir, 0777, true)) {
-						echo "$dir folder was created\n";
+				if ($file) {
+					$dir = dirname($file);
+					if (!is_dir($dir)) {
+						echo "$dir folder does not exist\n";
+						if (mkdir($dir, 0777, true)) {
+							echo "$dir folder was created\n";
+						} else {
+							showError("Error creating folder '$dir'\n");
+						}				
+					}
+					
+					if (file_put_contents($file, $html)) {
+						echo "File saved '$file'";
 					} else {
-						showError("Error creating folder '$dir'\n");
-					}				
-				}
-				
-				if (file_put_contents($file, $html)) {
-					echo "File saved '$file'";
+						showError("Error saving file '$file'\nPossible causes are missing write permission or incorrect file path!");
+					}
 				} else {
-					showError("Error saving file '$file'\nPossible causes are missing write permission or incorrect file path!");
+					showError('Invalid filename!');
 				}
 			} else {
 				showError("Missing reusable element data!\n");
 			}
 		break;
 		case 'oembedProxy':
-			header('Content-Type: application/json');
-			echo file_get_contents($_GET['url']);
+			$url = $_GET['url'] ?? '';
+			if (validOembedUrl($url)) {
+				header('Content-Type: application/json');
+				echo file_get_contents($url);
+			} else {
+				showError('Invalid url!');
+			}
 		break;
 		default:
 			showError("Invalid action '$action'!");

programming_language: PHP
filename: scan.php
change_type: ModificationType.MODIFY
diff: @@ -19,13 +19,19 @@ https://github.com/givanz/VvvebJs
 
 //scan media folder for all files to display in media modal
 
-if (isset($_POST['mediaPath'])) {
-	define('UPLOAD_PATH', $_POST['mediaPath']);
+function sanitizePath($path) {
+	//sanitize, remove double dot .. and remove get parameters if any
+	$path = preg_replace('@/+@' , DIRECTORY_SEPARATOR, preg_replace('@\?.*$@' , '', preg_replace('@\.{2,}@' , '', preg_replace('@[^\/\\a-zA-Z0-9\-\._]@', '', $path))));
+	return $path;
+}
+
+if (isset($_POST['mediaPath']) && ($path = sanitizePath(substr($_POST['mediaPath'], 0, 256)))) {
+	define('UPLOAD_PATH', $path);
 } else {
 	define('UPLOAD_PATH', 'media');
 }
 
-$scandir = __DIR__ . '/' . UPLOAD_PATH;
+$scandir = __DIR__ . DIRECTORY_SEPARATOR. UPLOAD_PATH;
 
 // Run the recursive function
 // This function scans the files folder recursively, and builds a large array

programming_language: PHP
filename: upload.php
change_type: ModificationType.MODIFY
diff: @@ -22,7 +22,7 @@ This script is used by image upload input to save the image on the server and re
 */ 
 
 $uploadDenyExtensions  = ['php'];
-$uploadAllowExtensions = ['ico','jpg','jpeg','png','gif','webp'];
+$uploadAllowExtensions = ['ico','jpg','jpeg','png','gif','webp','svg'];
 
 function showError($error) {
 	header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
@@ -31,20 +31,27 @@ function showError($error) {
 
 function sanitizeFileName($file)
 {
+	$disallow = ['.htaccess', 'passwd'];
+	$file = str_replace($disallow, '', $file);
+	
 	//sanitize, remove double dot .. and remove get parameters if any
 	$file = preg_replace('@\?.*$@' , '', preg_replace('@\.{2,}@' , '', preg_replace('@[^\/\\a-zA-Z0-9\-\._]@', '', $file)));
+	
 	return $file;
 }
 
-
-define('UPLOAD_FOLDER', __DIR__ . '/');
+define('UPLOAD_FOLDER', __DIR__ . DIRECTORY_SEPARATOR);
 if (isset($_POST['mediaPath'])) {
-	define('UPLOAD_PATH', sanitizeFileName($_POST['mediaPath']) .'/');
+	define('UPLOAD_PATH', sanitizeFileName($_POST['mediaPath']) . DIRECTORY_SEPARATOR);
 } else {
-	define('UPLOAD_PATH', '/');
+	define('UPLOAD_PATH', DIRECTORY_SEPARATOR);
+}
+
+$fileName  = sanitizeFileName($_FILES['file']['name']);
+if (!$fileName) {
+	showError('Invalid filename!');
 }
 
-$fileName  = $_FILES['file']['name'];
 $extension = strtolower(substr($fileName, strrpos($fileName, '.') + 1));
 
 //check if extension is on deny list
@@ -52,15 +59,13 @@ if (in_array($extension, $uploadDenyExtensions)) {
 	showError("File type $extension not allowed!");
 }
 
-/*
 //comment deny code above and uncomment this code to change to a more restrictive allowed list
 // check if extension is on allow list
 if (!in_array($extension, $uploadAllowExtensions)) {
 	showError("File type $extension not allowed!");
 }
-*/
 
-$destination = UPLOAD_FOLDER . UPLOAD_PATH . '/' . $fileName;
+$destination = UPLOAD_FOLDER . UPLOAD_PATH . DIRECTORY_SEPARATOR . $fileName;
 move_uploaded_file($_FILES['file']['tmp_name'], $destination);
 
 if (isset($_POST['onlyFilename'])) {

programming_language: PHP