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: change_log.txt, libs/Smarty.class.php, libs/sysplugins/smarty_security.php
Commit date: 2018-04-26 13:02:51 +0200
Commit message: - bugfix  regarding Security Vulnerability did not solve the problem under Linux

Diff: diff --git a/change_log.txt b/change_log.txt
index 5765a171..ffd6368f 100644
--- a/change_log.txt
+++ b/change_log.txt
@@ -1,6 +1,10 @@
-﻿===== 3.1.32 ===== (24.04.2018)
+﻿===== 3.1.33-dev-1 =====
+26.04.2018
+ - bugfix  regarding Security Vulnerability did not solve the problem under Linux.
+
+===== 3.1.32 ===== (24.04.2018)
 24.04.2018
  - bugfix  possible Security Vulnerability in Smarty_Security class.
 
 26.03.2018
  - bugfix plugins may not be loaded if {function} or {block} tags are executed in nocache mode
diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php
index 597fbbf0..8ae85206 100644
--- a/libs/Smarty.class.php
+++ b/libs/Smarty.class.php
@@ -25,11 +25,11 @@
  * @copyright 2018 Uwe Tews
  * @author    Monte Ohrt <monte at ohrt dot com>
  * @author    Uwe Tews   <uwe dot tews at gmail dot com>
  * @author    Rodney Rehm
  * @package   Smarty
- * @version   3.1.32
+ * @version   3.1.33-dev
  */
 /**
  * set SMARTY_DIR to absolute path to Smarty library files.
  * Sets SMARTY_DIR only if user application has not already defined it.
  */
@@ -110,11 +110,11 @@ require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_resource_file.php';
 class Smarty extends Smarty_Internal_TemplateBase
 {
     /**
      * smarty version
      */
-    const SMARTY_VERSION = '3.1.32';
+    const SMARTY_VERSION = '3.1.33-dev-1';
     /**
      * define variable scopes
      */
     const SCOPE_LOCAL    = 1;
     const SCOPE_PARENT   = 2;
@@ -833,11 +833,11 @@ class Smarty extends Smarty_Internal_TemplateBase
         if (!$this->_pluginsDirNormalized) {
             if (!is_array($this->plugins_dir)) {
                 $this->plugins_dir = (array)$this->plugins_dir;
             }
             foreach ($this->plugins_dir as $k => $v) {
-                $this->plugins_dir[ $k ] = $this->_realpath(rtrim($v, "/\\") . DIRECTORY_SEPARATOR, true);
+                $this->plugins_dir[ $k ] = $this->_realpath(rtrim($v, '/\\') . DIRECTORY_SEPARATOR, true);
             }
             $this->_cache[ 'plugin_files' ] = array();
             $this->_pluginsDirNormalized = true;
         }
         return $this->plugins_dir;
@@ -1041,24 +1041,19 @@ class Smarty extends Smarty_Internal_TemplateBase
      * @return string
      */
     public function _realpath($path, $realpath = null)
     {
         static $nds = null;
-        static $sepDotsep = null;
-        static $sepDot = null;
-        static $sepSep =null;
+        static $sepDotSep = null;
+         static $sepSep =null;
         if (!isset($nds)) {
             $nds = array('/' => '\\', '\\' => '/');
-            $sepDotsep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
-            $sepDot = DIRECTORY_SEPARATOR . '.';
+            $sepDotSep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
             $sepSep = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
         }
         // normalize DIRECTORY_SEPARATOR
-        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], $sepDotsep), DIRECTORY_SEPARATOR, $path);
-        if (strpos($path,$sepDot) === false && (($realpath === false && $path[0] === '.') || $realpath === null) && $path[0] !== '\\') {
-            return $path;
-        }
+        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], $sepDotSep), DIRECTORY_SEPARATOR, $path);
         preg_match('%^(?<root>(?:[[:alpha:]]:[\\\\]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?<path>(.*))$%u',
                    $path,
                    $parts);
         $path = $parts[ 'path' ];
         if ($parts[ 'root' ] === '\\') {
@@ -1067,11 +1062,11 @@ class Smarty extends Smarty_Internal_TemplateBase
             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);
+        $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();
diff --git a/libs/sysplugins/smarty_security.php b/libs/sysplugins/smarty_security.php
index 126f6fb4..9c7758f7 100644
--- a/libs/sysplugins/smarty_security.php
+++ b/libs/sysplugins/smarty_security.php
@@ -256,12 +256,10 @@ class Smarty_Security
      * @param Smarty $smarty
      */
     public function __construct($smarty)
     {
         $this->smarty = $smarty;
-        $this->smarty->_cache[ 'template_dir_new' ] = true;
-        $this->smarty->_cache[ 'config_dir_new' ] = true;
     }
 
     /**
      * Check if PHP function is trusted.
      *
@@ -519,28 +517,27 @@ class Smarty_Security
                 $this->_updateResourceDir($this->_include_dir, $_dir);
                 $this->_include_dir = $_dir;
             }
             $this->_include_path_status = $this->smarty->use_include_path;
         }
-        if ($isConfig !== true) {
+
             $_dir = $this->smarty->getTemplateDir();
             if ($this->_template_dir !== $_dir) {
                 $this->_updateResourceDir($this->_template_dir, $_dir);
                 $this->_template_dir = $_dir;
             }
-        }
-        if ($isConfig !== false) {
+
             $_dir = $this->smarty->getConfigDir();
             if ($this->_config_dir !== $_dir) {
                 $this->_updateResourceDir($this->_config_dir, $_dir);
                 $this->_config_dir = $_dir;
             }
-        }
+
         if ($this->_secure_dir !== $this->secure_dir) {
             $this->secure_dir = (array)$this->secure_dir;
             foreach($this->secure_dir as $k => $d) {
-                $this->secure_dir[$k] = $this->smarty->_realpath($d.DIRECTORY_SEPARATOR,true);
+                $this->secure_dir[$k] = $this->smarty->_realpath($d. DIRECTORY_SEPARATOR,true);
             }
             $this->_updateResourceDir($this->_secure_dir, $this->secure_dir);
             $this->_secure_dir = $this->secure_dir;
         }
         $addPath =  $this->_checkDir($filepath, $this->_resource_dir);
@@ -595,11 +592,11 @@ class Smarty_Security
         if (!$this->_trusted_dir || $this->_trusted_dir !== $this->trusted_dir) {
             $this->_php_resource_dir = array();
 
             $this->_trusted_dir = $this->trusted_dir;
             foreach ((array) $this->trusted_dir as $directory) {
-                $directory = $this->smarty->_realpath($directory . DIRECTORY_SEPARATOR, true);
+                $directory = $this->smarty->_realpath($directory . '/', true);
                 $this->_php_resource_dir[ $directory ] = true;
             }
         }
         $addPath =  $this->_checkDir($filepath, $this->_php_resource_dir);
         if ($addPath !== false) {
@@ -614,20 +611,20 @@ class Smarty_Security
      * @param array $oldDir
      * @param array $newDir
      */
     private function _updateResourceDir($oldDir, $newDir) {
         foreach ($oldDir as $directory) {
-            $directory = $this->smarty->_realpath($directory, true);
+ //           $directory = $this->smarty->_realpath($directory, true);
             $length = strlen($directory);
             foreach ($this->_resource_dir as $dir) {
                 if (substr($dir, 0,$length) === $directory) {
                     unset($this->_resource_dir[ $dir ]);
                 }
             }
         }
         foreach ($newDir as $directory) {
-            $directory = $this->smarty->_realpath($directory, true);
+ //           $directory = $this->smarty->_realpath($directory, true);
             $this->_resource_dir[ $directory ] = true;
         }
     }
     /**
      * Check if file is inside a valid directory
@@ -638,34 +635,27 @@ class Smarty_Security
      * @return array|bool
      * @throws \SmartyException
      */
     private function _checkDir($filepath, $dirs)
     {
-        $directory = dirname($filepath) . DIRECTORY_SEPARATOR;
-        if (isset($dirs[ $directory ])) {
-            return false;
-        }
-        $filepath = $this->smarty->_realpath($filepath, true);
-        $directory = dirname($filepath) . DIRECTORY_SEPARATOR;
+        $directory = dirname($this->smarty->_realpath($filepath, true)) . DIRECTORY_SEPARATOR;
         $_directory = array();
         while (true) {
              // test if the directory is trusted
             if (isset($dirs[ $directory ])) {
                return $_directory;
             }
             // abort if we've reached root
-            if (!preg_match('#[\\\/][^\\\/]+[\\\/]$#', $directory)) {
-                break;
+            if (!preg_match('#[\\\\/][^\\\\/]+[\\\\/]$#', $directory)) {
+                // give up
+                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_SEPARATOR, $directory);
+            $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', '/', $directory);
         }
-
-        // give up
-        throw new SmartyException("directory '{$filepath}' not allowed by security setting");
     }
 
     /**
      * Loads security class and enables security
      *

Vulnerable code: ﻿===== 3.1.32 ===== (24.04.2018)
 * @version   3.1.32
    const SMARTY_VERSION = '3.1.32';
                $this->plugins_dir[ $k ] = $this->_realpath(rtrim($v, "/\\") . DIRECTORY_SEPARATOR, true);
        static $sepDotsep = null;
        static $sepDot = null;
        static $sepSep =null;
            $sepDotsep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
            $sepDot = DIRECTORY_SEPARATOR . '.';
        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], $sepDotsep), DIRECTORY_SEPARATOR, $path);
        if (strpos($path,$sepDot) === false && (($realpath === false && $path[0] === '.') || $realpath === null) && $path[0] !== '\\') {
            return $path;
        }
        $path = str_replace(array($sepDotsep,$sepSep), DIRECTORY_SEPARATOR, $path);
        $this->smarty->_cache[ 'template_dir_new' ] = true;
        $this->smarty->_cache[ 'config_dir_new' ] = true;
        if ($isConfig !== true) {
        }
        if ($isConfig !== false) {
        }
                $this->secure_dir[$k] = $this->smarty->_realpath($d.DIRECTORY_SEPARATOR,true);
                $directory = $this->smarty->_realpath($directory . DIRECTORY_SEPARATOR, true);
            $directory = $this->smarty->_realpath($directory, true);
            $directory = $this->smarty->_realpath($directory, true);
        $directory = dirname($filepath) . DIRECTORY_SEPARATOR;
        if (isset($dirs[ $directory ])) {
            return false;
        }
        $filepath = $this->smarty->_realpath($filepath, true);
        $directory = dirname($filepath) . DIRECTORY_SEPARATOR;
            if (!preg_match('#[\\\/][^\\\/]+[\\\/]$#', $directory)) {
                break;
            $directory = preg_replace('#[\\\/][^\\\/]+[\\\/]$#', DIRECTORY_SEPARATOR, $directory);

        // give up
        throw new SmartyException("directory '{$filepath}' not allowed by security setting");
Fixed code: ﻿===== 3.1.33-dev-1 =====
26.04.2018
 - bugfix  regarding Security Vulnerability did not solve the problem under Linux.

===== 3.1.32 ===== (24.04.2018)
 * @version   3.1.33-dev
    const SMARTY_VERSION = '3.1.33-dev-1';
                $this->plugins_dir[ $k ] = $this->_realpath(rtrim($v, '/\\') . DIRECTORY_SEPARATOR, true);
        static $sepDotSep = null;
         static $sepSep =null;
            $sepDotSep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
        $path = str_replace(array($nds[DIRECTORY_SEPARATOR], $sepDotSep), DIRECTORY_SEPARATOR, $path);
        $path = str_replace(array($sepDotSep,$sepSep), DIRECTORY_SEPARATOR, $path);



                $this->secure_dir[$k] = $this->smarty->_realpath($d. DIRECTORY_SEPARATOR,true);
                $directory = $this->smarty->_realpath($directory . '/', true);
 //           $directory = $this->smarty->_realpath($directory, true);
 //           $directory = $this->smarty->_realpath($directory, true);
        $directory = dirname($this->smarty->_realpath($filepath, true)) . DIRECTORY_SEPARATOR;
            if (!preg_match('#[\\\\/][^\\\\/]+[\\\\/]$#', $directory)) {
                // give up
                throw new SmartyException(sprintf('Smarty Security: not trusted file path \'%s\' ',$filepath));
            $directory = preg_replace('#[\\\\/][^\\\\/]+[\\\\/]$#', '/', $directory);