Explorar el Código

Fix directory traversal vulnerability and double URL decode in litefm (#666)

* Fix directory traversal vulnerability and double URL decode in litefm

* refactor: centralize path validation into validate_path() function

- Add validate_path() to includes/helpers.php with 3-layer security:
  block '..' traversal, standalone '.' segments, and non-whitelisted chars
- Replace inline regex checks in litefm.php and blacklist.php
- Add validate_path() guards to all 7 file operations in fm_dir.php
  (delete, rename, move, copy, compress, uncompress, send_by_email)
- Use htmlspecialchars() in error output to prevent reflected XSS
iceeedR hace 2 meses
padre
commit
86cb4275ae
Se han modificado 4 ficheros con 97 adiciones y 7 borrados
  1. 30 0
      includes/helpers.php
  2. 60 0
      modules/litefm/fm_dir.php
  3. 4 4
      modules/litefm/litefm.php
  4. 3 3
      modules/update/blacklist.php

+ 30 - 0
includes/helpers.php

@@ -495,4 +495,34 @@ function getOGPLangConstantsJSON(){
 	
 	return false;
 }
+
+/**
+ * Validates a file path for security before passing to RPC/Shell.
+ *
+ * @param string $path The full path to validate.
+ * @return bool True if safe, False if it contains dangerous characters or traversal.
+ */
+function validate_path($path) {
+    // 1. Block any instance of ".." (Parent Directory Traversal)
+    if (strpos($path, '..') !== false) {
+        return false;
+    }
+
+    // 2. Block "." if it is the entire filename or a standalone path segment
+    // This prevents operations on the "current directory" itself.
+    $parts = explode('/', $path);
+    foreach ($parts as $part) {
+        if ($part === '.') {
+            return false;
+        }
+    }
+
+    // 3. Strict Whitelist: Allow only alphanumeric, underscores, dots (within names), hyphens, and slashes.
+    // This blocks ; | & ` $ \n and other shell/perl injection characters.
+    if (preg_match('/[^a-zA-Z0-9._\-\/]/', $path)) {
+        return false;
+    }
+
+    return true;
+}
 ?>

+ 60 - 0
modules/litefm/fm_dir.php

@@ -209,7 +209,15 @@ function exec_ogp_module()
 			{
 				if(isset($_SESSION['fm_files_'.$home_id][$item]))
 				{
+					if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+						print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+						continue;
+					}
 					$item_path = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+					if(preg_match("/\/\.\.\/|\||;/", $item_path)) {
+						print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+						continue;
+					}
 					$files .= $item_path.";";
 				}
 			}
@@ -231,7 +239,15 @@ function exec_ogp_module()
 			{
 				if(isset($_SESSION['fm_files_'.$home_id][$item]))
 				{
+					if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+						print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+						continue;
+					}
 					$item_path = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+					if(preg_match("/\/\.\.\/|\||;/", $item_path)) {
+						print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+						continue;
+					}
 					$new_item = removeInvalidFileNameCharacters(stripslashes($_POST['values'][$i]));
 					$new_item_path = clean_path( $path . "/" . $new_item );
 					if ($item_path != $new_item_path)
@@ -256,7 +272,15 @@ function exec_ogp_module()
 				{
 					if(isset($_SESSION['fm_files_'.$home_id][$item]))
 					{
+						if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+							print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+							continue;
+						}
 						$item_path = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+						if(preg_match("/\/\.\.\/|\||;/", $item_path)) {
+							print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+							continue;
+						}
 						$destination = clean_path($destination . "/.");
 						$remote->shell_action('move', "$item_path;$destination");
 						$db->logger( get_lang("move") . ": $item_path " . get_lang("to") . " $destination" );
@@ -278,7 +302,15 @@ function exec_ogp_module()
 				{
 					if(isset($_SESSION['fm_files_'.$home_id][$item]))
 					{
+						if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+							print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+							continue;
+						}
 						$item_path = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+						if(preg_match("/\/\.\.\/|\||;/", $item_path)) {
+							print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+							continue;
+						}
 						$destination = clean_path($destination . "/.");
 						$remote->shell_action('copy', "$item_path;$destination");
 						$db->logger( get_lang("copy") . ": $item_path " . get_lang("to") . " $destination" );
@@ -296,7 +328,15 @@ function exec_ogp_module()
 		{
 			if(isset($_SESSION['fm_files_'.$home_id][$item]))
 			{
+				if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+					print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+					continue;
+				}
 				$item_path = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+				if(preg_match("/\/\.\.\/|\||;/", $item_path)) {
+					print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+					continue;
+				}
 				$files_w_path .= $item_path.'<br>';
 				$items .= $_SESSION['fm_files_'.$home_id][$item].'\n';
 			}
@@ -318,7 +358,15 @@ function exec_ogp_module()
 			{
 				if(isset($_SESSION['fm_files_'.$home_id][$item]))
 				{
+					if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+						print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+						continue;
+					}
 					$file_location = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+					if(preg_match("/\/\.\.\/|\||;/", $file_location)) {
+						print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+						continue;
+					}
 					$remote->uncompress_file($file_location, $destination);
 					$db->logger( get_lang("uncompress") . ": $file_location " . to . " $destination." );
 				}
@@ -346,7 +394,15 @@ function exec_ogp_module()
 		{
 			if(isset($_SESSION['fm_files_'.$home_id][$item]))
 			{
+				if(!validate_path($_SESSION['fm_files_'.$home_id][$item])){
+					print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+					continue;
+				}
 				$item_path = clean_path( $path . "/" . $_SESSION['fm_files_'.$home_id][$item] );
+				if(preg_match("/\/\.\.\/|\||;/", $item_path)) {
+					print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$item]));
+					continue;
+				}
 				$items .= $_SESSION['fm_files_'.$home_id][$item].'\n';
 			}
 		}
@@ -385,6 +441,10 @@ function exec_ogp_module()
 	{
 		if(isset($_SESSION['fm_files_'.$home_id][$_POST['item']]))
 		{
+			if(preg_match("/\/\.\.\/|\||;/", $_SESSION['fm_files_'.$home_id][$_POST['item']])) {
+				print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($_SESSION['fm_files_'.$home_id][$_POST['item']]));
+				return;
+			}
 			if($_POST['set_attr'] == '+i' or $_POST['set_attr'] == '-i')
 			{
 				$type = $_POST['set_attr'] == '+i' ? get_lang("chattr_locked") : get_lang("chattr_unlocked");

+ 4 - 4
modules/litefm/litefm.php

@@ -54,7 +54,7 @@ function litefm_check($home_id)
 {
 	if (isset($_GET['item']) and !isset($_GET['upload']) and !isset( $_POST['delete'] ) and !isset( $_POST['create_folder'] ) and !isset( $_POST['secureButton'] ) and !isset( $_POST['delete_check'] ) and !isset( $_POST['secure_check'] ))
     {
-		$fileName = !empty($_POST['name']) ? urldecode($_POST['name']) : urldecode($_GET['name']);
+		$fileName = !empty($_POST['name']) ? $_POST['name'] : (isset($_GET['name']) ? $_GET['name'] : '');
 		if(isset($_GET['type'])){
 			$type = $_GET['type'];
 		}else{
@@ -66,10 +66,10 @@ function litefm_check($home_id)
 			
 		$path = $_SESSION['fm_files_'.$home_id][$_GET['item']];
 		if($path == $fileName){
-			// Make sure nobody tries to get outside thier game server by referencing the .. directory
-			if(preg_match("/\/\.\.\/|\||;/", $path))
+			// Validate the path for dangerous characters and traversal attempts
+			if(!validate_path($path))
 			{
-				print_failure(get_lang("unallowed_char"));
+				print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($path));
 				$_SESSION['fm_cwd_'.$home_id] = NULL;
 				return FALSE;
 			}

+ 3 - 3
modules/update/blacklist.php

@@ -38,10 +38,10 @@ function path_check()
 	if (isset($_GET['path']) and !isset( $_POST['save_to_blacklist'] ))
     {
         $path = $_GET['path'];
-        // Make sure nobody tries to get outside thier game server by referencing the .. directory
-        if(preg_match("/\.\.|\||;/", $path))
+        // Validate the path for dangerous characters and traversal attempts
+        if(!validate_path($path))
         {
-            print_failure(get_lang("unallowed_char"));
+            print_failure(get_lang("unallowed_char") . " : " . htmlspecialchars($path));
             $_SESSION['fm_cwd'] = NULL;
             return FALSE;
         }