mirror of
https://github.com/smarty-php/smarty.git
synced 2025-09-25 20:00:54 +02:00
Merge pull request from GHSA-4rmg-292m-wg3w
* Fixed a code injection vulnerability in extends-tag * update tests for smarty v4
This commit is contained in:
1
changelog/GHSA-4rmg-292m-wg3w.md
Normal file
1
changelog/GHSA-4rmg-292m-wg3w.md
Normal file
@@ -0,0 +1 @@
|
||||
- Fixed a code injection vulnerability in extends-tag. This addresses CVE-2024-35226.
|
@@ -30,7 +30,7 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $optional_attributes = array('extends_resource');
|
||||
public $optional_attributes = array();
|
||||
|
||||
/**
|
||||
* Attribute definition: Overwrites base class.
|
||||
@@ -62,29 +62,7 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
|
||||
}
|
||||
// add code to initialize inheritance
|
||||
$this->registerInit($compiler, true);
|
||||
$file = trim($_attr[ 'file' ], '\'"');
|
||||
if (strlen($file) > 8 && substr($file, 0, 8) === 'extends:') {
|
||||
// generate code for each template
|
||||
$files = array_reverse(explode('|', substr($file, 8)));
|
||||
$i = 0;
|
||||
foreach ($files as $file) {
|
||||
if ($file[ 0 ] === '"') {
|
||||
$file = trim($file, '".');
|
||||
} else {
|
||||
$file = "'{$file}'";
|
||||
}
|
||||
$i++;
|
||||
if ($i === count($files) && isset($_attr[ 'extends_resource' ])) {
|
||||
$this->compileEndChild($compiler);
|
||||
}
|
||||
$this->compileInclude($compiler, $file);
|
||||
}
|
||||
if (!isset($_attr[ 'extends_resource' ])) {
|
||||
$this->compileEndChild($compiler);
|
||||
}
|
||||
} else {
|
||||
$this->compileEndChild($compiler, $_attr[ 'file' ]);
|
||||
}
|
||||
$this->compileEndChild($compiler, $_attr[ 'file' ]);
|
||||
$compiler->has_code = false;
|
||||
return '';
|
||||
}
|
||||
@@ -115,44 +93,4 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
|
||||
'') . ");\n?>"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add code for including subtemplate to end of template
|
||||
*
|
||||
* @param \Smarty_Internal_TemplateCompilerBase $compiler
|
||||
* @param string $template subtemplate name
|
||||
*
|
||||
* @throws \SmartyCompilerException
|
||||
* @throws \SmartyException
|
||||
*/
|
||||
private function compileInclude(Smarty_Internal_TemplateCompilerBase $compiler, $template)
|
||||
{
|
||||
$compiler->parser->template_postfix[] = new Smarty_Internal_ParseTree_Tag(
|
||||
$compiler->parser,
|
||||
$compiler->compileTag(
|
||||
'include',
|
||||
array(
|
||||
$template,
|
||||
array('scope' => 'parent')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create source code for {extends} from source components array
|
||||
*
|
||||
* @param \Smarty_Internal_Template $template
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function extendsSourceArrayCode(Smarty_Internal_Template $template)
|
||||
{
|
||||
$resources = array();
|
||||
foreach ($template->source->components as $source) {
|
||||
$resources[] = $source->resource;
|
||||
}
|
||||
return $template->smarty->left_delimiter . 'extends file=\'extends:' . join('|', $resources) .
|
||||
'\' extends_resource=true' . $template->smarty->right_delimiter;
|
||||
}
|
||||
}
|
||||
|
@@ -455,15 +455,29 @@ abstract class Smarty_Internal_TemplateCompilerBase
|
||||
$this->smarty->_current_file = $this->template->source->filepath;
|
||||
// get template source
|
||||
if (!empty($this->template->source->components)) {
|
||||
// we have array of inheritance templates by extends: resource
|
||||
// generate corresponding source code sequence
|
||||
$_content =
|
||||
Smarty_Internal_Compile_Extends::extendsSourceArrayCode($this->template);
|
||||
$_compiled_code = '<?php $_smarty_tpl->_loadInheritance(); $_smarty_tpl->inheritance->init($_smarty_tpl, true); ?>';
|
||||
|
||||
$i = 0;
|
||||
$reversed_components = array_reverse($this->template->getSource()->components);
|
||||
foreach ($reversed_components as $source) {
|
||||
$i++;
|
||||
if ($i === count($reversed_components)) {
|
||||
$_compiled_code .= '<?php $_smarty_tpl->inheritance->endChild($_smarty_tpl); ?>';
|
||||
}
|
||||
$_compiled_code .= $this->compileTag(
|
||||
'include',
|
||||
[
|
||||
var_export($source->resource, true),
|
||||
['scope' => 'parent'],
|
||||
]
|
||||
);
|
||||
}
|
||||
$_compiled_code = $this->postFilter($_compiled_code, $this->template);
|
||||
} else {
|
||||
// get template source
|
||||
$_content = $this->template->source->getContent();
|
||||
$_compiled_code = $this->postFilter($this->doCompile($this->preFilter($_content), true));
|
||||
}
|
||||
$_compiled_code = $this->postFilter($this->doCompile($this->preFilter($_content), true));
|
||||
if (!empty($this->required_plugins[ 'compiled' ]) || !empty($this->required_plugins[ 'nocache' ])) {
|
||||
$_compiled_code = '<?php ' . $this->compileRequiredPlugins() . "?>\n" . $_compiled_code;
|
||||
}
|
||||
|
@@ -1371,8 +1371,38 @@ class CompileBlockExtendsTest extends PHPUnit_Smarty
|
||||
);
|
||||
}
|
||||
|
||||
public function testBlockWithAssign() {
|
||||
$this->assertEquals('Captured content is: Content with lots of html here', $this->smarty->fetch('038_child.tpl'));
|
||||
}
|
||||
public function testBlockWithAssign() {
|
||||
$this->assertEquals('Captured content is: Content with lots of html here', $this->smarty->fetch('038_child.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test escaping of file parameter
|
||||
*/
|
||||
public function testEscaping()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('hello world', $this->smarty->fetch('escaping.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test escaping of file parameter 2
|
||||
*/
|
||||
public function testEscaping2()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('hello world', $this->smarty->fetch('escaping2.tpl'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test escaping of file parameter 3
|
||||
*/
|
||||
public function testEscaping3()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('hello world', $this->smarty->fetch('escaping3.tpl'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1 @@
|
||||
{extends "extends:helloworld.tpl', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}
|
@@ -0,0 +1 @@
|
||||
{extends 'extends:"helloworld.tpl\', var_dump(shell_exec(\'ls\')), 1, 2, 3, 4, 5, 6);}}?>'}
|
@@ -0,0 +1 @@
|
||||
{extends file='extends:"helloworld.tpl'|cat:"', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}
|
@@ -82,6 +82,18 @@ class CompileIncludeTest extends PHPUnit_Smarty
|
||||
$this->assertEquals('I1I2I3', $content, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* test template name escaping
|
||||
*/
|
||||
public function testIncludeFilenameEscaping()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$tpl = $this->smarty->createTemplate('test_include_security.tpl');
|
||||
$content = $this->smarty->fetch($tpl);
|
||||
$this->assertEquals("hello world", $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* test standard output
|
||||
*
|
||||
|
@@ -0,0 +1 @@
|
||||
{include file="helloworld.tpl', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}
|
@@ -32,4 +32,11 @@ class ExtendsIssue419Test extends PHPUnit_Smarty
|
||||
$this->assertEquals('child', $this->smarty->fetch('extends:001_parent.tpl|001_child.tpl'));
|
||||
}
|
||||
|
||||
public function testextendsSecurity()
|
||||
{
|
||||
$this->expectException(SmartyException::class);
|
||||
$this->expectExceptionMessageRegExp('/Unable to load.*/');
|
||||
$this->assertEquals('child', $this->smarty->fetch('string:{include "001_parent.tpl\', var_dump(shell_exec(\'ls\')), 1, 2, 3, 4, 5, 6);}}?>"}'));
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user