CVE-2022-1345 (published 2022-04-13T19:15Z)

Stored XSS viva .svg file upload in GitHub repository causefx/organizr prior to 2.1.1810. This allows attackers to execute malicious scripts in the user's browser and it can lead to session hijacking, sensitive data exposure, and worse.

Severity: LOW
CVSS v2 base score: 3.5 (AV:N/AC:M/Au:S/C:N/I:P/A:N)
CVSS v3 base score: 9.0, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/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/causefx/organizr
commit_details: repo_url: https://github.com/causefx/organizr
author: CauseFX
author_date: 2022-04-11 09:52:22-07:00
msg: added sanitizeUserString and sanitizeEmail functions
added sanitize to uploaded image names
added sanitize to tabs, categories, users and bookmarks
removed svg files from approved image lists
repository: repo_url: https://github.com/causefx/organizr
repo_name: causefx/Organizr
description: HTPC/Homelab Services Organizer - Written in PHP
repo_language: PHP
owner: causefx
file_changes: filename: organizr.class.php
change_type: ModificationType.MODIFY
diff: @@ -1895,7 +1895,7 @@ class Organizr
 			$tempFile = $_FILES['file']['tmp_name'];
 			$targetPath = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
 			$this->makeDir($targetPath);
-			$targetFile = $targetPath . $_FILES['file']['name'];
+			$targetFile = $targetPath . $this->sanitizeUserString($_FILES['file']['name']);
 			$this->setAPIResponse(null, pathinfo($_FILES['file']['name'], PATHINFO_BASENAME) . ' has been uploaded', null);
 			return move_uploaded_file($tempFile, $targetFile);
 		}
@@ -4873,7 +4873,7 @@ class Organizr
 		$array['type'] = ($array['type']) ?? 1;
 		$array['order'] = ($array['order']) ?? $this->getNextTabOrder() + 1;
 		if (array_key_exists('name', $array)) {
-			$array['name'] = htmlspecialchars($array['name']);
+			$array['name'] = $this->sanitizeUserString($array['name']);
 			if ($this->isTabNameTaken($array['name'])) {
 				$this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
 				return false;
@@ -4923,7 +4923,7 @@ class Organizr
 			return false;
 		}
 		if (array_key_exists('name', $array)) {
-			$array['name'] = htmlspecialchars($array['name']);
+			$array['name'] = $this->sanitizeUserString($array['name']);
 			if ($this->isTabNameTaken($array['name'], $id)) {
 				$this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
 				return false;
@@ -4995,6 +4995,7 @@ class Organizr
 		$array['order'] = ($array['order']) ?? $this->getNextCategoryOrder() + 1;
 		$array['category_id'] = ($array['category_id']) ?? $this->getNextCategoryId() + 1;
 		if (array_key_exists('category', $array)) {
+			$array['category'] = $this->sanitizeUserString($array['category']);
 			if ($this->isCategoryNameTaken($array['category'])) {
 				$this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
 				return false;
@@ -5005,6 +5006,9 @@ class Organizr
 		}
 		if (!array_key_exists('image', $array)) {
 			$this->setAPIResponse('error', 'Category image was not supplied', 422);
+			return false;
+		} else {
+			$array['image'] = $this->sanitizeUserString($array['image']);
 		}
 		$response = [
 			array(
@@ -5039,11 +5043,15 @@ class Organizr
 			return false;
 		}
 		if (array_key_exists('category', $array)) {
+			$array['category'] = $this->sanitizeUserString($array['category']);
 			if ($this->isCategoryNameTaken($array['category'], $id)) {
 				$this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
 				return false;
 			}
 		}
+		if (array_key_exists('image', $array)) {
+			$array['image'] = $this->sanitizeUserString($array['image']);
+		}
 		if (array_key_exists('default', $array)) {
 			if ($array['default']) {
 				$this->clearCategoryDefault();
@@ -6184,6 +6192,21 @@ class Organizr
 		return false;
 	}
 
+	public function validateEmail($email)
+	{
+		return filter_var(trim($email), FILTER_VALIDATE_EMAIL);
+	}
+
+	public function sanitizeEmail($email)
+	{
+		return filter_var(trim($email), FILTER_SANITIZE_EMAIL);
+	}
+
+	public function sanitizeUserString($string)
+	{
+		return htmlspecialchars(trim($string));
+	}
+
 	public function updateUser($id, $array)
 	{
 		if (!$id) {
@@ -6211,6 +6234,7 @@ class Organizr
 				$this->setAPIResponse('error', 'Username was set but empty', 409);
 				return false;
 			}
+			$array['username'] = $this->sanitizeUserString($array['username']);
 			if ($this->usernameTaken($array['username'], $array['username'], $id)) {
 				$this->setAPIResponse('error', 'Username: ' . $array['username'] . ' is already taken', 409);
 				return false;
@@ -6221,6 +6245,12 @@ class Organizr
 				$this->setAPIResponse('error', 'Email was set but empty', 409);
 				return false;
 			}
+			if ($this->validateEmail($array['email'])) {
+				$array['email'] = $this->sanitizeEmail($array['email']);
+			} else {
+				$this->setResponse(409, 'Email is not a valid email', ['email' => $array['email']]);
+				return false;
+			}
 			if ($this->usernameTaken($array['email'], $array['email'], $id)) {
 				$this->setAPIResponse('error', 'Email: ' . $array['email'] . ' is already taken', 409);
 				return false;
@@ -6260,6 +6290,9 @@ class Organizr
 			}
 			$array['password'] = password_hash($array['password'], PASSWORD_BCRYPT);
 		}
+		if (array_key_exists('image', $array)) {
+			$array['image'] = $this->sanitizeUserString($array['image']);
+		}
 		if (array_key_exists('register_date', $array)) {
 			$this->setAPIResponse('error', 'Cannot update register date', 409);
 			return false;
@@ -6317,10 +6350,30 @@ class Organizr
 			$this->setAPIResponse('error', 'Username was not supplied', 409);
 			return false;
 		}
+		if ($username == '') {
+			$this->setResponse(409, 'Username was set but empty');
+			return false;
+		} else {
+			$username = $this->sanitizeUserString($username);
+		}
 		if (!$password) {
 			$this->setAPIResponse('error', 'Password was not supplied', 409);
 			return false;
 		}
+		if (!$email) {
+			$this->setAPIResponse('error', 'Email was set not supplied', 409);
+			return false;
+		}
+		if ($email == '') {
+			$this->setAPIResponse('error', 'Email was set but empty', 409);
+			return false;
+		}
+		if ($this->validateEmail($email)) {
+			$email = $this->sanitizeEmail($email);
+		} else {
+			$this->setResponse(409, 'Email is not a valid email', ['email' => $email]);
+			return false;
+		}
 		$this->setLoggerChannel('User Management');
 		if ($this->createUser($username, $password, $email)) {
 			$this->logger->info('Account created for [' . $username . ']');
@@ -6340,10 +6393,21 @@ class Organizr
 			$this->setAPIResponse('error', 'Username was set but empty', 409);
 			return false;
 		}
+		$username = $this->sanitizeUserString($username);
 		if (!$password) {
 			$this->setAPIResponse('error', 'Password was set but empty', 409);
 			return false;
 		}
+		if ($email == '') {
+			$this->setAPIResponse('error', 'Email was set but empty', 409);
+			return false;
+		}
+		if ($this->validateEmail($email)) {
+			$email = $this->sanitizeEmail($email);
+		} else {
+			$this->setResponse(409, 'Email is not a valid email', ['email' => $email]);
+			return false;
+		}
 		if ($this->usernameTaken($username, $email)) {
 			$this->setAPIResponse('error', 'Username: ' . $username . ' or Email: ' . $email . ' is already taken', 409);
 			return false;
@@ -6397,6 +6461,7 @@ class Organizr
 				$this->setAPIResponse('error', 'Group was set but empty', 409);
 				return false;
 			}
+			$array['group'] = $this->sanitizeUserString($array['group']);
 			if ($this->isGroupNameTaken($array['group'], $id)) {
 				$this->setAPIResponse('error', 'Group name: ' . $array['group'] . ' is already taken', 409);
 				return false;
@@ -6476,6 +6541,7 @@ class Organizr
 		$array['default'] = ($array['default']) ?? 0;
 		$array['group_id'] = $this->getNextGroupOrder() + 1;
 		if (array_key_exists('group', $array)) {
+			$array['group'] = $this->sanitizeUserString($array['group']);
 			if ($this->isGroupNameTaken($array['group'])) {
 				$this->setAPIResponse('error', 'Group name: ' . $array['group'] . ' is already taken', 409);
 				return false;

programming_language: PHP
filename: log-functions.php
change_type: ModificationType.MODIFY
diff: @@ -212,7 +212,7 @@ trait LogFunctions
 		if ($this->hasDB()) {
 			$setLogger = false;
 			if ($username) {
-				$username = htmlspecialchars($username);
+				$username = $this->sanitizeUserString($username);
 			}
 			if ($this->logger) {
 				if ($channel) {

programming_language: PHP
filename: organizr-functions.php
change_type: ModificationType.MODIFY
diff: @@ -216,7 +216,6 @@ trait OrganizrFunctions
 				case 'png':
 				case 'jpeg':
 				case 'jpg':
-				case 'svg':
 					return true;
 				default:
 					return false;

programming_language: PHP
filename: plugin.php
change_type: ModificationType.MODIFY
diff: @@ -570,6 +570,7 @@ class Bookmark extends Organizr
 		$array['enabled'] = ($array['enabled']) ?? 0;
 		$array['order'] = ($array['order']) ?? $this->_getNextBookmarkTabOrder() + 1;
 		if (array_key_exists('name', $array)) {
+			$array['name'] = $this->sanitizeUserString($array['name']);
 			if ($this->_isBookmarkTabNameTaken($array['name'])) {
 				$this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
 				return false;
@@ -585,8 +586,11 @@ class Bookmark extends Organizr
 		if (!array_key_exists('image', $array)) {
 			$this->setAPIResponse('error', 'Tab image was not supplied', 422);
 			return false;
+		} else {
+			$array['image'] = $this->sanitizeUserString($array['image']);
 		}
 		if (array_key_exists('background_color', $array)) {
+			$array['background_color'] = $this->sanitizeUserString($array['background_color']);
 			if (!$this->_checkColorHexCode($array['background_color'])) {
 				$this->setAPIResponse('error', 'Tab background color is invalid', 422);
 				return false;
@@ -596,6 +600,7 @@ class Bookmark extends Organizr
 			return false;
 		}
 		if (array_key_exists('text_color', $array)) {
+			$array['text_color'] = $this->sanitizeUserString($array['text_color']);
 			if (!$this->_checkColorHexCode($array['text_color'])) {
 				$this->setAPIResponse('error', 'Tab text color is invalid', 422);
 				return false;
@@ -636,23 +641,29 @@ class Bookmark extends Organizr
 			return false;
 		}
 		if (array_key_exists('name', $array)) {
+			$array['name'] = $this->sanitizeUserString($array['name']);
 			if ($this->_isBookmarkTabNameTaken($array['name'], $id)) {
 				$this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
 				return false;
 			}
 		}
 		if (array_key_exists('background_color', $array)) {
+			$array['background_color'] = $this->sanitizeUserString($array['background_color']);
 			if (!$this->_checkColorHexCode($array['background_color'])) {
 				$this->setAPIResponse('error', 'Tab background color is invalid', 422);
 				return false;
 			}
 		}
 		if (array_key_exists('text_color', $array)) {
+			$array['text_color'] = $this->sanitizeUserString($array['text_color']);
 			if (!$this->_checkColorHexCode($array['text_color'])) {
 				$this->setAPIResponse('error', 'Tab text color is invalid', 422);
 				return false;
 			}
 		}
+		if (array_key_exists('image', $array)) {
+			$array['image'] = $this->sanitizeUserString($array['image']);
+		}
 		$response = [
 			array(
 				'function' => 'query',
@@ -871,6 +882,7 @@ class Bookmark extends Organizr
 		$array['order'] = ($array['order']) ?? $this->_getNextBookmarkCategoryOrder() + 1;
 		$array['category_id'] = ($array['category_id']) ?? $this->_getNextBookmarkCategoryId() + 1;
 		if (array_key_exists('category', $array)) {
+			$array['category'] = $this->sanitizeUserString($array['category']);
 			if ($this->_isBookmarkCategoryNameTaken($array['category'])) {
 				$this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
 				return false;
@@ -913,6 +925,7 @@ class Bookmark extends Organizr
 			return false;
 		}
 		if (array_key_exists('category', $array)) {
+			$array['category'] = $this->sanitizeUserString($array['category']);
 			if ($this->_isBookmarkCategoryNameTaken($array['category'], $id)) {
 				$this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
 				return false;

programming_language: PHP