CVE-2018-11331: Unrestricted Upload of File with Dangerous Type (CWE-434)
value: An issue was discovered in Pluck before 4.7.6. Remote PHP code execution is possible because the set of disallowed filetypes for uploads in missing some applicable ones such as .phtml and .htaccess.
CWE description: The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
CVSS v3 base score: 9.8
Published: 2018-05-21T21:29Z
Repository: https://github.com/pluck-cms/pluck
Language: PHP
Files changed: data/inc/editpage.php, data/inc/files.php, data/inc/functions.all.php, data/inc/images.php, data/modules/.htaccess, data/themes/.htaccess, files/.htaccess, images/.htaccess
Commit date: 2018-05-21 12:21:01 +0200
Commit message: bugfix for XSS and backdoor file upload found by s7acktrac3 issue #58

Diff: diff --git a/data/inc/editpage.php b/data/inc/editpage.php
index 656b8d5..e5cb459 100755
--- a/data/inc/editpage.php
+++ b/data/inc/editpage.php
@@ -37,14 +37,14 @@ if (isset($_POST['save']) || isset($_POST['save_exit'])) {
 		}
 		else
 			$title = $_POST['title'];
 		//If we are editing an existing page, pass current seo-name.
 		if (isset($_GET['page'])) {
-			$seoname = save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data, $_GET['page']);
+			$seoname = latinOnlyInput(save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data, $_GET['page']));
 		} else {
 		//If we are creating a new page, don't pass seo-name.
-			$seoname = save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data);
+			$seoname = latinOnlyInput(save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data));
 		}
 		//If seoname is false, a file already exists with the same name
 		if (empty($seoname)) {
 			$error = show_error($lang['page']['name_exists'], 1, true);
 		}
diff --git a/data/inc/files.php b/data/inc/files.php
index 0f945ee..d7c9739 100755
--- a/data/inc/files.php
+++ b/data/inc/files.php
@@ -30,28 +30,31 @@ defined('IN_PLUCK') or exit('Access denied!');
 		<input type="submit" name="submit" value="<?php echo $lang['general']['upload']; ?>" />
 	</form>
 </div>
 <?php
 if (isset($_POST['submit'])) {
-		if (!copy($_FILES['filefile']['tmp_name'], 'files/'.$_FILES['filefile']['name']))
+		if (!copy($_FILES['filefile']['tmp_name'], 'files/'.latinOnlyInput(latinOnlyInput($_FILES['filefile']['name']))))
 			show_error($lang['general']['upload_failed'], 1);
 		else {
-			if (strcasecmp(substr($_FILES['filefile']['name'], -3),'php') == 0){
-				if (!rename('files/'.$_FILES['filefile']['name'], 'files/'.$_FILES['filefile']['name'].'.txt')){
+			$lastfour = strtolower(substr(latinOnlyInput($_FILES['filefile']['name']), -4));
+			$lastfive = strtolower(substr(latinOnlyInput($_FILES['filefile']['name']), -5));
+			$blockedExtentions = array('.php','php3','php4','php5','php6','php7','phtml');
+			if (in_array($lastfour, $blockedExtentions) or in_array($lastfive, $blockedExtentions) ){
+				if (!rename('files/'.latinOnlyInput($_FILES['filefile']['name']), 'files/'.latinOnlyInput($_FILES['filefile']['name']).'.txt')){
 					show_error($lang['general']['upload_failed'], 1);
 				}
-				chmod('files/'.$_FILES['filefile']['name'].'.txt', 0775);
+				chmod('files/'.latinOnlyInput($_FILES['filefile']['name']).'.txt', 0775);
 			}else{
-				chmod('files/'.$_FILES['filefile']['name'], 0775);
+				chmod('files/'.latinOnlyInput($_FILES['filefile']['name']), 0775);
 			}
 			?>
 				<div class="menudiv">
-					<strong><?php echo $lang['files']['name']; ?></strong> <?php echo $_FILES['filefile']['name']; ?>
+					<strong><?php echo $lang['files']['name']; ?></strong> <?php echo latinOnlyInput($_FILES['filefile']['name']); ?>
 					<br />
-					<strong><?php echo $lang['files']['size']; ?></strong> <?php echo $_FILES['filefile']['size'].' '.$lang['images']['bytes']; ?>
+					<strong><?php echo $lang['files']['size']; ?></strong> <?php echo latinOnlyInput($_FILES['filefile']['size']).' '.$lang['images']['bytes']; ?>
 					<br />
-					<strong><?php echo $lang['files']['type']; ?></strong> <?php echo $_FILES['filefile']['type']; ?>
+					<strong><?php echo $lang['files']['type']; ?></strong> <?php echo latinOnlyInput($_FILES['filefile']['type']); ?>
 					<br />
 					<strong><?php echo $lang['files']['success']; //TODO: Need to show this message another place, and with show_error(). ?></strong>
 				</div>
 			<?php
 		}
@@ -64,10 +67,11 @@ if (isset($_POST['submit'])) {
 //Show the uploaded images
 $files = read_dir_contents('files', 'files');
 	if ($files) {
 		natcasesort($files);
 		foreach ($files as $file) {
+			if (!($file == '.htaccess')){
 		?>
 			<div class="menudiv">
 				<span>
 					<img src="data/image/file.png" alt="" />
 				</span>
@@ -82,10 +86,11 @@ $files = read_dir_contents('files', 'files');
 						<img src="data/image/delete.png" title="<?php echo $lang['trashcan']['move_to_trash']; ?>" alt="<?php echo $lang['trashcan']['move_to_trash']; ?>" />
 					</a>
 				</span>
 			</div>
 			<?php
+			}
 		}
 		unset($files);
 	}
 	else
 		echo '<span class="kop4">'.$lang['general']['nothing_yet'].'</span>';
diff --git a/data/inc/functions.all.php b/data/inc/functions.all.php
index 31802e2..fbf9ee2 100644
--- a/data/inc/functions.all.php
+++ b/data/inc/functions.all.php
@@ -213,14 +213,32 @@ function sanitize($var, $html = true) {
  * @param string $var The url to sanitize.
  * @return string The sanitized variable.
  */
 function preventXSS($var) {
 	$var = str_replace('\'', '', $var);
+	$var = htmlspecialchars($var, ENT_COMPAT, 'UTF-8', false);
 
 	return $var;
 }
 
+/**
+ * latinOnlyInput from a URL, to make it ready for saving in a file.
+ *
+ * @since 4.7.5
+ * @package all
+ * @param string $var Variable 
+ * @return string The sanitized variable.
+ * 
+ * Added as bugfix for XSS and backdoor file upload found by s7acktrac3
+ */
+function latinOnlyInput($var) {
+	$var = str_replace(chr(0), '', $var);
+	$var = preg_replace("/[^a-zA-Z0-9.\ -_]+/", "", $var);;
+	return $var;
+}
+
+
 /**
  * Displays or returns an error, notice or success message.
  *
  * @since 4.7
  * @package all
diff --git a/data/inc/images.php b/data/inc/images.php
index 6f09d06..c006759 100644
--- a/data/inc/images.php
+++ b/data/inc/images.php
@@ -37,21 +37,21 @@ if (isset($_POST['submit'])) {
 		/* fix issue 44. Thanks to Klaus.  */
         $imagewhitelist = array('jfif', '.png', '.jpg', '.gif', 'jpeg');  
         if (!in_array(strtolower(substr($_FILES['imagefile']['name'], -4)), $imagewhitelist))
 			show_error($lang['general']['upload_failed'], 1);
 		/* end of fix issue 44. Thanks to Klaus.  */
-		if (!copy($_FILES['imagefile']['tmp_name'], 'images/'.$_FILES['imagefile']['name']))
+		if (!copy($_FILES['imagefile']['tmp_name'], 'images/'.latinOnlyInput($_FILES['imagefile']['name'])))
 			show_error($lang['general']['upload_failed'], 1);
 		else {
 			chmod('images/'.$_FILES['imagefile']['name'], 0666);
 			?>
 				<div class="menudiv">
-					<strong><?php echo $lang['images']['name']; ?></strong> <?php echo $_FILES['imagefile']['name']; ?>
+					<strong><?php echo $lang['images']['name']; ?></strong> <?php echo latinOnlyInput($_FILES['imagefile']['name']); ?>
 					<br />
-					<strong><?php echo $lang['images']['size']; ?></strong> <?php echo $_FILES['imagefile']['size'].' '.$lang['images']['bytes']; ?>
+					<strong><?php echo $lang['images']['size']; ?></strong> <?php echo latinOnlyInput($_FILES['imagefile']['size']).' '.$lang['images']['bytes']; ?>
 					<br />
-					<strong><?php echo $lang['images']['type']; ?></strong> <?php echo $_FILES['imagefile']['type']; ?>
+					<strong><?php echo $lang['images']['type']; ?></strong> <?php echo latinOnlyInput($_FILES['imagefile']['type']); ?>
 					<br />
 					<strong><?php echo $lang['images']['success']; //TODO: Need to show this message another place, and with show_error(). ?></strong>
 				</div>
 			<?php
 		}
@@ -65,10 +65,11 @@ if (isset($_POST['submit'])) {
 //Show the uploaded images
 $images = read_dir_contents('images', 'files');
 	if ($images) {
 		natcasesort($images);
 		foreach ($images as $image) {
+			if (!($image == '.htaccess')){
 		?>
 			<div class="menudiv">
 				<span>
 					<img src="data/image/image.png" alt="" />
 				</span>
@@ -83,10 +84,11 @@ $images = read_dir_contents('images', 'files');
 						<img src="data/image/delete.png" title="<?php echo $lang['trashcan']['move_to_trash']; ?>" alt="<?php echo $lang['trashcan']['move_to_trash']; ?>" />
 					</a>
 				</span>
 			</div>
 			<?php
+			}
 		}
 		unset($images);
 	}
 	else
 		echo '<span class="kop4">'.$lang['general']['nothing_yet'].'</span>';
diff --git a/data/modules/.htaccess b/data/modules/.htaccess
new file mode 100644
index 0000000..76635f2
--- /dev/null
+++ b/data/modules/.htaccess
@@ -0,0 +1,9 @@
+<FilesMatch \.php$>
+    SetHandler None
+</FilesMatch>
+
+<FilesMatch \.phtml>
+    SetHandler None
+</FilesMatch>
+
+Options -ExecCGI
diff --git a/data/themes/.htaccess b/data/themes/.htaccess
new file mode 100644
index 0000000..76635f2
--- /dev/null
+++ b/data/themes/.htaccess
@@ -0,0 +1,9 @@
+<FilesMatch \.php$>
+    SetHandler None
+</FilesMatch>
+
+<FilesMatch \.phtml>
+    SetHandler None
+</FilesMatch>
+
+Options -ExecCGI
diff --git a/files/.files b/files/.files
deleted file mode 100644
index e69de29..0000000
diff --git a/files/.htaccess b/files/.htaccess
new file mode 100644
index 0000000..76635f2
--- /dev/null
+++ b/files/.htaccess
@@ -0,0 +1,9 @@
+<FilesMatch \.php$>
+    SetHandler None
+</FilesMatch>
+
+<FilesMatch \.phtml>
+    SetHandler None
+</FilesMatch>
+
+Options -ExecCGI
diff --git a/images/.htaccess b/images/.htaccess
new file mode 100644
index 0000000..76635f2
--- /dev/null
+++ b/images/.htaccess
@@ -0,0 +1,9 @@
+<FilesMatch \.php$>
+    SetHandler None
+</FilesMatch>
+
+<FilesMatch \.phtml>
+    SetHandler None
+</FilesMatch>
+
+Options -ExecCGI
diff --git a/images/.images b/images/.images
deleted file mode 100644
index e69de29..0000000

Vulnerable code: 			$seoname = save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data, $_GET['page']);
			$seoname = save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data);
		if (!copy($_FILES['filefile']['tmp_name'], 'files/'.$_FILES['filefile']['name']))
			if (strcasecmp(substr($_FILES['filefile']['name'], -3),'php') == 0){
				if (!rename('files/'.$_FILES['filefile']['name'], 'files/'.$_FILES['filefile']['name'].'.txt')){
				chmod('files/'.$_FILES['filefile']['name'].'.txt', 0775);
				chmod('files/'.$_FILES['filefile']['name'], 0775);
					<strong><?php echo $lang['files']['name']; ?></strong> <?php echo $_FILES['filefile']['name']; ?>
					<strong><?php echo $lang['files']['size']; ?></strong> <?php echo $_FILES['filefile']['size'].' '.$lang['images']['bytes']; ?>
					<strong><?php echo $lang['files']['type']; ?></strong> <?php echo $_FILES['filefile']['type']; ?>
		if (!copy($_FILES['imagefile']['tmp_name'], 'images/'.$_FILES['imagefile']['name']))
					<strong><?php echo $lang['images']['name']; ?></strong> <?php echo $_FILES['imagefile']['name']; ?>
					<strong><?php echo $lang['images']['size']; ?></strong> <?php echo $_FILES['imagefile']['size'].' '.$lang['images']['bytes']; ?>
					<strong><?php echo $lang['images']['type']; ?></strong> <?php echo $_FILES['imagefile']['type']; ?>
Fixed code: 			$seoname = latinOnlyInput(save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data, $_GET['page']));
			$seoname = latinOnlyInput(save_page($title, $_POST['content'], $_POST['hidden'], $_POST['sub_page'], $_POST['description'], $_POST['keywords'], $module_additional_data));
		if (!copy($_FILES['filefile']['tmp_name'], 'files/'.latinOnlyInput(latinOnlyInput($_FILES['filefile']['name']))))
			$lastfour = strtolower(substr(latinOnlyInput($_FILES['filefile']['name']), -4));
			$lastfive = strtolower(substr(latinOnlyInput($_FILES['filefile']['name']), -5));
			$blockedExtentions = array('.php','php3','php4','php5','php6','php7','phtml');
			if (in_array($lastfour, $blockedExtentions) or in_array($lastfive, $blockedExtentions) ){
				if (!rename('files/'.latinOnlyInput($_FILES['filefile']['name']), 'files/'.latinOnlyInput($_FILES['filefile']['name']).'.txt')){
				chmod('files/'.latinOnlyInput($_FILES['filefile']['name']).'.txt', 0775);
				chmod('files/'.latinOnlyInput($_FILES['filefile']['name']), 0775);
					<strong><?php echo $lang['files']['name']; ?></strong> <?php echo latinOnlyInput($_FILES['filefile']['name']); ?>
					<strong><?php echo $lang['files']['size']; ?></strong> <?php echo latinOnlyInput($_FILES['filefile']['size']).' '.$lang['images']['bytes']; ?>
					<strong><?php echo $lang['files']['type']; ?></strong> <?php echo latinOnlyInput($_FILES['filefile']['type']); ?>
			if (!($file == '.htaccess')){
			}
	$var = htmlspecialchars($var, ENT_COMPAT, 'UTF-8', false);
/**
 * latinOnlyInput from a URL, to make it ready for saving in a file.
 *
 * @since 4.7.5
 * @package all
 * @param string $var Variable 
 * @return string The sanitized variable.
 * 
 * Added as bugfix for XSS and backdoor file upload found by s7acktrac3
 */
function latinOnlyInput($var) {
	$var = str_replace(chr(0), '', $var);
	$var = preg_replace("/[^a-zA-Z0-9.\ -_]+/", "", $var);;
	return $var;
}


		if (!copy($_FILES['imagefile']['tmp_name'], 'images/'.latinOnlyInput($_FILES['imagefile']['name'])))
					<strong><?php echo $lang['images']['name']; ?></strong> <?php echo latinOnlyInput($_FILES['imagefile']['name']); ?>
					<strong><?php echo $lang['images']['size']; ?></strong> <?php echo latinOnlyInput($_FILES['imagefile']['size']).' '.$lang['images']['bytes']; ?>
					<strong><?php echo $lang['images']['type']; ?></strong> <?php echo latinOnlyInput($_FILES['imagefile']['type']); ?>
			if (!($image == '.htaccess')){
			}
<FilesMatch \.php$>
    SetHandler None
</FilesMatch>

<FilesMatch \.phtml>
    SetHandler None
</FilesMatch>

Options -ExecCGI
<FilesMatch \.php$>
    SetHandler None
</FilesMatch>

<FilesMatch \.phtml>
    SetHandler None
</FilesMatch>

Options -ExecCGI
<FilesMatch \.php$>
    SetHandler None
</FilesMatch>

<FilesMatch \.phtml>
    SetHandler None
</FilesMatch>

Options -ExecCGI
<FilesMatch \.php$>
    SetHandler None
</FilesMatch>

<FilesMatch \.phtml>
    SetHandler None
</FilesMatch>

Options -ExecCGI