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:
Simon Wisselink
2024-05-28 22:44:30 +02:00
committed by GitHub
parent 4549822cdd
commit 76881c8d33
10 changed files with 78 additions and 72 deletions

View File

@@ -0,0 +1 @@
- Fixed a code injection vulnerability in extends-tag. This addresses CVE-2024-35226.

View File

@@ -30,7 +30,7 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
* *
* @var array * @var array
*/ */
public $optional_attributes = array('extends_resource'); public $optional_attributes = array();
/** /**
* Attribute definition: Overwrites base class. * 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 // add code to initialize inheritance
$this->registerInit($compiler, true); $this->registerInit($compiler, true);
$file = trim($_attr[ 'file' ], '\'"'); $this->compileEndChild($compiler, $_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' ]);
}
$compiler->has_code = false; $compiler->has_code = false;
return ''; return '';
} }
@@ -115,44 +93,4 @@ class Smarty_Internal_Compile_Extends extends Smarty_Internal_Compile_Shared_Inh
'') . ");\n?>" '') . ");\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;
}
} }

View File

@@ -455,15 +455,29 @@ abstract class Smarty_Internal_TemplateCompilerBase
$this->smarty->_current_file = $this->template->source->filepath; $this->smarty->_current_file = $this->template->source->filepath;
// get template source // get template source
if (!empty($this->template->source->components)) { if (!empty($this->template->source->components)) {
// we have array of inheritance templates by extends: resource $_compiled_code = '<?php $_smarty_tpl->_loadInheritance(); $_smarty_tpl->inheritance->init($_smarty_tpl, true); ?>';
// generate corresponding source code sequence
$_content = $i = 0;
Smarty_Internal_Compile_Extends::extendsSourceArrayCode($this->template); $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 { } else {
// get template source // get template source
$_content = $this->template->source->getContent(); $_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' ])) { if (!empty($this->required_plugins[ 'compiled' ]) || !empty($this->required_plugins[ 'nocache' ])) {
$_compiled_code = '<?php ' . $this->compileRequiredPlugins() . "?>\n" . $_compiled_code; $_compiled_code = '<?php ' . $this->compileRequiredPlugins() . "?>\n" . $_compiled_code;
} }

View File

@@ -1371,8 +1371,38 @@ class CompileBlockExtendsTest extends PHPUnit_Smarty
); );
} }
public function testBlockWithAssign() { public function testBlockWithAssign() {
$this->assertEquals('Captured content is: Content with lots of html here', $this->smarty->fetch('038_child.tpl')); $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'));
}
} }

View File

@@ -0,0 +1 @@
{extends "extends:helloworld.tpl', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}

View File

@@ -0,0 +1 @@
{extends 'extends:"helloworld.tpl\', var_dump(shell_exec(\'ls\')), 1, 2, 3, 4, 5, 6);}}?>'}

View File

@@ -0,0 +1 @@
{extends file='extends:"helloworld.tpl'|cat:"', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}

View File

@@ -82,6 +82,18 @@ class CompileIncludeTest extends PHPUnit_Smarty
$this->assertEquals('I1I2I3', $content, $text); $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 * test standard output
* *

View File

@@ -0,0 +1 @@
{include file="helloworld.tpl', var_dump(shell_exec('ls')), 1, 2, 3, 4, 5, 6);}}?>"}

View File

@@ -32,4 +32,11 @@ class ExtendsIssue419Test extends PHPUnit_Smarty
$this->assertEquals('child', $this->smarty->fetch('extends:001_parent.tpl|001_child.tpl')); $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);}}?>"}'));
}
} }