CVE-2018-13982 (PHP, published 2018-09-18T21:29Z)
Description: 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.
Severity: MEDIUM (CVSS v3 base score: 7.5)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
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.
Repository: https://github.com/smarty-php/smarty
Files changed:
change_log.txt
libs/Smarty.class.php
libs/sysplugins/smarty_security.php
Commit message: - bugfix  possible Security Vulnerability in Smarty_Security class.


Vulnerable code:
    const SMARTY_VERSION = '3.1.32-dev-45';
        $nds = DIRECTORY_SEPARATOR === '/' ? '\\' : '/';
        $path = str_replace($nds, DIRECTORY_SEPARATOR, $path);
        // remove noop 'DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR' and 'DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR' patterns
        $path = preg_replace('#([\\\\/]([.]?[\\\\/])+)#u', DIRECTORY_SEPARATOR, $path);
            preg_match_all('#(([.]?[\\\\/])*([.][.])[\\\\/]([.]?[\\\\/])*)+#u', $path, $match)
                $path = preg_replace('#(([\\\\/]([.]?[\\\\/])*[^\\\\/.]+){' . $count .
                                     '}[\\\\/]([.]?[\\\\/])*([.][.][\\\\/]([.]?[\\\\/])*){' . $count . '})(?=[^.])#u',
        return $parts[ 'root' ] . $path;
            foreach ($this->_include_dir as $directory) {
                unset($this->_resource_dir[ $directory ]);
            }
            if ($this->smarty->use_include_path) {
                $this->_include_dir = array();
                $_dirs = $this->smarty->ext->_getIncludePath->getIncludePathDirs($this->smarty);
                foreach ($_dirs as $directory) {
                    $this->_include_dir[] = $directory;
                    $this->_resource_dir[ $directory ] = true;
                }
        if ($isConfig !== true &&
            (!isset($this->smarty->_cache[ 'template_dir_new' ]) || $this->smarty->_cache[ 'template_dir_new' ])
        ) {
                foreach ($this->_template_dir as $directory) {
                    unset($this->_resource_dir[ $directory ]);
                }
                foreach ($_dir as $directory) {
                    $this->_resource_dir[ $directory ] = true;
                }
            $this->smarty->_cache[ 'template_dir_new' ] = false;
        if ($isConfig !== false &&
            (!isset($this->smarty->_cache[ 'config_dir_new' ]) || $this->smarty->_cache[ 'config_dir_new' ])
        ) {
                foreach ($this->_config_dir as $directory) {
                    unset($this->_resource_dir[ $directory ]);
                }
                foreach ($_dir as $directory) {
                    $this->_resource_dir[ $directory ] = true;
                }
            $this->smarty->_cache[ 'config_dir_new' ] = false;
        if ($this->_secure_dir !== (array) $this->secure_dir) {
            foreach ($this->_secure_dir as $directory) {
                unset($this->_resource_dir[ $directory ]);
            foreach ((array) $this->secure_dir as $directory) {
                $directory = $this->smarty->_realpath($directory . DIRECTORY_SEPARATOR, true);
                $this->_resource_dir[ $directory ] = true;
            }
            $this->_secure_dir = (array) $this->secure_dir;
        $this->_resource_dir = $this->_checkDir($filepath, $this->_resource_dir);

        $this->_php_resource_dir =
            $this->_checkDir($this->smarty->_realpath($filepath, true), $this->_php_resource_dir);
        return true;
     * @return array
            // remember the directory to add it to _resource_dir in case we're successful
            $_directory[ $directory ] = true;
            // test if the directory is trusted
                // merge sub directories of current $directory into _resource_dir to speed up subsequent lookup
                $dirs = array_merge($dirs, $_directory);

                return $dirs;
            // bubble up one level

Fixed code:
24.04.2018
 - bugfix  possible Security Vulnerability in Smarty_Security class.

    const SMARTY_VERSION = '3.1.32-dev-46';
        static $nds = array('/' => '\\', '\\' => '/');
        static $sepDotsep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR;
        static $sepDot = DIRECTORY_SEPARATOR . '.';
        static $sepSep = DIRECTORY_SEPARATOR . 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;
        }
       // remove noop 'DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR' and 'DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR' patterns
        $path = str_replace(array($sepDotsep,$sepSep), DIRECTORY_SEPARATOR, $path);
            preg_match_all('#[\\\\/]([.][.][\\\\/])+#u', $path, $match)
                $path = preg_replace('#([\\\\/]+[^\\\\/]+){' . $count .
                                     '}[\\\\/]+([.][.][\\\\/]+){' . $count . '}#u',
        return $realpath !== false ? $parts[ 'root' ] . $path : str_ireplace(getcwd(), '.', $parts[ 'root' ] . $path);
            $_dir = $this->smarty->use_include_path ? $this->smarty->ext->_getIncludePath->getIncludePathDirs($this->smarty) : array();
            if ($this->_include_dir !== $_dir) {
                $this->_updateResourceDir($this->_include_dir, $_dir);
                $this->_include_dir = $_dir;
        if ($isConfig !== true) {
                $this->_updateResourceDir($this->_template_dir, $_dir);
        if ($isConfig !== false) {
                $this->_updateResourceDir($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->_updateResourceDir($this->_secure_dir, $this->secure_dir);
            $this->_secure_dir = $this->secure_dir;
        }
        $addPath =  $this->_checkDir($filepath, $this->_resource_dir);
        if ($addPath !== false) {
           $this->_resource_dir = array_merge($this->_resource_dir, $addPath);
        $addPath =  $this->_checkDir($filepath, $this->_php_resource_dir);
        if ($addPath !== false) {
           $this->_php_resource_dir = array_merge($this->_php_resource_dir, $addPath);
        }
         return true;
    /**
     * Remove old directories and its sub folders, add new directories
     *
     * @param array $oldDir
     * @param array $newDir
     */
    private function _updateResourceDir($oldDir, $newDir) {
        foreach ($oldDir as $directory) {
            $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);
            $this->_resource_dir[ $directory ] = true;
        }
    }
     * @return array|bool
        $directory = dirname($filepath) . DIRECTORY_SEPARATOR;
        if (isset($dirs[ $directory ])) {
            return false;
        }
        $filepath = $this->smarty->_realpath($filepath, true);
             // test if the directory is trusted
               return $_directory;
            // remember the directory to add it to _resource_dir in case we're successful
            $_directory[ $directory ] = true;
           // bubble up one level