Compare commits

...

18 Commits

Author SHA1 Message Date
Simon Wisselink b3ade90dec Merge branch 'release/3.1.46' into support/3.1 2022-08-01 23:58:13 +02:00
Simon Wisselink 4bcca1a2b4 version bump 2022-08-01 23:58:11 +02:00
Simon Wisselink 0f7037d7ee Fixed second param of unescape modifier (#779)
Fixes #777
2022-08-01 23:49:30 +02:00
Simon Wisselink 459bd4adbd Merge branch 'AnrDaemon-fix-issue-549-v3' into support/3.1 2022-07-15 10:45:57 +02:00
Simon Wisselink 71c063d0b2 Added changelog 2022-07-15 10:44:38 +02:00
Simon Wisselink c7c2d476aa Merge branch 'fix-issue-549-v3' of github.com:AnrDaemon/smarty into AnrDaemon-fix-issue-549-v3 2022-07-15 10:33:38 +02:00
Simon Wisselink a2713ab89e Merge branch 'release/3.1.45' into support/3.1 2022-05-17 14:57:52 +02:00
Simon Wisselink 28ea4f078c version bump 2022-05-17 14:57:50 +02:00
Simon Wisselink b96c3060d5 Add CVE to changelog 2022-05-17 14:57:30 +02:00
Simon Wisselink 3606c4717e Merge branch 'security/blockfunctioninjection_v3' into support/3.1 2022-05-17 14:55:24 +02:00
Simon Wisselink 8a8cf58c5a Changelog 2022-05-16 13:38:45 +02:00
Simon Wisselink 4eb86a6770 Replace '*/' in user supplied input in C-style comments with '* /'. 2022-05-16 13:36:03 +02:00
Simon Wisselink 5f59e14fc1 Added change to changelog 2022-04-26 21:59:46 +02:00
AnrDaemon ed14f5269a Fixed replace modifier by converting encoding if needed
`mb_split` will fail if `$pattern` or `$string` contains byte sequences not valid for `mb_regex_encoding()`.

Convert both strings and set regex encoding before calling `mb_split()` if needed.

Fixes smarty-php#549
2022-03-04 10:45:57 +03:00
Pavel Kochman 9f47b059ed math equation return warning: math: illegal character for : {math equation="max(x, y)" x=$x y=$y} 2022-02-07 09:45:14 +01:00
Simon Wisselink 99085d8dc6 Merge branch 'release/3.1.44' into support/3.1 2022-01-18 00:12:04 +01:00
Simon Wisselink eed1e07128 version bump 2022-01-18 00:12:03 +01:00
Simon Wisselink 208f881d06 Bugfix/702 (#709)
* fix(math): fix equation regexp

Fixes #702.
2022-01-18 00:10:25 +01:00
16 changed files with 207 additions and 27 deletions
+19
View File
@@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [3.1.46] - 2022-08-01
### Fixed
- Fixed problems with smarty_mb_str_replace [#549](https://github.com/smarty-php/smarty/issues/549)
- Fixed second parameter of unescape modifier not working [#777](https://github.com/smarty-php/smarty/issues/777)
## [3.1.45] - 2022-05-17
### Security
- Prevent PHP injection through malicious block name or include file name. This addresses CVE-2022-29221
### Fixed
- Math equation `max(x, y)` didn't work anymore [#721](https://github.com/smarty-php/smarty/issues/721)
## [3.1.44] - 2022-01-18
### Fixed
- Fixed illegal characters bug in math function security check [#702](https://github.com/smarty-php/smarty/issues/702)
## [3.1.43] - 2022-01-10
### Security
+1 -1
View File
@@ -111,7 +111,7 @@ class Smarty extends Smarty_Internal_TemplateBase
/**
* smarty version
*/
const SMARTY_VERSION = '3.1.43';
const SMARTY_VERSION = '3.1.46';
/**
* define variable scopes
*/
+2 -2
View File
@@ -69,8 +69,8 @@ function smarty_function_math($params, $template)
// Adapted from https://www.php.net/manual/en/function.eval.php#107377
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))';
$operators = '[+\/*\^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)+\)|\((?1)+\)))(?:'.$operators.'(?2))?)+$/';
$operators = '[,+\/*\^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)+\)|\((?1)+\)))(?:'.$operators.'(?1))?)+$/';
if (!preg_match($regexp, $equation)) {
trigger_error("math: illegal characters", E_USER_WARNING);
+15 -7
View File
@@ -14,20 +14,28 @@
* @author Rodney Rehm
*
* @param array $params parameters
* @param Smarty_Internal_TemplateCompilerBase $compiler
*
* @return string with compiled code
*/
function smarty_modifiercompiler_unescape($params)
function smarty_modifiercompiler_unescape($params, Smarty_Internal_TemplateCompilerBase $compiler)
{
if (!isset($params[ 1 ])) {
$params[ 1 ] = 'html';
}
$compiler->template->_checkPlugins(
array(
array(
'function' => 'smarty_literal_compiler_param',
'file' => SMARTY_PLUGINS_DIR . 'shared.literal_compiler_param.php'
)
)
);
$esc_type = smarty_literal_compiler_param($params, 1, 'html');
if (!isset($params[ 2 ])) {
$params[ 2 ] = '\'' . addslashes(Smarty::$_CHARSET) . '\'';
} else {
$params[ 2 ] = "'{$params[ 2 ]}'";
}
switch (trim($params[ 1 ], '"\'')) {
switch ($esc_type) {
case 'entity':
case 'htmlall':
if (Smarty::$_MBSTRING) {
+34
View File
@@ -44,9 +44,43 @@ if (!function_exists('smarty_mb_str_replace')) {
}
}
} else {
$mb_reg_charset = mb_regex_encoding();
// Check if mbstring regex is using UTF-8
$reg_is_unicode = !strcasecmp($mb_reg_charset, "UTF-8");
if(!$reg_is_unicode) {
// ...and set to UTF-8 if not
mb_regex_encoding("UTF-8");
}
// See if charset used by Smarty is matching one used by regex...
$current_charset = mb_regex_encoding();
$convert_result = (bool)strcasecmp(Smarty::$_CHARSET, $current_charset);
if($convert_result) {
// ...convert to it if not.
$subject = mb_convert_encoding($subject, $current_charset, Smarty::$_CHARSET);
$search = mb_convert_encoding($search, $current_charset, Smarty::$_CHARSET);
$replace = mb_convert_encoding($replace, $current_charset, Smarty::$_CHARSET);
}
$parts = mb_split(preg_quote($search), $subject);
// If original regex encoding was not unicode...
if(!$reg_is_unicode) {
// ...restore original regex encoding to avoid breaking the system.
mb_regex_encoding($mb_reg_charset);
}
if($parts === false) {
// This exception is thrown if call to mb_split failed.
// Usually it happens, when $search or $replace are not valid for given mb_regex_encoding().
// There may be other cases for it to fail, please file an issue if you find a reproducible one.
throw new SmartyException("Source string is not a valid $current_charset sequence (probably)");
}
$count = count($parts) - 1;
$subject = implode($replace, $parts);
// Convert results back to charset used by Smarty, if needed.
if($convert_result) {
$subject = mb_convert_encoding($subject, Smarty::$_CHARSET, $current_charset);
}
}
return $subject;
}
@@ -125,7 +125,7 @@ class Smarty_Internal_Compile_Blockclose extends Smarty_Internal_Compile_Shared_
// setup buffer for template function code
$compiler->parser->current_buffer = new Smarty_Internal_ParseTree_Template();
$output = "<?php\n";
$output .= "/* {block {$_name}} */\n";
$output .= $compiler->cStyleComment(" {block {$_name}} ") . "\n";
$output .= "class {$_className} extends Smarty_Internal_Block\n";
$output .= "{\n";
foreach ($_block as $property => $value) {
@@ -155,7 +155,7 @@ class Smarty_Internal_Compile_Blockclose extends Smarty_Internal_Compile_Shared_
}
$output .= "}\n";
$output .= "}\n";
$output .= "/* {/block {$_name}} */\n\n";
$output .= $compiler->cStyleComment(" {/block {$_name}} ") . "\n\n";
$output .= "?>\n";
$compiler->parser->current_buffer->append_subtree(
$compiler->parser,
@@ -134,7 +134,7 @@ class Smarty_Internal_Compile_Functionclose extends Smarty_Internal_CompileBase
if ($compiler->template->compiled->has_nocache_code) {
$compiler->parent_compiler->tpl_function[ $_name ][ 'call_name_caching' ] = $_funcNameCaching;
$output = "<?php\n";
$output .= "/* {$_funcNameCaching} */\n";
$output .= $compiler->cStyleComment(" {$_funcNameCaching} ") . "\n";
$output .= "if (!function_exists('{$_funcNameCaching}')) {\n";
$output .= "function {$_funcNameCaching} (Smarty_Internal_Template \$_smarty_tpl,\$params) {\n";
$output .= "ob_start();\n";
@@ -159,7 +159,7 @@ class Smarty_Internal_Compile_Functionclose extends Smarty_Internal_CompileBase
$output .= "/*/%%SmartyNocache:{$compiler->template->compiled->nocache_hash}%%*/\";\n?>";
$output .= "<?php echo str_replace('{$compiler->template->compiled->nocache_hash}', \$_smarty_tpl->compiled->nocache_hash, ob_get_clean());\n";
$output .= "}\n}\n";
$output .= "/*/ {$_funcName}_nocache */\n\n";
$output .= $compiler->cStyleComment("/ {$_funcName}_nocache ") . "\n\n";
$output .= "?>\n";
$compiler->parser->current_buffer->append_subtree(
$compiler->parser,
@@ -179,7 +179,7 @@ class Smarty_Internal_Compile_Functionclose extends Smarty_Internal_CompileBase
}
$compiler->parent_compiler->tpl_function[ $_name ][ 'call_name' ] = $_funcName;
$output = "<?php\n";
$output .= "/* {$_funcName} */\n";
$output .= $compiler->cStyleComment(" {$_funcName} ") . "\n";
$output .= "if (!function_exists('{$_funcName}')) {\n";
$output .= "function {$_funcName}(Smarty_Internal_Template \$_smarty_tpl,\$params) {\n";
$output .= $_paramsCode;
@@ -196,7 +196,7 @@ class Smarty_Internal_Compile_Functionclose extends Smarty_Internal_CompileBase
);
$compiler->parser->current_buffer->append_subtree($compiler->parser, $_functionCode);
$output = "<?php\n}}\n";
$output .= "/*/ {$_funcName} */\n\n";
$output .= $compiler->cStyleComment("/ {$_funcName} ") . "\n\n";
$output .= "?>\n";
$compiler->parser->current_buffer->append_subtree(
$compiler->parser,
@@ -318,14 +318,14 @@ class Smarty_Internal_Compile_Include extends Smarty_Internal_CompileBase
}
// get compiled code
$compiled_code = "<?php\n\n";
$compiled_code .= "/* Start inline template \"{$sourceInfo}\" =============================*/\n";
$compiled_code .= $compiler->cStyleComment(" Start inline template \"{$sourceInfo}\" =============================") . "\n";
$compiled_code .= "function {$tpl->compiled->unifunc} (Smarty_Internal_Template \$_smarty_tpl) {\n";
$compiled_code .= "?>\n" . $tpl->compiler->compileTemplateSource($tpl, null, $compiler->parent_compiler);
$compiled_code .= "<?php\n";
$compiled_code .= "}\n?>\n";
$compiled_code .= $tpl->compiler->postFilter($tpl->compiler->blockOrFunctionCode);
$compiled_code .= "<?php\n\n";
$compiled_code .= "/* End inline template \"{$sourceInfo}\" =============================*/\n";
$compiled_code .= $compiler->cStyleComment(" End inline template \"{$sourceInfo}\" =============================") . "\n";
$compiled_code .= '?>';
unset($tpl->compiler);
if ($tpl->compiled->has_nocache_code) {
@@ -157,10 +157,12 @@ class Smarty_Internal_Config_File_Compiler
$this->smarty->_debug->end_compile($this->template);
}
// template header code
$template_header =
"<?php /* Smarty version " . Smarty::SMARTY_VERSION . ", created on " . strftime("%Y-%m-%d %H:%M:%S") .
"\n";
$template_header .= " compiled from '{$this->template->source->filepath}' */ ?>\n";
$template_header = sprintf(
"<?php /* Smarty version %s, created on %s\n compiled from '%s' */ ?>\n",
Smarty::SMARTY_VERSION,
date("Y-m-d H:i:s"),
str_replace('*/', '* /' , $this->template->source->filepath)
);
$code = '<?php $_smarty_tpl->smarty->ext->configLoad->_loadConfigVars($_smarty_tpl, ' .
var_export($this->config_data, true) . '); ?>';
return $template_header . $this->template->smarty->ext->_codeFrame->create($this->template, $code);
@@ -44,9 +44,12 @@ class Smarty_Internal_Runtime_CodeFrame
$properties[ 'file_dependency' ] = $_template->cached->file_dependency;
$properties[ 'cache_lifetime' ] = $_template->cache_lifetime;
}
$output = "<?php\n";
$output .= "/* Smarty version {$properties[ 'version' ]}, created on " . strftime("%Y-%m-%d %H:%M:%S") .
"\n from '" . str_replace('*/', '* /', $_template->source->filepath) . "' */\n\n";
$output = sprintf(
"<?php\n/* Smarty version %s, created on %s\n from '%s' */\n\n",
$properties[ 'version' ],
date("Y-m-d H:i:s"),
str_replace('*/', '* /', $_template->source->filepath)
);
$output .= "/* @var Smarty_Internal_Template \$_smarty_tpl */\n";
$dec = "\$_smarty_tpl->_decodeProperties(\$_smarty_tpl, " . var_export($properties, true) . ',' .
($cache ? 'true' : 'false') . ')';
@@ -1455,6 +1455,10 @@ abstract class Smarty_Internal_TemplateCompilerBase
*/
abstract protected function doCompile($_content, $isTemplateSource = false);
public function cStyleComment($string) {
return '/*' . str_replace('*/', '* /' , $string) . '*/';
}
/**
* Compile Tag
*
@@ -63,6 +63,11 @@ class PluginModifierUnescapeTest extends PHPUnit_Smarty
$tpl = $this->smarty->createTemplate('string:{"' . $encoded . '"|unescape:"url"}');
$this->assertEquals($result, $this->smarty->fetch($tpl));
}
}
?>
public function testCharset()
{
$tpl = $this->smarty->createTemplate("string:{'&#039;Stiff Opposition Expected to Casketless Funeral Plan&#039;'|unescape:'htmlall':'utf-8'}");
$this->assertEquals("'Stiff Opposition Expected to Casketless Funeral Plan'", $this->smarty->fetch($tpl));
}
}
@@ -44,6 +44,30 @@ class MathTest extends PHPUnit_Smarty
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
public function testMultipleOperators()
{
$this->smarty->disableSecurity();
$expected = "2 -- 2";
$tpl = $this->smarty->createTemplate('eval:{$x = 5}{$y = 4}{math equation="x - y + 1" x=$x y=$y} -- {math equation="5 - 4 + 1"}');
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
public function testMathMaxFunctionParameters()
{
$this->smarty->disableSecurity();
$expected = max(0, 2) . ' -- ' . max(0, 2, 3);
$tpl = $this->smarty->createTemplate('eval:{$x = 0}{$y = 2}{$z = 3}{math equation="max(x, y)" x=$x y=$y} -- {math equation="max(x, y, z)" x=$x y=$y z=$z}');
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
public function testMathMinFunctionParameters()
{
$this->smarty->disableSecurity();
$expected = min(1, 2) . ' -- ' . min(1, 2, 0);
$tpl = $this->smarty->createTemplate('eval:{$x = 1}{$y = 2}{$z = 0}{math equation="min(x, y)" x=$x y=$y} -- {math equation="min(x, y, z)" x=$x y=$y z=$z}');
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
public function testSyntaxSin()
{
$this->smarty->disableSecurity();
@@ -0,0 +1,77 @@
<?php
/**
* Smarty PHPunit tests - issue #549 regression tests
*
* @package PHPunit
* @author Andrey Repin <anrdaemon@yandex.ru>
*/
/**
* class for compiler tests
*
* @runTestsInSeparateProcess
* @preserveGlobalState disabled
* @backupStaticAttributes enabled
*
* mb_split breaks if Smarty encoding is not the same as mbstring regex encoding.
*/
class MbSplitEncodingIssue549Test extends PHPUnit_Smarty
{
/** @var string Saved Smarty charset */
private $charset;
/** @var array Source data for tests, hexed to protect from accidental reencoding */
private $data = array(
"subject" => '4772c3bc6e6577616c64', // "Grünewald"
"pattern" => '77616c64', // "wald"
"replacement" => '7374c3bc726d', // "stürm"
"result" => '4772c3bc6e657374c3bc726d', // "Grünestürm"
);
public function setUp()
{
if(!\Smarty::$_MBSTRING)
{
return $this->markTestSkipped("mbstring extension is not in use by Smarty");
}
$this->charset = \Smarty::$_CHARSET;
$this->setUpSmarty(dirname(__FILE__));
}
public function tearDown()
{
\Smarty::$_CHARSET = $this->charset ?: \Smarty::$_CHARSET;
$this->cleanDirs();
}
/** Provider for testReplaceModifier
*/
public function encodingPairsProvider()
{
return array(
"with non-UNICODE src/non-UNICODE regex (PHP < 5.6 default)" => array("Windows-1252", "EUC-JP"),
"with UTF-8 src/non-UNICODE regex (PHP < 5.6 default)" => array("UTF-8", "EUC-JP"),
"with UTF-8 src/UTF-8 regex (PHP >= 5.6)" => array("UTF-8", "UTF-8"),
"with non-UNICODE src/UTF-8 regex" => array("Windows-1252", "UTF-8"),
);
}
/** Test behavior of `replace` modifier with different source and regex encodings
*
* @dataProvider encodingPairsProvider
*/
public function testReplaceModifier($mb_int_encoding, $mb_regex_encoding)
{
$data = $this->data;
\array_walk($data, function(&$value, $key) use($mb_int_encoding) {
$value = \mb_convert_encoding(pack("H*", $value), $mb_int_encoding, "UTF-8");
});
\extract($data, \EXTR_SKIP);
\mb_regex_encoding($mb_regex_encoding);
\Smarty::$_CHARSET = $mb_int_encoding;
$this->assertEquals($result, $this->smarty->fetch("string:{\"$subject\"|replace:\"$pattern\":\"$replacement\"}"));
}
}
@@ -0,0 +1,2 @@
# Ignore anything in here, but keep this directory
*
@@ -0,0 +1,2 @@
# Ignore anything in here, but keep this directory
*