CVE-2018-13982: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Smarty_Security::isTrustedResourceDir() in Smarty before 3.1.33 is prone to a path traversal vulnerability due to insufficient template code sanitization. This allows attackers controlling the executed template code to bypass the trusted directory security restriction and read arbitrary files.
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: 7.5
Published: 2018-09-18T21:29Z
Repository: https://github.com/smarty-php/smarty
Language: PHP
Files changed: libs/Smarty.class.php, libs/sysplugins/smarty_security.php
Commit date: 2018-04-26 18:06:45 +0200
Commit message: - bugfix  regarding Security Vulnerability did not solve the problem under Linux

Diff: diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php
index 8ae85206f..bdeffb167 100644
--- a/libs/Smarty.class.php
+++ b/libs/Smarty.class.php
@@ -1040,20 +1040,13 @@ class Smarty extends Smarty_Internal_TemplateBase
      *
      * @return string
      */
     public function _realpath($path, $realpath = null)
     {
-        static $nds = null;
-        static $sepDotSep = null;
-         static $sepSep =null;
-        if (!isset($nds)) {
-            $nds = array('/' => '\\', '\\' => '/');
-            $sepDotSep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
-            $sepSep = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
-        }
+        $nds = array('/' => '\\', '\\' => '/');
         // normalize DIRECTORY_SEPARATOR
-        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], $sepDotSep), DIRECTORY_SEPARATOR, $path);
+        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR, $path);
         preg_match('%^(?<root>(?:[[:alpha:]]:[\\\\]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?<path>(.*))$%u',
                    $path,
                    $parts);
         $path = $parts[ 'path' ];
         if ($parts[ 'root' ] === '\\') {
@@ -1061,28 +1054,15 @@ class Smarty extends Smarty_Internal_TemplateBase
         } else {
             if ($realpath !== null && !$parts[ 'root' ]) {
                 $path = getcwd() . DIRECTORY_SEPARATOR . $path;
             }
         }
-       // remove noop 'DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR' and 'DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR' patterns
-        $path = str_replace(array($sepDotSep,$sepSep), DIRECTORY_SEPARATOR, $path);
-        // resolve '..DIRECTORY_SEPARATOR' pattern, smallest first
-        if (strpos($path, '..' . DIRECTORY_SEPARATOR) !== false &&
-            preg_match_all('#[\\\\/]([.][.][\\\\/])+#u', $path, $match)
-        ) {
-            $counts = array();
-            foreach ($match[ 0 ] as $m) {
-                $counts[] = (int)((strlen($m) - 1) / 3);
-            }
-            sort($counts);
-            foreach ($counts as $count) {
-                $path = preg_replace('#([\\\\/]+[^\\\\/]+){' . $count .
-                                     '}[\\\\/]+([.][.][\\\\/]+){' . $count . '}#u',
-                                     DIRECTORY_SEPARATOR,
-                                     $path);
-            }
-        }
+        do {
+            $path = preg_replace(
+            array('#[\\\\/]{2}#', '#[\\\\/][.][\\\\/]#', '#[\\\\/]([^\\\\/.]+)[\\\\/][.][.][\\\\/]#'),
+            DIRECTORY_SEPARATOR, $path, -1, $count);
+        } while($count > 0);
         return $realpath !== false ? $parts[ 'root' ] . $path : str_ireplace(getcwd(), '.', $parts[ 'root' ] . $path);
     }
 
     /**
      * Empty template objects cache
@@ -1282,17 +1262,17 @@ class Smarty extends Smarty_Internal_TemplateBase
     public function __get($name)
     {
         if (isset($this->accessMap[ $name ])) {
             $method = 'get' . $this->accessMap[ $name ];
             return $this->{$method}();
-        } else if (isset($this->_cache[ $name ])) {
+        } else {if (isset($this->_cache[ $name ])) {
             return $this->_cache[ $name ];
-        } else if (in_array($name, $this->obsoleteProperties)) {
+        } else {if (in_array($name, $this->obsoleteProperties)) {
             return null;
         } else {
             trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE);
-        }
+        }}}
         return null;
     }
 
     /**
      * <<magic>> Generic setter.
@@ -1307,19 +1287,19 @@ class Smarty extends Smarty_Internal_TemplateBase
     public function __set($name, $value)
     {
         if (isset($this->accessMap[ $name ])) {
             $method = 'set' . $this->accessMap[ $name ];
             $this->{$method}($value);
-        } else if (in_array($name, $this->obsoleteProperties)) {
+        } else {if (in_array($name, $this->obsoleteProperties)) {
             return;
         } else {
             if (is_object($value) && method_exists($value, $name)) {
                 $this->$name = $value;
             } else {
                 trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE);
             }
-        }
+        }}
     }
 
     /**
      * Normalize and set directory string
      *
diff --git a/libs/sysplugins/smarty_security.php b/libs/sysplugins/smarty_security.php
index 9c7758f7e..2706fad2b 100644
--- a/libs/sysplugins/smarty_security.php
+++ b/libs/sysplugins/smarty_security.php
@@ -650,11 +650,11 @@ class Smarty_Security
                 throw new SmartyException(sprintf('Smarty Security: not trusted file path \'%s\' ',$filepath));
             }
             // remember the directory to add it to _resource_dir in case we're successful
             $_directory[ $directory ] = true;
            // bubble up one level
-            $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', '/', $directory);
+            $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', DIRECTORY_SEPARATOR, $directory);
         }
     }
 
     /**
      * Loads security class and enables security

Vulnerable code:         static $nds = null;
        static $sepDotSep = null;
         static $sepSep =null;
        if (!isset($nds)) {
            $nds = array('/' => '\\', '\\' => '/');
            $sepDotSep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
            $sepSep = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
        }
        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], $sepDotSep), DIRECTORY_SEPARATOR, $path);
       // remove noop 'DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR' and 'DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR' patterns
        $path = str_replace(array($sepDotSep,$sepSep), DIRECTORY_SEPARATOR, $path);
        // resolve '..DIRECTORY_SEPARATOR' pattern, smallest first
        if (strpos($path, '..' . DIRECTORY_SEPARATOR) !== false &&
            preg_match_all('#[\\\\/]([.][.][\\\\/])+#u', $path, $match)
        ) {
            $counts = array();
            foreach ($match[ 0 ] as $m) {
                $counts[] = (int)((strlen($m) - 1) / 3);
            }
            sort($counts);
            foreach ($counts as $count) {
                $path = preg_replace('#([\\\\/]+[^\\\\/]+){' . $count .
                                     '}[\\\\/]+([.][.][\\\\/]+){' . $count . '}#u',
                                     DIRECTORY_SEPARATOR,
                                     $path);
            }
        }
        } else if (isset($this->_cache[ $name ])) {
        } else if (in_array($name, $this->obsoleteProperties)) {
        }
        } else if (in_array($name, $this->obsoleteProperties)) {
        }
            $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', '/', $directory);
Fixed code:         $nds = array('/' => '\\', '\\' => '/');
        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR, $path);
        do {
            $path = preg_replace(
            array('#[\\\\/]{2}#', '#[\\\\/][.][\\\\/]#', '#[\\\\/]([^\\\\/.]+)[\\\\/][.][.][\\\\/]#'),
            DIRECTORY_SEPARATOR, $path, -1, $count);
        } while($count > 0);
        } else {if (isset($this->_cache[ $name ])) {
        } else {if (in_array($name, $this->obsoleteProperties)) {
        }}}
        } else {if (in_array($name, $this->obsoleteProperties)) {
        }}
            $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', DIRECTORY_SEPARATOR, $directory);