CVE-2022-26960: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: connector.minimal.php in std42 elFinder through 2.1.60 is affected by path traversal. This allows unauthenticated remote attackers to read, write, and browse files outside the configured document root. This is due to improper handling of absolute file paths.
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: 9.1
Published: 2022-03-21T17:15Z
Repository: https://github.com/Studio-42/elFinder
Language: PHP
Files changed: php/elFinderVolumeDriver.class.php, php/elFinderVolumeLocalFileSystem.class.php
Commit date: 2022-03-14 20:59:19 +0900
Commit message: [security:CVE-2022-26960] fix a path traversal issue

Fixed a paste traversal vulnerability. The problem was getting out of
the configured directory and allowing the hosting server's file system
to read and write "arbitrary" files.

Special thanks to Gaetan Ferry (Synacktiv) for reporting this issue.

Diff: diff --git a/php/elFinderVolumeDriver.class.php b/php/elFinderVolumeDriver.class.php
index 898f6ad8f..e11d3ffca 100644
--- a/php/elFinderVolumeDriver.class.php
+++ b/php/elFinderVolumeDriver.class.php
@@ -6792,27 +6792,38 @@ abstract class elFinderVolumeDriver
         }
         if ($base !== $systemroot) {
             $base = rtrim($base, $separator);
         }
 
-        // 'Here'
-        if ($path === '' || $path === '.' . $separator) return $base;
-
         $sepquoted = preg_quote($separator, '#');
 
+        // normalize `//` to `/`
+        $path = preg_replace('#' . $sepquoted . '+#', $separator, $path); // '#/+#'
+
+        // remove `./`
+        $path = preg_replace('#(?<=^|' . $sepquoted . ')\.' . $sepquoted . '#', '', $path); // '#(?<=^|/)\./#'
+
+        // 'Here'
+        if ($path === '') return $base;
+
+        // join $base to $path if $path start `../`
         if (substr($path, 0, 3) === '..' . $separator) {
             $path = $base . $separator . $path;
         }
+
         // normalize `/../`
         $normreg = '#(' . $sepquoted . ')[^' . $sepquoted . ']+' . $sepquoted . '\.\.' . $sepquoted . '#'; // '#(/)[^\/]+/\.\./#'
         while (preg_match($normreg, $path)) {
             $path = preg_replace($normreg, '$1', $path, 1);
         }
         if ($path !== $systemroot) {
             $path = rtrim($path, $separator);
         }
 
+        // discard the surplus `../`
+        $path = str_replace('..' . $separator, '', $path);
+
         // Absolute path
         if ($path[0] === $separator || strpos($path, $systemroot) === 0) {
             return $path;
         }
 
diff --git a/php/elFinderVolumeLocalFileSystem.class.php b/php/elFinderVolumeLocalFileSystem.class.php
index 12db2dc4d..ce2186b99 100644
--- a/php/elFinderVolumeLocalFileSystem.class.php
+++ b/php/elFinderVolumeLocalFileSystem.class.php
@@ -483,10 +483,11 @@ class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
     protected function _abspath($path)
     {
         if ($path === DIRECTORY_SEPARATOR) {
             return $this->root;
         } else {
+            $path = $this->_normpath($path);
             if (strpos($path, $this->systemRoot) === 0) {
                 return $path;
             } else if (DIRECTORY_SEPARATOR !== '/' && preg_match('/^[a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . '/', $path)) {
                 return $path;
             } else {

Vulnerable code:         // 'Here'
        if ($path === '' || $path === '.' . $separator) return $base;

Fixed code:         // normalize `//` to `/`
        $path = preg_replace('#' . $sepquoted . '+#', $separator, $path); // '#/+#'

        // remove `./`
        $path = preg_replace('#(?<=^|' . $sepquoted . ')\.' . $sepquoted . '#', '', $path); // '#(?<=^|/)\./#'

        // 'Here'
        if ($path === '') return $base;

        // join $base to $path if $path start `../`

        // discard the surplus `../`
        $path = str_replace('..' . $separator, '', $path);

            $path = $this->_normpath($path);