From e9ff55f6910832428e48a35b2e0748189ad49ae3 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 28 Apr 2026 13:25:53 +0200 Subject: [PATCH] Fix sandbox bypass: PHP code injection via {% use %} template name --- src/Node/ModuleNode.php | 6 +++--- tests/Node/ModuleTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Node/ModuleNode.php b/src/Node/ModuleNode.php index 71c572019..a3f66827f 100644 --- a/src/Node/ModuleNode.php +++ b/src/Node/ModuleNode.php @@ -248,11 +248,11 @@ final class ModuleNode extends Node ->string($key) ->raw("])) {\n") ->indent() - ->write("throw new RuntimeError('Block ") + ->write("throw new RuntimeError(sprintf('Block \"%s\" is not defined in trait \"%s\".', ") ->string($key) - ->raw(' is not defined in trait ') + ->raw(', ') ->subcompile($trait->getNode('template')) - ->raw(".', ") + ->raw('), ') ->repr($node->getTemplateLine()) ->raw(", \$this->source);\n") ->outdent() diff --git a/tests/Node/ModuleTest.php b/tests/Node/ModuleTest.php index df28815c5..b8df54f77 100644 --- a/tests/Node/ModuleTest.php +++ b/tests/Node/ModuleTest.php @@ -21,6 +21,7 @@ namespace Twig\Tests\Node; */ use Twig\Environment; +use Twig\Error\RuntimeError; use Twig\Loader\ArrayLoader; use Twig\Node\BodyNode; use Twig\Node\EmptyNode; @@ -56,6 +57,29 @@ class ModuleTest extends NodeTestCase $this->assertEquals($source->getName(), $node->getTemplateName()); } + public function testUseTagTemplateNameDoesNotInjectPhpInCompiledOutput() + { + $evilName = "evil' . print('BAD-EOL') . '.twig"; + $loader = new ArrayLoader([ + $evilName => '{% block existing %}ok{% endblock %}', + 'main.twig' => "{% use \"$evilName\" with absent_block as alias %}", + ]); + $twig = new Environment($loader); + + ob_start(); + $message = null; + try { + $twig->load('main.twig'); + } catch (RuntimeError $e) { + $message = $e->getMessage(); + } + $stdout = ob_get_clean(); + + $this->assertSame('', $stdout, 'No code from the template name must execute when the trait is loaded.'); + $this->assertNotNull($message, 'A RuntimeError must be raised for the missing block.'); + $this->assertStringContainsString($evilName, $message, 'The error message must contain the literal template name.'); + } + public static function provideTests(): iterable { $twig = new Environment(new ArrayLoader(['foo.twig' => '{{ foo }}']));