From: Fabien Potencier <fabien@potencier.org>
Date: Mon, 9 Sep 2024 18:53:26 +0200
Subject: Fix a security issue when an included sandboxed template has been
 loaded before without the sandbox context

Origin: backport, https://github.com/twigphp/Twig/commit/2102dd135986db79192d26fb5f5817a566e0a7de
Bug: https://github.com/twigphp/Twig/security/advisories/GHSA-6j75-5wfj-gh66
Bug-Debian: https://bugs.debian.org/1081561
Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-45411
---
 src/Extension/CoreExtension.php | 11 ++++-------
 tests/Extension/CoreTest.php    | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 42 insertions(+), 7 deletions(-)

diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php
index f99adda..50c4109 100644
--- a/src/Extension/CoreExtension.php
+++ b/src/Extension/CoreExtension.php
@@ -1325,13 +1325,6 @@ function twig_include(Environment $env, $context, $template, $variables = [], $w
         if (!$alreadySandboxed = $sandbox->isSandboxed()) {
             $sandbox->enableSandbox();
         }
-
-        foreach ((\is_array($template) ? $template : [$template]) as $name) {
-            // if a Template instance is passed, it might have been instantiated outside of a sandbox, check security
-            if ($name instanceof TemplateWrapper || $name instanceof Template) {
-                $name->unwrap()->checkSecurity();
-            }
-        }
     }
 
     try {
@@ -1344,6 +1337,10 @@ function twig_include(Environment $env, $context, $template, $variables = [], $w
             }
         }
 
+        if ($isSandboxed && $loaded) {
+            $loaded->unwrap()->checkSecurity();
+        }
+
         return $loaded ? $loaded->render($variables) : '';
     } finally {
         if ($isSandboxed && !$alreadySandboxed) {
diff --git a/tests/Extension/CoreTest.php b/tests/Extension/CoreTest.php
index 29a799b..82c1ade 100644
--- a/tests/Extension/CoreTest.php
+++ b/tests/Extension/CoreTest.php
@@ -14,7 +14,11 @@ namespace Twig\Tests\Extension;
 use PHPUnit\Framework\TestCase;
 use Twig\Environment;
 use Twig\Error\RuntimeError;
+use Twig\Extension\SandboxExtension;
+use Twig\Loader\ArrayLoader;
 use Twig\Loader\LoaderInterface;
+use Twig\Sandbox\SecurityError;
+use Twig\Sandbox\SecurityPolicy;
 
 class CoreTest extends TestCase
 {
@@ -326,6 +330,40 @@ class CoreTest extends TestCase
             [1, 42, "\x00\x34\x32"],
         ];
     }
+
+    public function testSandboxedInclude()
+    {
+        $twig = new Environment(new ArrayLoader([
+            'index' => '{{ include("included", sandboxed=true) }}',
+            'included' => '{{ "included"|e }}',
+        ]));
+        $policy = new SecurityPolicy([], [], [], [], ['include']);
+        $sandbox = new SandboxExtension($policy, false);
+        $twig->addExtension($sandbox);
+
+        // We expect a compile error
+        $this->expectException(SecurityError::class);
+        $twig->render('index');
+    }
+
+    public function testSandboxedIncludeWithPreloadedTemplate()
+    {
+        $twig = new Environment(new ArrayLoader([
+            'index' => '{{ include("included", sandboxed=true) }}',
+            'included' => '{{ "included"|e }}',
+        ]));
+        $policy = new SecurityPolicy([], [], [], [], ['include']);
+        $sandbox = new SandboxExtension($policy, false);
+        $twig->addExtension($sandbox);
+
+        // The template is loaded without the sandbox enabled
+        // so, no compile error
+        $twig->load('included');
+
+        // We expect a runtime error
+        $this->expectException(SecurityError::class);
+        $twig->render('index');
+    }
 }
 
 final class CoreTestIteratorAggregate implements \IteratorAggregate
