CVE-2021-32682 (published 2021-06-14T17:15Z)

elFinder is an open-source file manager for web, written in JavaScript using jQuery UI. Several vulnerabilities affect elFinder 2.1.58. These vulnerabilities can allow an attacker to execute arbitrary code and commands on the server hosting the elFinder PHP connector, even with minimal configuration. The issues were patched in version 2.1.59. As a workaround, ensure the connector is not exposed without authentication.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
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.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         
cwe_id: CWE-78
cwe_name: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
extended_description: 
		   This weakness can lead to a vulnerability in environments in which the attacker does not have direct access to the operating system, such as in web applications. Alternately, if the weakness occurs in a privileged program, it could allow the attacker to specify commands that normally would not be accessible, or to call alternate commands with privileges that the attacker does not have. The problem is exacerbated if the compromised process does not follow the principle of least privilege, because the attacker-controlled commands may run with special system privileges that increases the amount of damage.
            There are at least two subtypes of OS command injection:
               
				 The application intends to execute a single, fixed program that is under its own control. It intends to use externally-supplied inputs as arguments to that program. For example, the program might use system("nslookup [HOSTNAME]") to run nslookup and allow the user to supply a HOSTNAME, which is used as an argument. Attackers cannot prevent nslookup from executing. However, if the program does not remove command separators from the HOSTNAME argument, attackers could place the separators into the arguments, which allows them to execute their own program after nslookup has finished executing.
				 The application accepts an input that it uses to fully select which program to run, as well as which commands to use. The application simply redirects this entire command to the operating system. For example, the program might use "exec([COMMAND])" to execute the [COMMAND] that was supplied by the user. If the COMMAND is under attacker control, then the attacker can execute arbitrary commands or programs. If the command is being executed using functions like exec() and CreateProcess(), the attacker might not be able to combine multiple commands together in the same line.
               
            From a weakness standpoint, these variants represent distinct programmer errors. In the first variant, the programmer clearly intends that input from untrusted parties will be part of the arguments in the command to be executed. In the second variant, the programmer does not intend for the command to be accessible to any untrusted party, but the programmer probably has not accounted for alternate ways in which malicious attackers can provide input.
         
         
cwe_id: CWE-918
cwe_name: Server-Side Request Forgery (SSRF)
description: The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
extended_description: By providing URLs to unexpected hosts or ports, attackers can make it appear that the server is sending the request, possibly bypassing access controls such as firewalls that prevent the attackers from accessing the URLs directly. The server can be used as a proxy to conduct port scanning of hosts in internal networks, use other URLs such as that can access documents on the system (using file://), or use other protocols such as gopher:// or tftp://, which may provide greater control over the contents of requests.
         

Fix information:
repo_url: https://github.com/Studio-42/elFinder
commit_details: repo_url: https://github.com/Studio-42/elFinder
author: Naoki Sawada
author_date: 2021-06-13 23:38:02+09:00
msg: Merge pull request from GHSA-wph3-44rj-92pr

* [php] fix multiple vulnerabilities

* fix archiver args

* fix remote Code Execution of zip command

* re-fix remote Code Execution of zip command

* re-fix Improper hostname validation in upload and put

* re-fix Directory traversal in the actions mkfile and mkdir

* Add check targets in archive()
repository: repo_url: https://github.com/Studio-42/elFinder
repo_name: Studio-42/elFinder
description: 📁 Open-source file manager for web, written in JavaScript using jQuery and jQuery UI
repo_language: JavaScript
owner: Studio-42
file_changes: filename: elFinder.class.php
change_type: ModificationType.MODIFY
diff: @@ -419,6 +419,21 @@ class elFinder
      */
     protected $removeContentSaveIds = array();
 
+    /**
+     * LAN class allowed when uploading via URL
+     * 
+     * Array keys are 'local', 'private_a', 'private_b', 'private_c' and 'link'
+     * 
+     * local:     127.0.0.0/8
+     * private_a: 10.0.0.0/8
+     * private_b: 172.16.0.0/12
+     * private_c: 192.168.0.0/16
+     * link:      169.254.0.0/16
+     *
+     * @var        array
+     */
+    protected $uploadAllowedLanIpClasses = array();
+
     /**
      * Flag of throw Error on exec()
      *
@@ -713,6 +728,10 @@ class elFinder
             $this->itemLockExpire = intval($opts['itemLockExpire']);
         }
 
+        if (!empty($opts['uploadAllowedLanIpClasses'])) {
+            $this->uploadAllowedLanIpClasses = array_flip($opts['uploadAllowedLanIpClasses']);
+        }
+
         // deprecated settings
         $this->netVolumesSessionKey = !empty($opts['netVolumesSessionKey']) ? $opts['netVolumesSessionKey'] : 'elFinderNetVolumes';
         self::$sessionCacheKey = !empty($opts['sessionCacheKey']) ? $opts['sessionCacheKey'] : 'elFinderCaches';
@@ -2524,16 +2543,87 @@ class elFinder
         }
         $flagFile = elFinder::$connectionFlagsPath . DIRECTORY_SEPARATOR . 'elfreq%s';
         if (!empty($args['makeFile'])) {
-            self::$abortCheckFile = sprintf($flagFile, $args['makeFile']);
+            self::$abortCheckFile = sprintf($flagFile, self::filenameDecontaminate($args['makeFile']));
             touch(self::$abortCheckFile);
             $GLOBALS['elFinderTempFiles'][self::$abortCheckFile] = true;
             return;
         }
 
-        $file = !empty($args['id']) ? sprintf($flagFile, $args['id']) : self::$abortCheckFile;
+        $file = !empty($args['id']) ? sprintf($flagFile, self::filenameDecontaminate($args['id'])) : self::$abortCheckFile;
         $file && is_file($file) && unlink($file);
     }
 
+    /**
+     * Validate an URL to prevent SSRF attacks.
+     *
+     * To prevent any risk of DNS rebinding, always use the IP address resolved by
+     * this method, as returned in the array entry `ip`.
+     *
+     * @param string $url
+     *
+     * @return false|array
+     */
+    protected function validate_address($url)
+    {
+        $info = parse_url($url);
+        $host = trim(strtolower($info['host']), '.');
+        // do not support IPv6 address
+        if (preg_match('/^\[.*\]$/', $host)) {
+            return false;
+        }
+        // do not support non dot host
+        if (strpos($host, '.') === false) {
+            return false;
+        }
+        // do not support URL-encoded host
+        if (strpos($host, '%') !== false) {
+            return false;
+        }
+        // disallow including "localhost" and "localdomain"
+        if (preg_match('/\b(?:localhost|localdomain)\b/', $host)) {
+            return false;
+        }
+        // check IPv4 local loopback, private network and link local
+        $ip = gethostbyname($host);
+        if (preg_match('/^0x[0-9a-f]+|[0-9]+(?:\.(?:0x[0-9a-f]+|[0-9]+)){1,3}$/', $ip, $m)) {
+            $long = (int)sprintf('%u', ip2long($ip));
+            if (!$long) {
+                return false;
+            }
+            $local = (int)sprintf('%u', ip2long('127.255.255.255')) >> 24;
+            $prv1  = (int)sprintf('%u', ip2long('10.255.255.255')) >> 24;
+            $prv2  = (int)sprintf('%u', ip2long('172.31.255.255')) >> 20;
+            $prv3  = (int)sprintf('%u', ip2long('192.168.255.255')) >> 16;
+            $link  = (int)sprintf('%u', ip2long('169.254.255.255')) >> 16;
+
+            if (!isset($this->uploadAllowedLanIpClasses['local']) && $long >> 24 === $local) {
+                return false;
+            }
+            if (!isset($this->uploadAllowedLanIpClasses['private_a']) && $long >> 24 === $prv1) {
+                return false;
+            }
+            if (!isset($this->uploadAllowedLanIpClasses['private_b']) && $long >> 20 === $prv2) {
+                return false;
+            }
+            if (!isset($this->uploadAllowedLanIpClasses['private_c']) && $long >> 16 === $prv3) {
+                return false;
+            }
+            if (!isset($this->uploadAllowedLanIpClasses['link']) && $long >> 16 === $link) {
+                return false;
+            }
+            $info['ip'] = long2ip($long);
+            if (!isset($info['port'])) {
+                $info['port'] = $info['scheme'] === 'https' ? 443 : 80;
+            }
+            if (!isset($info['path'])) {
+                $info['path'] = '/';
+            }
+            return $info;
+        } else {
+            return false;
+        }
+    }
+
     /**
      * Get remote contents
      *
@@ -2552,54 +2642,20 @@ class elFinder
     protected function get_remote_contents(&$url, $timeout = 30, $redirect_max = 5, $ua = 'Mozilla/5.0', $fp = null)
     {
         if (preg_match('~^(?:ht|f)tps?://[-_.!\~*\'()a-z0-9;/?:\@&=+\$,%#\*\[\]]+~i', $url)) {
-            $info = parse_url($url);
-            $host = trim(strtolower($info['host']), '.');
-            // do not support IPv6 address
-            if (preg_match('/^\[.*\]$/', $host)) {
-                return false;
-            }
-            // do not support non dot host
-            if (strpos($host, '.') === false) {
-                return false;
-            }
-            // do not support URL-encoded host
-            if (strpos($host, '%') !== false) {
+            $info = $this->validate_address($url);
+            if ($info === false) {
                 return false;
             }
-            // disallow including "localhost" and "localdomain"
-            if (preg_match('/\b(?:localhost|localdomain)\b/', $host)) {
-                return false;
-            }
-            // wildcard DNS (e.g xip.io)
-            if (preg_match('/0x[0-9a-f]+|[0-9]+(?:\.(?:0x[0-9a-f]+|[0-9]+)){1,3}/', $host)) {
-                $host = gethostbyname($host);
-            }
-            // check IPv4 local loopback, private network and link local
-            if (preg_match('/^0x[0-9a-f]+|[0-9]+(?:\.(?:0x[0-9a-f]+|[0-9]+)){1,3}$/', $host, $m)) {
-                $long = (int)sprintf('%u', ip2long($host));
-                if (!$long) {
-                    return false;
-                }
-                $local = (int)sprintf('%u', ip2long('127.255.255.255')) >> 24;
-                $prv1 = (int)sprintf('%u', ip2long('10.255.255.255')) >> 24;
-                $prv2 = (int)sprintf('%u', ip2long('172.31.255.255')) >> 20;
-                $prv3 = (int)sprintf('%u', ip2long('192.168.255.255')) >> 16;
-                $link = (int)sprintf('%u', ip2long('169.254.255.255')) >> 16;
-
-                if ($long >> 24 === $local || $long >> 24 === $prv1 || $long >> 20 === $prv2 || $long >> 16 === $prv3 || $long >> 16 === $link) {
-                    return false;
-                }
-            }
             // dose not support 'user' and 'pass' for security reasons
-            $url = $info['scheme'].'://'.$host.(!empty($info['port'])? (':'.$info['port']) : '').$info['path'].(!empty($info['query'])? ('?'.$info['query']) : '').(!empty($info['fragment'])? ('#'.$info['fragment']) : '');
+            $url = $info['scheme'].'://'.$info['host'].(!empty($info['port'])? (':'.$info['port']) : '').$info['path'].(!empty($info['query'])? ('?'.$info['query']) : '').(!empty($info['fragment'])? ('#'.$info['fragment']) : '');
             // check by URL upload filter
             if ($this->urlUploadFilter && is_callable($this->urlUploadFilter)) {
                 if (!call_user_func_array($this->urlUploadFilter, array($url, $this))) {
                     return false;
                 }
             }
-            $method = (function_exists('curl_exec') && !ini_get('safe_mode') && !ini_get('open_basedir')) ? 'curl_get_contents' : 'fsock_get_contents';
-            return $this->$method($url, $timeout, $redirect_max, $ua, $fp);
+            $method = (function_exists('curl_exec')) ? 'curl_get_contents' : 'fsock_get_contents';
+            return $this->$method($url, $timeout, $redirect_max, $ua, $fp, $info);
         }
         return false;
     }
@@ -2619,8 +2675,11 @@ class elFinder
      * @retval false  error
      * @author Naoki Sawada
      **/
-    protected function curl_get_contents(&$url, $timeout, $redirect_max, $ua, $outfp)
+    protected function curl_get_contents(&$url, $timeout, $redirect_max, $ua, $outfp, $info)
     {
+        if ($redirect_max == 0) {
+            return false;
+        }
         $ch = curl_init();
         curl_setopt($ch, CURLOPT_URL, $url);
         curl_setopt($ch, CURLOPT_HEADER, false);
@@ -2633,11 +2692,19 @@ class elFinder
         curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 1);
         curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, $timeout);
         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
-        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
-        curl_setopt($ch, CURLOPT_MAXREDIRS, $redirect_max);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
         curl_setopt($ch, CURLOPT_USERAGENT, $ua);
+        curl_setopt($ch, CURLOPT_RESOLVE, [$info['host'] . ':' . $info['port'] . ':' . $info['ip']]);
         $result = curl_exec($ch);
-        $url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
+        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        if ($http_code == 301 || $http_code == 302) {
+            $new_url = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
+            $info = $this->validate_address($new_url);
+            if ($info === false) {
+                return false;
+            }
+            return $this->curl_get_contents($new_url, $timeout, $redirect_max - 1, $ua, $outfp, $info);
+        }
         curl_close($ch);
         return $outfp ? $outfp : $result;
     }
@@ -2658,7 +2725,7 @@ class elFinder
      * @throws elFinderAbortException
      * @author Naoki Sawada
      */
-    protected function fsock_get_contents(&$url, $timeout, $redirect_max, $ua, $outfp)
+    protected function fsock_get_contents(&$url, $timeout, $redirect_max, $ua, $outfp, $info)
     {
         $connect_timeout = 3;
         $connect_try = 3;
@@ -2669,22 +2736,15 @@ class elFinder
         $getSize = null;
         $headers = '';
 
-        $arr = parse_url($url);
-        if (!$arr) {
-            // Bad request
-            return false;
-        }
+        $arr = $info;
         if ($arr['scheme'] === 'https') {
             $ssl = 'ssl://';
         }
 
         // query
         $arr['query'] = isset($arr['query']) ? '?' . $arr['query'] : '';
-        // port
-        $port = isset($arr['port']) ? $arr['port'] : '';
-        $arr['port'] = $port ? $port : ($ssl ? 443 : 80);
 
-        $url_base = $arr['scheme'] . '://' . $arr['host'] . ($port ? (':' . $port) : '');
+        $url_base = $arr['scheme'] . '://' . $info['host'] . ':' . $info['port'];
         $url_path = isset($arr['path']) ? $arr['path'] : '/';
         $uri = $url_path . $arr['query'];
 
@@ -2765,7 +2825,11 @@ class elFinder
                         sleep(1);
                     }
                     fclose($fp);
-                    return $this->fsock_get_contents($url, $timeout, $redirect_max, $ua, $outfp);
+                    $info = $this->validate_address($url);
+                    if ($info === false) {
+                        return false;
+                    }
+                    return $this->fsock_get_contents($url, $timeout, $redirect_max, $ua, $outfp, $info);
                 }
                 break;
             case 200:
@@ -3831,7 +3895,8 @@ class elFinder
         $targets = isset($args['targets']) && is_array($args['targets']) ? $args['targets'] : array();
         $name = isset($args['name']) ? $args['name'] : '';
 
-        if (($volume = $this->volume($targets[0])) == false) {
+        $targets = array_filter($targets, array($this, 'volume'));
+        if (!$targets || ($volume = $this->volume($targets[0])) === false) {
             return $this->error(self::ERROR_ARCHIVE, self::ERROR_TRGDIR_NOT_FOUND);
         }
 
@@ -4339,7 +4404,7 @@ var go = function() {
         if (!elFinder::$commonTempPath) {
             return false;
         }
-        $lock = elFinder::$commonTempPath . DIRECTORY_SEPARATOR . $hash . '.lock';
+        $lock = elFinder::$commonTempPath . DIRECTORY_SEPARATOR . self::filenameDecontaminate($hash) . '.lock';
         if (file_exists($lock)) {
             if (filemtime($lock) + $this->itemLockExpire < time()) {
                 unlink($lock);
@@ -4368,7 +4433,7 @@ var go = function() {
             $hashes = array($hashes);
         }
         foreach ($hashes as $hash) {
-            $lock = elFinder::$commonTempPath . DIRECTORY_SEPARATOR . $hash . '.lock';
+            $lock = elFinder::$commonTempPath . DIRECTORY_SEPARATOR . self::filenameDecontaminate($hash) . '.lock';
             if ($this->itemLocked($hash)) {
                 $cnt = file_get_contents($lock) + 1;
             } else {
@@ -4519,6 +4584,16 @@ var go = function() {
         return (string)self::$ApiVersion . '.' . (string)self::$ApiRevision;
     }
 
+    /**
+     * Return self::$commonTempPath
+     *
+     * @return     string  The common temporary path.
+     */
+    public static function getCommonTempPath()
+    {
+        return self::$commonTempPath;
+    }
+
     /**
      * Return Is Animation Gif
      *
@@ -5104,6 +5179,24 @@ var go = function() {
         }
     }
 
+    /**
+     * Decontaminate of filename
+     *
+     * @param      String  $name   The name
+     *
+     * @return     String  Decontaminated filename
+     */
+    public static function filenameDecontaminate($name)
+    {
+        // Directory traversal defense
+        if (DIRECTORY_SEPARATOR === '\\') {
+            $name = str_replace('\\', '/', $name);
+        }
+        $parts = explode('/', trim($name, '/'));
+        $name = array_pop($parts); 
+        return $name;
+    }
+
     /**
      * Execute shell command
      *
@@ -5279,4 +5372,4 @@ class elFinderAbortException extends Exception
 
 class elFinderTriggerException extends Exception
 {
-}
\ No newline at end of file
+}

programming_language: PHP
filename: elFinderVolumeDriver.class.php
change_type: ModificationType.MODIFY
diff: @@ -5451,7 +5451,7 @@ abstract class elFinderVolumeDriver
         $stat = $this->stat($path);
 
         if (empty($stat)) {
-            return $this->setError(elFinder::ERROR_RM, $path, elFinder::ERROR_FILE_NOT_FOUND);
+            return $this->setError(elFinder::ERROR_RM, $this->relpathCE($path), elFinder::ERROR_FILE_NOT_FOUND);
         }
 
         $stat['realpath'] = $path;
@@ -6727,7 +6727,7 @@ abstract class elFinderVolumeDriver
             unset($o);
             $this->procExec(ELFINDER_RAR_PATH, $o, $c);
             if ($c == 0 || $c == 7) {
-                $arcs['create']['application/x-rar'] = array('cmd' => ELFINDER_RAR_PATH, 'argc' => 'a -inul' . (defined('ELFINDER_RAR_MA4') && ELFINDER_RAR_MA4? ' -ma4' : ''), 'ext' => 'rar');
+                $arcs['create']['application/x-rar'] = array('cmd' => ELFINDER_RAR_PATH, 'argc' => 'a -inul' . (defined('ELFINDER_RAR_MA4') && ELFINDER_RAR_MA4? ' -ma4' : '') . ' --', 'ext' => 'rar');
             }
             unset($o);
             $this->procExec(ELFINDER_UNRAR_PATH, $o, $c);
@@ -6737,17 +6737,17 @@ abstract class elFinderVolumeDriver
             unset($o);
             $this->procExec(ELFINDER_7Z_PATH, $o, $c);
             if ($c == 0) {
-                $arcs['create']['application/x-7z-compressed'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'a', 'ext' => '7z');
+                $arcs['create']['application/x-7z-compressed'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'a --', 'ext' => '7z');
                 $arcs['extract']['application/x-7z-compressed'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'x -y', 'ext' => '7z', 'toSpec' => '-o', 'getsize' => array('argc' => 'l', 'regex' => '/^.+(?:\r\n|\n|\r)[^\r\n0-9]+([0-9]+)[^\r\n]+$/s', 'replace' => '$1'));
 
                 if (empty($arcs['create']['application/zip'])) {
-                    $arcs['create']['application/zip'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'a -tzip', 'ext' => 'zip');
+                    $arcs['create']['application/zip'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'a -tzip --', 'ext' => 'zip');
                 }
                 if (empty($arcs['extract']['application/zip'])) {
                     $arcs['extract']['application/zip'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'x -tzip -y', 'ext' => 'zip', 'toSpec' => '-o', 'getsize' => array('argc' => 'l', 'regex' => '/^.+(?:\r\n|\n|\r)[^\r\n0-9]+([0-9]+)[^\r\n]+$/s', 'replace' => '$1'));
                 }
                 if (empty($arcs['create']['application/x-tar'])) {
-                    $arcs['create']['application/x-tar'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'a -ttar', 'ext' => 'tar');
+                    $arcs['create']['application/x-tar'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'a -ttar --', 'ext' => 'tar');
                 }
                 if (empty($arcs['extract']['application/x-tar'])) {
                     $arcs['extract']['application/x-tar'] = array('cmd' => ELFINDER_7Z_PATH, 'argc' => 'x -ttar -y', 'ext' => 'tar', 'toSpec' => '-o', 'getsize' => array('argc' => 'l', 'regex' => '/^.+(?:\r\n|\n|\r)[^\r\n0-9]+([0-9]+)[^\r\n]+$/s', 'replace' => '$1'));
@@ -6872,8 +6872,14 @@ abstract class elFinderVolumeDriver
                     $files[$i] = '.' . DIRECTORY_SEPARATOR . basename($file);
                 }
                 $files = array_map('escapeshellarg', $files);
-
-                $cmd = $arc['cmd'] . ' ' . $arc['argc'] . ' ' . escapeshellarg($name) . ' ' . implode(' ', $files);
+                $prefix = $switch = '';
+                // The zip command accepts the "-" at the beginning of the file name as a command switch,
+                // and can't use '--' before archive name, so add "./" to name for security reasons.
+                if ($arc['ext'] === 'zip' && strpos($arc['argc'], '-tzip') === false) {
+                    $prefix = './';
+                    $switch = '-- ';
+                }
+                $cmd = $arc['cmd'] . ' ' . $arc['argc'] . ' ' . $prefix . escapeshellarg($name) . ' ' . $switch . implode(' ', $files);
                 $err_out = '';
                 $this->procExec($cmd, $o, $c, $err_out, $dir);
                 chdir($cwd);

programming_language: PHP
filename: elFinderVolumeLocalFileSystem.class.php
change_type: ModificationType.MODIFY
diff: @@ -75,7 +75,6 @@ class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
         $this->options['alias'] = '';              // alias to replace root dir name
         $this->options['dirMode'] = 0755;            // new dirs mode
         $this->options['fileMode'] = 0644;            // new files mode
-        $this->options['quarantine'] = '.quarantine'; // quarantine folder name - required to check archive (must be hidden)
         $this->options['rootCssClass'] = 'elfinder-navbar-root-local';
         $this->options['followSymLinks'] = true;
         $this->options['detectDirIcon'] = '';         // file name that is detected as a folder icon e.g. '.diricon.png'
@@ -172,14 +171,20 @@ class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
             }
         }
         // check quarantine path
+        $_quarantine = '';
         if (!empty($this->options['quarantine'])) {
             if (strpos($this->options['quarantine'], DIRECTORY_SEPARATOR) === false) {
-                $hiddens['quarantine'] = $this->options['quarantine'];
-                $this->options['quarantine'] = $this->_abspath($this->options['quarantine']);
+                //$hiddens['quarantine'] = $this->options['quarantine'];
+                //$this->options['quarantine'] = $this->_abspath($this->options['quarantine']);
+                $_quarantine = $this->_abspath($this->options['quarantine']);
+                $this->options['quarantine'] = '';
             } else {
                 $this->options['quarantine'] = $this->_normpath($this->options['quarantine']);
             }
+        } else {
+            $_quarantine = $this->_abspath('.quarantine');
         }
+        is_dir($_quarantine) && self::localRmdirRecursive($_quarantine);
 
         parent::configure();
 
@@ -223,6 +228,8 @@ class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
                     unset($hiddens['quarantine']);
                 }
             }
+        } else if ($_path = elFinder::getCommonTempPath()) {
+            $this->quarantine = $_path;
         }
 
         if (!$this->quarantine) {
@@ -346,7 +353,20 @@ class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
      **/
     protected function _joinPath($dir, $name)
     {
-        return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $name;
+        $dir = rtrim($dir, DIRECTORY_SEPARATOR);
+        $path = realpath($dir . DIRECTORY_SEPARATOR . $name);
+        // realpath() returns FALSE if the file does not exist
+        if ($path === false || strpos($path, $this->root) !== 0) {
+            if (DIRECTORY_SEPARATOR !== '/') {
+                $name = str_replace('/', DIRECTORY_SEPARATOR, $name);
+            }
+            // Directory traversal measures
+            if (strpos($name, '..' . DIRECTORY_SEPARATOR) !== false) {
+                $name = basename($name);
+            }
+            $path = $dir . DIRECTORY_SEPARATOR . $name;
+        }
+        return $path; 
     }
 
     /**
@@ -1439,4 +1459,3 @@ class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
         return $res;
     }
 } // END class 
-

programming_language: PHP
filename: elFinderVolumeSFTPphpseclib.class.php
change_type: ModificationType.MODIFY
diff: @@ -581,7 +581,7 @@ class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {
      **/
     protected function _mkdir($path, $name)
     {
-        $path = $this->_joinPath($path, $name);
+        $path = $this->_joinPath($path, $this->_basename($name));
         if ($this->connect->mkdir($path) === false) {
             return false;
         }
@@ -601,7 +601,7 @@ class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {
      **/
     protected function _mkfile($path, $name)
     {
-        $path = $this->_joinPath($path, $name);
+        $path = $this->_joinPath($path, $this->_basename($name));
         return $this->connect->put($path, '') ? $path : false;
 /*
         if ($this->tmp) {
@@ -630,7 +630,7 @@ class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {
     {
         $res = false;
 
-        $target = $this->_joinPath($targetDir, $name);
+        $target = $this->_joinPath($targetDir, $this->_basename($name));
         if ($this->tmp) {
             $local = $this->getTempFile();
 
@@ -661,7 +661,7 @@ class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {
      */
     protected function _move($source, $targetDir, $name)
     {
-        $target = $this->_joinPath($targetDir, $name);
+        $target = $this->_joinPath($targetDir, $this->_basename($name));
         return $this->connect->rename($source, $target) ? $target : false;
     }
 
@@ -706,7 +706,7 @@ class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {
     protected function _save($fp, $dir, $name, $stat)
     {
         //TODO optionally encrypt $fp before uploading if mime is not already encrypted type
-        $path = $this->_joinPath($dir, $name);
+        $path = $this->_joinPath($dir, $this->_basename($name));
         return $this->connect->put($path, $fp)
             ? $path
             : false;

programming_language: PHP