From 8d21f38dc35c4cd6b31c2f23fc9b8e5adbc56dfe Mon Sep 17 00:00:00 2001 From: Uwe Tews Date: Tue, 24 Apr 2018 10:38:18 +0200 Subject: [PATCH] - bugfix possible Security Vulnerability in Smarty_Security class. --- change_log.txt | 3 + libs/Smarty.class.php | 24 ++++--- libs/sysplugins/smarty_security.php | 107 ++++++++++++++-------------- 3 files changed, 73 insertions(+), 61 deletions(-) diff --git a/change_log.txt b/change_log.txt index f92af8405..a3ebcc36b 100644 --- a/change_log.txt +++ b/change_log.txt @@ -1,4 +1,7 @@ ===== 3.1.32 - dev === +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 https://github.com/smarty-php/smarty/issues/371 diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php index d9e96703b..aba244415 100644 --- a/libs/Smarty.class.php +++ b/libs/Smarty.class.php @@ -112,7 +112,7 @@ class Smarty extends Smarty_Internal_TemplateBase /** * smarty version */ - const SMARTY_VERSION = '3.1.32-dev-45'; + const SMARTY_VERSION = '3.1.32-dev-46'; /** * define variable scopes */ @@ -1042,9 +1042,15 @@ public function _getTemplateId($template_name, */ public function _realpath($path, $realpath = null) { - $nds = DIRECTORY_SEPARATOR === '/' ? '\\' : '/'; + static $nds = array('/' => '\\', '\\' => '/'); + static $sepDotsep = DIRECTORY_SEPARATOR . '.' . DIRECTORY_SEPARATOR; + static $sepDot = DIRECTORY_SEPARATOR . '.'; + static $sepSep = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR; // normalize DIRECTORY_SEPARATOR - $path = str_replace($nds, DIRECTORY_SEPARATOR, $path); + $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; + } preg_match('%^(?(?:[[:alpha:]]:[\\\\]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?(.*))$%u', $path, $parts); @@ -1056,11 +1062,11 @@ public function _realpath($path, $realpath = null) $path = getcwd() . DIRECTORY_SEPARATOR . $path; } } - // remove noop 'DIRECTORY_SEPARATOR DIRECTORY_SEPARATOR' and 'DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR' patterns - $path = preg_replace('#([\\\\/]([.]?[\\\\/])+)#u', 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) + preg_match_all('#[\\\\/]([.][.][\\\\/])+#u', $path, $match) ) { $counts = array(); foreach ($match[ 0 ] as $m) { @@ -1068,13 +1074,13 @@ public function _realpath($path, $realpath = null) } sort($counts); foreach ($counts as $count) { - $path = preg_replace('#(([\\\\/]([.]?[\\\\/])*[^\\\\/.]+){' . $count . - '}[\\\\/]([.]?[\\\\/])*([.][.][\\\\/]([.]?[\\\\/])*){' . $count . '})(?=[^.])#u', + $path = preg_replace('#([\\\\/]+[^\\\\/]+){' . $count . + '}[\\\\/]+([.][.][\\\\/]+){' . $count . '}#u', DIRECTORY_SEPARATOR, $path); } } - return $parts[ 'root' ] . $path; + return $realpath !== false ? $parts[ 'root' ] . $path : str_ireplace(getcwd(), '.', $parts[ 'root' ] . $path); } /** diff --git a/libs/sysplugins/smarty_security.php b/libs/sysplugins/smarty_security.php index e501cd825..126f6fb46 100644 --- a/libs/sysplugins/smarty_security.php +++ b/libs/sysplugins/smarty_security.php @@ -514,60 +514,39 @@ public function isTrustedStream($stream_name) public function isTrustedResourceDir($filepath, $isConfig = null) { if ($this->_include_path_status !== $this->smarty->use_include_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; - } + $_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; } $this->_include_path_status = $this->smarty->use_include_path; } - if ($isConfig !== true && - (!isset($this->smarty->_cache[ 'template_dir_new' ]) || $this->smarty->_cache[ 'template_dir_new' ]) - ) { + if ($isConfig !== true) { $_dir = $this->smarty->getTemplateDir(); if ($this->_template_dir !== $_dir) { - foreach ($this->_template_dir as $directory) { - unset($this->_resource_dir[ $directory ]); - } - foreach ($_dir as $directory) { - $this->_resource_dir[ $directory ] = true; - } + $this->_updateResourceDir($this->_template_dir, $_dir); $this->_template_dir = $_dir; } - $this->smarty->_cache[ 'template_dir_new' ] = false; } - if ($isConfig !== false && - (!isset($this->smarty->_cache[ 'config_dir_new' ]) || $this->smarty->_cache[ 'config_dir_new' ]) - ) { + if ($isConfig !== false) { $_dir = $this->smarty->getConfigDir(); if ($this->_config_dir !== $_dir) { - foreach ($this->_config_dir as $directory) { - unset($this->_resource_dir[ $directory ]); - } - foreach ($_dir as $directory) { - $this->_resource_dir[ $directory ] = true; - } + $this->_updateResourceDir($this->_config_dir, $_dir); $this->_config_dir = $_dir; } - $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 ]); + 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); } - 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->_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); } - $this->_resource_dir = $this->_checkDir($filepath, $this->_resource_dir); return true; } @@ -622,40 +601,64 @@ public function isTrustedPHPDir($filepath) $this->_php_resource_dir[ $directory ] = true; } } - - $this->_php_resource_dir = - $this->_checkDir($this->smarty->_realpath($filepath, true), $this->_php_resource_dir); - return true; + $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; + } + } /** * Check if file is inside a valid directory * * @param string $filepath * @param array $dirs valid directories * - * @return array + * @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 = array(); while (true) { - // remember the directory to add it to _resource_dir in case we're successful - $_directory[ $directory ] = true; - // test if the directory is trusted + // test if the directory is trusted if (isset($dirs[ $directory ])) { - // merge sub directories of current $directory into _resource_dir to speed up subsequent lookup - $dirs = array_merge($dirs, $_directory); - - return $dirs; + return $_directory; } // abort if we've reached root if (!preg_match('#[\\\/][^\\\/]+[\\\/]$#', $directory)) { break; } - // bubble up one level + // 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); }