CVE-2021-44664: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: An Authenticated Remote Code Exection (RCE) vulnerability exists in Xerte through 3.9 in website_code/php/import/fileupload.php by uploading a maliciously crafted PHP file though the project interface disguised as a language file to bypasses the upload filters. Attackers can manipulate the files destination by abusing path traversal in the 'mediapath' variable.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 8.8
Published: 2022-02-24T21:15Z
Repository: https://github.com/thexerteproject/xerteonlinetoolkits
Language: PHP
Files changed: editor/elfinder/php/connector.php, editor/uploadImage.php, modules/decision/engine/upload.php, modules/site/engine/upload.php, website_code/php/import/fileupload.php
Commit date: 2021-05-07 16:59:18 +0200
Commit message: Prohibit path traversal on upload

 - Thank you Rik Lutz

Diff: diff --git a/editor/elfinder/php/connector.php b/editor/elfinder/php/connector.php
index 10f1cbb4c..986a67eb9 100644
--- a/editor/elfinder/php/connector.php
+++ b/editor/elfinder/php/connector.php
@@ -37,11 +37,11 @@ if (!isset($_SESSION['toolkits_logon_id'])){
 }
 // Get session data to set paths
 $rootpath = $_REQUEST['uploadDir'];
 $rooturl = $_REQUEST['uploadURL'];
 
-if (strpos($rootpath, 'USER-FILES') === false || strpos($rooturl, 'USER-FILES') === false)
+if (strpos($rootpath, 'USER-FILES') === false  || strpos($rootpath, '../') !== false || strpos($rooturl, 'USER-FILES') === false)
 {
     die("Invalid upload location");
 }
 
 include_once dirname(__FILE__).DIRECTORY_SEPARATOR.'elFinderConnector.class.php';
diff --git a/editor/uploadImage.php b/editor/uploadImage.php
index 2893e12e5..27db52493 100644
--- a/editor/uploadImage.php
+++ b/editor/uploadImage.php
@@ -70,11 +70,11 @@ if (!isset($_FILES['upload']))
     echo json_encode($response);
     exit(-1);
 }
 
 // Check upload path, should contain USER-FILES
-if (strpos($_REQUEST['uploadURL'], 'USER-FILES') === false)
+if (strpos($_REQUEST['uploadPath'], 'USER-FILES') === false || strpos($_REQUEST['uploadPath'], '../') !== false || strpos($_REQUEST['uploadURL'], 'USER-FILES') === false)
 {
     // Invalid folder, reject!
     $response->uploaded = 0;
     $response->error = IMAGEUPOLOAD_NOT_UPLOADED;
 
diff --git a/modules/decision/engine/upload.php b/modules/decision/engine/upload.php
index e8922bf5c..96840ea2e 100644
--- a/modules/decision/engine/upload.php
+++ b/modules/decision/engine/upload.php
@@ -94,11 +94,11 @@ else {
 $pass = true;
 if (strpos($_FILES['Filedata']['name'], '../') !== false)
     $pass = false;
 if (strpos($_FILES['Filedata']['name'], '...') !== false)
     $pass = false;
-if (strpos($_GET['path'], 'USER-FILES') === false)
+if (strpos($_GET['path'], 'USER-FILES') === false || strpos($_GET['path'], '../') !== false)
     $pass = false;
 
 if ($pass === false) {
     receive_message($_SESSION['toolkits_logon_username'], "UPLOAD", "CRITICAL", "Invalid filename: " . $_FILES['Filedata']['name'], "Invalid filename: " . $_FILES['Filedata']['name']);
     exit();
diff --git a/modules/site/engine/upload.php b/modules/site/engine/upload.php
index 6f266e985..239211c42 100644
--- a/modules/site/engine/upload.php
+++ b/modules/site/engine/upload.php
@@ -77,11 +77,11 @@ if (empty($_FILES)) {
 $pass = true;
 if (strpos($_FILES['Filedata']['name'], '../') !== false)
     $pass = false;
 if (strpos($_FILES['Filedata']['name'], '...') !== false)
     $pass = false;
-if (strpos($_GET['path'], 'USER-FILES') === false)
+if (strpos($_GET['path'], 'USER-FILES') === false || strpos($_GET['path'], '../') !== false)
     $pass = false;
 
 if ($pass === false) {
     receive_message($_SESSION['toolkits_logon_username'], "UPLOAD", "CRITICAL", "Invalid filename: " . $_FILES['Filedata']['name'], "Invalid filename: " . $_FILES['Filedata']['name']);
     exit();
diff --git a/website_code/php/import/fileupload.php b/website_code/php/import/fileupload.php
index 8d52d1650..c812050c4 100644
--- a/website_code/php/import/fileupload.php
+++ b/website_code/php/import/fileupload.php
@@ -53,11 +53,11 @@ function convertToBytes(string $from): ?int {
 if (!isset($_SESSION['toolkits_logon_username']))
 {
     _debug("Session is invalid or expired");
     die("Session is invalid or expired");
 }
-if (strpos($_POST['mediapath'], 'USER-FILES') === false)
+if (strpos($_POST['mediapath'], 'USER-FILES') === false || strpos($_POST['mediapath'], '../') !== false)
 {
     // Invalid upload path
     _debug("Invalid or illegal mediapath");
     die("Invalid or illegal mediapath");
 }

Vulnerable code: if (strpos($rootpath, 'USER-FILES') === false || strpos($rooturl, 'USER-FILES') === false)
if (strpos($_REQUEST['uploadURL'], 'USER-FILES') === false)
if (strpos($_GET['path'], 'USER-FILES') === false)
if (strpos($_GET['path'], 'USER-FILES') === false)
if (strpos($_POST['mediapath'], 'USER-FILES') === false)
Fixed code: if (strpos($rootpath, 'USER-FILES') === false  || strpos($rootpath, '../') !== false || strpos($rooturl, 'USER-FILES') === false)
if (strpos($_REQUEST['uploadPath'], 'USER-FILES') === false || strpos($_REQUEST['uploadPath'], '../') !== false || strpos($_REQUEST['uploadURL'], 'USER-FILES') === false)
if (strpos($_GET['path'], 'USER-FILES') === false || strpos($_GET['path'], '../') !== false)
if (strpos($_GET['path'], 'USER-FILES') === false || strpos($_GET['path'], '../') !== false)
if (strpos($_POST['mediapath'], 'USER-FILES') === false || strpos($_POST['mediapath'], '../') !== false)