mirror of
https://github.com/twigphp/Twig.git
synced 2026-08-30 03:57:21 +00:00
Fix sandbox bypass: PHP code injection via _self / import macro reference
This commit is contained in:
committed by
Fabien Potencier
parent
aeb37f4801
commit
324fa60545
@@ -67,12 +67,15 @@ final class DotExpressionParser extends AbstractExpressionParser implements Infi
|
||||
|
||||
if (
|
||||
$expr instanceof NameExpression
|
||||
&& $attribute instanceof ConstantExpression
|
||||
&& \is_string($name = $attribute->getAttribute('value'))
|
||||
&& preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $name)
|
||||
&& (
|
||||
null !== $parser->getImportedSymbol('template', $expr->getAttribute('name'))
|
||||
|| '_self' === $expr->getAttribute('name') && $attribute instanceof ConstantExpression
|
||||
|| '_self' === $expr->getAttribute('name')
|
||||
)
|
||||
) {
|
||||
return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$attribute->getAttribute('value'), $arguments, $expr->getTemplateLine());
|
||||
return new MacroReferenceExpression(new TemplateVariable($expr->getAttribute('name'), $expr->getTemplateLine()), 'macro_'.$name, $arguments, $expr->getTemplateLine());
|
||||
}
|
||||
|
||||
return new GetAttrExpression($expr, $attribute, $arguments, $type, $lineno, $nullSafe);
|
||||
|
||||
@@ -519,6 +519,73 @@ EOF
|
||||
$this->assertEquals('<p>username</p>', $twig->load('index')->render([]));
|
||||
}
|
||||
|
||||
public function testSelfMacroReferenceWithStringLiteralDoesNotInjectPhp()
|
||||
{
|
||||
$twig = $this->getEnvironment(true, [], ['index' => '{{ _self.(\'foo + 1; trigger_error("BAD-MACRO-REF") //\') }}']);
|
||||
|
||||
$compiled = $twig->compileSource($twig->getLoader()->getSourceContext('index'));
|
||||
$this->assertStringNotContainsString('trigger_error("BAD-MACRO-REF")', $compiled, 'Attacker-controlled string must not appear raw in compiled PHP source.');
|
||||
$this->assertStringNotContainsString('->macro_foo + 1;', $compiled, 'No raw injection should reach the generated method-call site.');
|
||||
|
||||
$triggered = false;
|
||||
set_error_handler(static function ($severity, $message) use (&$triggered) {
|
||||
if (str_contains($message, 'BAD-MACRO-REF')) {
|
||||
$triggered = true;
|
||||
}
|
||||
return true;
|
||||
}, \E_USER_NOTICE | \E_USER_WARNING);
|
||||
try {
|
||||
try {
|
||||
$twig->load('index')->render([]);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
$this->assertFalse($triggered, 'No PHP from the template literal must execute.');
|
||||
}
|
||||
|
||||
public function testImportedTemplateMacroReferenceWithBadIdentifierDoesNotInjectPhp()
|
||||
{
|
||||
$payload = '{% import "m" as m %}{{ m.(\'foo + 1; trigger_error("BAD-IMPORT-REF") //\') }}';
|
||||
$twig = $this->getEnvironment(true, [], [
|
||||
'index' => $payload,
|
||||
'm' => '{% macro greet() %}hi{% endmacro %}',
|
||||
], ['import']);
|
||||
|
||||
$compiled = $twig->compileSource($twig->getLoader()->getSourceContext('index'));
|
||||
$this->assertStringNotContainsString('trigger_error("BAD-IMPORT-REF")', $compiled, 'Attacker-controlled string must not appear raw in compiled PHP source.');
|
||||
|
||||
$triggered = false;
|
||||
set_error_handler(static function ($severity, $message) use (&$triggered) {
|
||||
if (str_contains($message, 'BAD-IMPORT-REF')) {
|
||||
$triggered = true;
|
||||
}
|
||||
return true;
|
||||
}, \E_USER_NOTICE | \E_USER_WARNING);
|
||||
try {
|
||||
try {
|
||||
$twig->load('index')->render([]);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
$this->assertFalse($triggered, 'No PHP from the template literal must execute.');
|
||||
}
|
||||
|
||||
public function testSelfMacroReferenceWithValidIdentifierStillWorks()
|
||||
{
|
||||
$twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<<EOF
|
||||
{%- macro greet(n) %}Hi {{ n }}{% endmacro %}
|
||||
{{- _self.('greet')('World') }}
|
||||
EOF
|
||||
], ['macro'], ['escape']);
|
||||
|
||||
$this->assertSame('Hi World', $twig->load('index')->render([]));
|
||||
}
|
||||
|
||||
public function testSandboxDisabledAfterIncludeFunctionError()
|
||||
{
|
||||
$twig = $this->getEnvironment(false, [], self::$templates);
|
||||
|
||||
Reference in New Issue
Block a user