CVE-2022-39365: Improper Control of Generation of Code ('Code Injection') (CWE-94)
value: Pimcore is an open source data and experience management platform. Prior to version 10.5.9, the user controlled twig templates rendering in `Pimcore/Mail` & `ClassDefinition\Layout\Text` is vulnerable to server-side template injection, which could lead to remote code execution. Version 10.5.9 contains a patch for this issue. As a workaround, one may apply the patch manually.
CWE description: The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.
CVSS v3 base score: 9.8
Published: 2022-10-27T15:15Z
Repository: https://github.com/pimcore/pimcore
Language: PHP
Files changed: bundles/CoreBundle/DependencyInjection/Configuration.php, bundles/CoreBundle/DependencyInjection/PimcoreCoreExtension.php, bundles/CoreBundle/Resources/config/pimcore/default.yaml, bundles/CoreBundle/Resources/config/templating_twig.yaml, doc/Development_Documentation/05_Objects/01_Object_Classes/03_Layout_Elements/01_Dynamic_Text_Labels.md, doc/Development_Documentation/19_Development_Tools_and_Details/25_Email_Framework/README.md, doc/Development_Documentation/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md, lib/Mail.php, lib/Templating/TwigDefaultDelegatingEngine.php, lib/Twig/Sandbox/SecurityPolicy.php, models/DataObject/ClassDefinition/Layout/Text.php
Commit date: 2022-10-27 15:09:23 +0200
Commit message: [Mail] Renderer email content twig templates in a sandbox (#13347)

* [Mail] Renderer email content twig templates in a sandbox

* [Mail] Renderer email content twig templates in a sandbox

* [Mail] Renderer email content twig templates in a sandbox

* [Mail] Renderer email content twig templates in a sandbox

* [Mail] Renderer email content twig templates in a sandbox

* [Mail] Renderer email content twig templates in a sandbox

* Apply suggestions from code review

Co-authored-by: Sebastian Blank <blank@data-factory.net>

* Update lib/Templating/TwigDefaultDelegatingEngine.php

Co-authored-by: Jacob Dreesen <j.dreesen@neusta.de>

* [Twig] Renderer user controlled twig templates in a sandbox - review changes #13347

* [Twig] Renderer user controlled twig templates in a sandbox - use custom security policy to whitelist object properties and methods execution by default #13347

* [Twig] Renderer user controlled twig templates in a sandbox - review changes #13347

* [Twig] Renderer user controlled twig templates in a sandbox - fix phpstan #13347

* [Twig] Renderer user controlled twig templates in a sandbox - fix service definition #13347

* [Twig] Renderer user controlled twig templates in a sandbox - docs typo #13347

Co-authored-by: Sebastian Blank <blank@data-factory.net>
Co-authored-by: Jacob Dreesen <j.dreesen@neusta.de>
Diff: diff --git a/bundles/CoreBundle/DependencyInjection/Configuration.php b/bundles/CoreBundle/DependencyInjection/Configuration.php
index c0a8a8a603db..4ca2f9a4f421 100644
--- a/bundles/CoreBundle/DependencyInjection/Configuration.php
+++ b/bundles/CoreBundle/DependencyInjection/Configuration.php
@@ -178,10 +178,11 @@ final class Configuration implements ConfigurationInterface
         $this->addStaticRoutesNode($rootNode);
         $this->addPerspectivesNode($rootNode);
         $this->addCustomViewsNode($rootNode);
         $this->addGlossaryNode($rootNode);
         $this->buildRedirectsStatusCodes($rootNode);
+        $this->addTemplatingEngineNode($rootNode);
 
         return $treeBuilder;
     }
 
     /**
@@ -2274,6 +2275,36 @@ final class Configuration implements ConfigurationInterface
                     ->children()
                         ->arrayNode('blocked_tags')
                             ->useAttributeAsKey('name')
                             ->prototype('scalar');
     }
+
+    private function addTemplatingEngineNode(ArrayNodeDefinition $rootNode): void
+    {
+        $rootNode
+            ->children()
+            ->arrayNode('templating_engine')
+            ->addDefaultsIfNotSet()
+            ->children()
+                ->arrayNode('twig')
+                ->addDefaultsIfNotSet()
+                ->children()
+                    ->arrayNode('sandbox_security_policy')
+                        ->info('Whitelist tags, filters & functions for evaluating twig 
+                        templates in a sandbox environment e.g. used by Mailer & Text layout component.')
+                        ->children()
+                            ->arrayNode('tags')
+                                ->scalarPrototype()->end()
+                            ->end()
+                            ->arrayNode('filters')
+                                ->scalarPrototype()->end()
+                            ->end()
+                            ->arrayNode('functions')
+                                ->scalarPrototype()->end()
+                            ->end()
+                        ->end()
+                    ->end()
+                ->end()
+            ->end()
+        ->end();
+    }
 }
diff --git a/bundles/CoreBundle/DependencyInjection/PimcoreCoreExtension.php b/bundles/CoreBundle/DependencyInjection/PimcoreCoreExtension.php
index 3432636547ae..7fffc7dcfb17 100644
--- a/bundles/CoreBundle/DependencyInjection/PimcoreCoreExtension.php
+++ b/bundles/CoreBundle/DependencyInjection/PimcoreCoreExtension.php
@@ -93,10 +93,15 @@ final class PimcoreCoreExtension extends ConfigurableExtension implements Prepen
 
         $container->setParameter('pimcore.documents.default_controller', $config['documents']['default_controller']);
         $container->setParameter('pimcore.documents.web_to_print.default_controller_print_page', $config['documents']['web_to_print']['default_controller_print_page']);
         $container->setParameter('pimcore.documents.web_to_print.default_controller_print_container', $config['documents']['web_to_print']['default_controller_print_container']);
 
+        //twig security policy whitelist config
+        $container->setParameter('pimcore.templating.twig.sandbox_security_policy.tags', $config['templating_engine']['twig']['sandbox_security_policy']['tags']);
+        $container->setParameter('pimcore.templating.twig.sandbox_security_policy.filters', $config['templating_engine']['twig']['sandbox_security_policy']['filters']);
+        $container->setParameter('pimcore.templating.twig.sandbox_security_policy.functions', $config['templating_engine']['twig']['sandbox_security_policy']['functions']);
+
         // register pimcore config on container
         // TODO is this bad practice?
         // TODO only extract what we need as parameter?
         $container->setParameter('pimcore.config', $config);
 
diff --git a/bundles/CoreBundle/Resources/config/pimcore/default.yaml b/bundles/CoreBundle/Resources/config/pimcore/default.yaml
index 238b140ec2cb..063a3578e1d1 100644
--- a/bundles/CoreBundle/Resources/config/pimcore/default.yaml
+++ b/bundles/CoreBundle/Resources/config/pimcore/default.yaml
@@ -291,10 +291,16 @@ pimcore:
         blocked_tags: [
               'a', 'script', 'style', 'code', 'pre', 'textarea', 'acronym',
               'abbr', 'option', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
           ]
 
+    templating_engine:
+        twig:
+            sandbox_security_policy:
+                tags: ['set']
+                filters: ['escape', 'trans']
+                functions: ['path', 'asset']
 presta_sitemap:
     # do not add properties by default
     defaults:
         lastmod: ~
         priority: ~
diff --git a/bundles/CoreBundle/Resources/config/templating_twig.yaml b/bundles/CoreBundle/Resources/config/templating_twig.yaml
index 5655bfd30405..59e02698e252 100644
--- a/bundles/CoreBundle/Resources/config/templating_twig.yaml
+++ b/bundles/CoreBundle/Resources/config/templating_twig.yaml
@@ -82,6 +82,17 @@ services:
     # content was added (e.g. headTitle)
     Twig\DeferredExtension\DeferredExtension: ~
 
     Twig\Extra\String\StringExtension: ~
 
+    Pimcore\Twig\Sandbox\SecurityPolicy:
+        arguments:
+            $allowedTags: '%pimcore.templating.twig.sandbox_security_policy.tags%'
+            $allowedFilters: '%pimcore.templating.twig.sandbox_security_policy.filters%'
+            $allowedFunctions: '%pimcore.templating.twig.sandbox_security_policy.functions%'
+
+    Twig\Extension\SandboxExtension:
+        class: Twig\Extension\SandboxExtension
+        arguments:
+            $policy: '@Pimcore\Twig\Sandbox\SecurityPolicy'
+
     Pimcore\Twig\Extension\AdminExtension: ~
diff --git a/doc/Development_Documentation/05_Objects/01_Object_Classes/03_Layout_Elements/01_Dynamic_Text_Labels.md b/doc/Development_Documentation/05_Objects/01_Object_Classes/03_Layout_Elements/01_Dynamic_Text_Labels.md
index d5126a7e9795..91d535b93766 100644
--- a/doc/Development_Documentation/05_Objects/01_Object_Classes/03_Layout_Elements/01_Dynamic_Text_Labels.md
+++ b/doc/Development_Documentation/05_Objects/01_Object_Classes/03_Layout_Elements/01_Dynamic_Text_Labels.md
@@ -65,5 +65,19 @@ Following variables are available in twig context:
 Here is an example of Twig content in htmleditor source edit mode:
 
 ![Template Class Definition](../../../img/dynamic_textlabel_3.png)
 
 ![Template editmode](../../../img/dynamic_textlabel_4.png)
+
+### Sandbox Restrictions
+Dynamic Text renders user controlled twig templates in a sandbox with restrictive
+security policies for tags, filters & functions. Please use following configuration to allow more in template rendering:
+
+```yaml
+    pimcore:
+          templating_engine:
+              twig:
+                sandbox_security_policy:
+                  tags: ['if']
+                  filters: ['upper']
+                  functions: ['include', 'path']
+```
\ No newline at end of file
diff --git a/doc/Development_Documentation/19_Development_Tools_and_Details/25_Email_Framework/README.md b/doc/Development_Documentation/19_Development_Tools_and_Details/25_Email_Framework/README.md
index 692a3d0cb6cd..c18ae513ed4e 100644
--- a/doc/Development_Documentation/19_Development_Tools_and_Details/25_Email_Framework/README.md
+++ b/doc/Development_Documentation/19_Development_Tools_and_Details/25_Email_Framework/README.md
@@ -96,5 +96,19 @@ $mail = new \Pimcore\Mail();
 $mail->to('example@pimcore.org');
 $mail->bcc("bcc@pimcore.org");
 $mail->html("<b>some</b> rich text");
 $mail->send();
 ```
+
+## Sandbox Restrictions
+Sending mails renders user controlled twig templates in a sandbox with restrictive 
+security policies for tags, filters & functions. Please use following configuration to allow more in template rendering:
+
+```yaml
+    pimcore:
+          templating_engine:
+              twig:
+                sandbox_security_policy:
+                  tags: ['if']
+                  filters: ['upper']
+                  functions: ['include', 'path']
+```
\ No newline at end of file
diff --git a/doc/Development_Documentation/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md b/doc/Development_Documentation/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md
index 9d7a6e72ea91..2eb6d2c7967d 100644
--- a/doc/Development_Documentation/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md
+++ b/doc/Development_Documentation/23_Installation_and_Upgrade/09_Upgrade_Notes/README.md
@@ -1,8 +1,20 @@
 # Upgrade Notes
 
 ## 10.5.8
+- [Twig] Sending mails and Dataobject Text Layouts, which allow rendering user controlled twig templates are now executed in a sandbox with restrictive security policies for tags, filters, functions.
+         Please use following configuration to allow more in template rendering:
+  ```yaml
+  pimcore:
+        templating_engine:
+            twig:
+              sandbox_security_policy:
+                tags: ['if']
+                filters: ['upper']
+                functions: ['include', 'path', 'range']
+  ```
+
 - [Nginx] Static pages nginx config has been updated to fix the issue for home static page generation. please adapt the following configuration:
 ```nginx
 map $args $static_page_root {
     default                                 /var/tmp/pages;
     "~*(^|&)pimcore_editmode=true(&|$)"     /var/nonexistent;
diff --git a/lib/Mail.php b/lib/Mail.php
index 561a88140101..6228ca0ed9f1 100644
--- a/lib/Mail.php
+++ b/lib/Mail.php
@@ -26,10 +26,11 @@ use Symfony\Component\Mailer\MailerInterface;
 use Symfony\Component\Mime\Address;
 use Symfony\Component\Mime\Email;
 use Symfony\Component\Mime\Header\Headers;
 use Symfony\Component\Mime\Header\MailboxListHeader;
 use Symfony\Component\Mime\Part\AbstractPart;
+use Twig\Sandbox\SecurityError;
 
 class Mail extends Email
 {
     /**
      * @var bool
@@ -649,17 +650,26 @@ class Mail extends Email
     /**
      * @param string $string
      *
      * @return string
      */
-    private function renderParams(string $string): string
+    private function renderParams(string $string, string $context): string
     {
         $templatingEngine = \Pimcore::getContainer()->get('pimcore.templating.engine.delegating');
-        $twig = $templatingEngine->getTwigEnvironment();
-        $template = $twig->createTemplate($string);
-
-        return $template->render($this->getParams());
+        try {
+            $twig = $templatingEngine->getTwigEnvironment(true);
+            $template = $twig->createTemplate($string);
+
+            return $template->render($this->getParams());
+        } catch (SecurityError $e) {
+            Logger::err((string) $e);
+
+            throw new \Exception(sprintf("Failed rendering the %s: %s. Please check your twig sandbox security policy or contact the administrator.",
+            $context, substr($e->getMessage(),0, strpos($e->getMessage(), ' in "__string'))));
+        } finally {
+            $templatingEngine->disableSandboxExtensionFromTwigEnvironment();
+        }
     }
 
     /**
      * Renders the content (Twig) and returns the rendered subject
      *
@@ -674,11 +684,11 @@ class Mail extends Email
         if (!$subject && $this->getDocument()) {
             $subject = $this->getDocument()->getSubject();
         }
 
         if ($subject) {
-            return $this->renderParams($subject);
+            return $this->renderParams($subject, 'subject');
         }
 
         return '';
     }
 
@@ -705,11 +715,11 @@ class Mail extends Email
             }
         }
 
         $content = null;
         if ($html) {
-            $content = $this->renderParams($html);
+            $content = $this->renderParams($html, 'body');
 
             // modifying the content e.g set absolute urls...
             $content = MailHelper::embedAndModifyCss($content, $this->getDocument());
             $content = MailHelper::setAbsolutePaths($content, $this->getDocument(), $this->getHostUrl());
         }
@@ -729,11 +739,11 @@ class Mail extends Email
     {
         $text = $this->getTextBody();
 
         //if the content was manually set with $obj->text(); this content will be used
         if ($text) {
-            $content = $this->renderParams($text);
+            $content = $this->renderParams($text, 'body');
         } else {
             //creating text version from html email
             try {
                 $htmlContent = $this->getBodyHtmlRendered();
                 $html = new DomCrawler($htmlContent);
diff --git a/lib/Templating/TwigDefaultDelegatingEngine.php b/lib/Templating/TwigDefaultDelegatingEngine.php
index a313ac6245dd..4abd7b38082d 100644
--- a/lib/Templating/TwigDefaultDelegatingEngine.php
+++ b/lib/Templating/TwigDefaultDelegatingEngine.php
@@ -13,38 +13,32 @@
  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  */
 
 namespace Pimcore\Templating;
 
+use Pimcore\Config;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Templating\DelegatingEngine as BaseDelegatingEngine;
 use Symfony\Component\Templating\EngineInterface;
 use Twig\Environment;
+use Twig\Extension\SandboxExtension;
 
 /**
  * @internal
  */
 class TwigDefaultDelegatingEngine extends BaseDelegatingEngine
 {
-    /**
-     * @var Environment
-     */
-    protected $twig;
-
     /**
      * @var bool
      */
     protected $delegate = false;
 
     /**
-     * @param Environment $twig
      * @param EngineInterface[] $engines
      */
-    public function __construct(Environment $twig, array $engines = [])
+    public function __construct(protected Environment $twig, protected Config $config, array $engines = [])
     {
-        $this->twig = $twig;
-
         parent::__construct($engines);
     }
 
     /**
      * {@inheritdoc}
@@ -98,18 +92,28 @@ class TwigDefaultDelegatingEngine extends BaseDelegatingEngine
     public function isDelegate()
     {
         return $this->delegate;
     }
 
-    /**
-     * @return Environment
-     */
-    public function getTwigEnvironment(): Environment
+    public function getTwigEnvironment(bool $sandboxed = false): Environment
     {
+        if ($sandboxed) {
+            /** @var SandboxExtension $sandboxExtension */
+            $sandboxExtension = $this->twig->getExtension(SandboxExtension::class);
+            $sandboxExtension->enableSandbox();
+        }
+
         return $this->twig;
     }
 
+    public function disableSandboxExtensionFromTwigEnvironment(): void
+    {
+        /** @var SandboxExtension $sandboxExtension */
+        $sandboxExtension = $this->twig->getExtension(SandboxExtension::class);
+        $sandboxExtension->disableSandbox();
+    }
+
     /**
      * @param string $view
      * @param array $parameters
      * @param Response|null $response
      *
diff --git a/lib/Twig/Sandbox/SecurityPolicy.php b/lib/Twig/Sandbox/SecurityPolicy.php
new file mode 100644
index 000000000000..832e52302143
--- /dev/null
+++ b/lib/Twig/Sandbox/SecurityPolicy.php
@@ -0,0 +1,91 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * Pimcore
+ *
+ * This source file is available under two different licenses:
+ * - GNU General Public License version 3 (GPLv3)
+ * - Pimcore Commercial License (PCL)
+ * Full copyright and license information is available in
+ * LICENSE.md which is distributed with this source code.
+ *
+ *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
+ *  @license    http://www.pimcore.org/license     GPLv3 and PCL
+ */
+
+namespace Pimcore\Twig\Sandbox;
+
+use Twig\Sandbox\SecurityNotAllowedFilterError;
+use Twig\Sandbox\SecurityNotAllowedFunctionError;
+use Twig\Sandbox\SecurityNotAllowedTagError;
+use Twig\Sandbox\SecurityPolicyInterface;
+
+/**
+ * Note: Reused to disable checks on object methods and properties.
+ *
+ * Represents a security policy which need to be enforced when sandbox mode is enabled.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+final class SecurityPolicy implements SecurityPolicyInterface
+{
+    private array $allowedTags;
+    private array $allowedFilters;
+    private array $allowedFunctions;
+
+    public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedFunctions = [])
+    {
+        $this->allowedTags = $allowedTags;
+        $this->allowedFilters = $allowedFilters;
+        $this->allowedFunctions = $allowedFunctions;
+    }
+
+    public function setAllowedTags(array $tags)
+    {
+        $this->allowedTags = $tags;
+    }
+
+    public function setAllowedFilters(array $filters)
+    {
+        $this->allowedFilters = $filters;
+    }
+
+    public function setAllowedFunctions(array $functions)
+    {
+        $this->allowedFunctions = $functions;
+    }
+
+    public function checkSecurity($tags, $filters, $functions): void
+    {
+        foreach ($tags as $tag) {
+            if (!\in_array($tag, $this->allowedTags)) {
+                throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag);
+            }
+        }
+
+        foreach ($filters as $filter) {
+            if (!\in_array($filter, $this->allowedFilters)) {
+                throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter);
+            }
+        }
+
+        foreach ($functions as $function) {
+            //check if a function is allowed or a pimcore twig functions
+            if (!\in_array($function, $this->allowedFunctions) && !str_starts_with($function, 'pimcore_')) {
+                throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function);
+            }
+        }
+    }
+
+    public function checkMethodAllowed($obj, $method): void
+    {
+        //do not perform any checks
+    }
+
+    public function checkPropertyAllowed($obj, $method): void
+    {
+        //do not perform any checks
+    }
+}
diff --git a/models/DataObject/ClassDefinition/Layout/Text.php b/models/DataObject/ClassDefinition/Layout/Text.php
index c0c51dc42775..5887afb336d7 100644
--- a/models/DataObject/ClassDefinition/Layout/Text.php
+++ b/models/DataObject/ClassDefinition/Layout/Text.php
@@ -13,12 +13,14 @@
  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  */
 
 namespace Pimcore\Model\DataObject\ClassDefinition\Layout;
 
+use Pimcore\Logger;
 use Pimcore\Model;
 use Pimcore\Model\DataObject\Concrete;
+use Twig\Sandbox\SecurityError;
 
 class Text extends Model\DataObject\ClassDefinition\Layout implements Model\DataObject\ClassDefinition\Data\LayoutDefinitionEnrichmentInterface
 {
     /**
      * Static type of this element
@@ -141,16 +143,26 @@ class Text extends Model\DataObject\ClassDefinition\Layout implements Model\Data
             $result = $renderer->renderLayoutText($this->renderingData, $object, $context);
             $this->html = $result;
         }
 
         $templatingEngine = \Pimcore::getContainer()->get('pimcore.templating.engine.delegating');
-        $twig = $templatingEngine->getTwigEnvironment();
-        $template = $twig->createTemplate($this->html);
-        $this->html = $template->render(array_merge($context,
-            [
-                'object' => $object,
-            ]
-        ));
+        try {
+            $twig = $templatingEngine->getTwigEnvironment(true);
+            $template = $twig->createTemplate($this->html);
+            $this->html = $template->render(array_merge($context,
+                [
+                    'object' => $object,
+                ]
+            ));
+        } catch (SecurityError $e) {
+            Logger::err((string) $e);
+
+            $this->html = sprintf("<h2>Error</h2>Failed rendering the template: <b>%s</b>.
+                Please check your twig sandbox security policy or contact the administrator.",
+                substr($e->getMessage(),0, strpos($e->getMessage(), ' in "__string')));
+        } finally {
+            $templatingEngine->disableSandboxExtensionFromTwigEnvironment();
+        }
 
         return $this;
     }
 }

Vulnerable code:     private function renderParams(string $string): string
        $twig = $templatingEngine->getTwigEnvironment();
        $template = $twig->createTemplate($string);

        return $template->render($this->getParams());
            return $this->renderParams($subject);
            $content = $this->renderParams($html);
            $content = $this->renderParams($text);
    /**
     * @var Environment
     */
    protected $twig;

     * @param Environment $twig
    public function __construct(Environment $twig, array $engines = [])
        $this->twig = $twig;

    /**
     * @return Environment
     */
    public function getTwigEnvironment(): Environment
        $twig = $templatingEngine->getTwigEnvironment();
        $template = $twig->createTemplate($this->html);
        $this->html = $template->render(array_merge($context,
            [
                'object' => $object,
            ]
        ));
Fixed code:         $this->addTemplatingEngineNode($rootNode);

    private function addTemplatingEngineNode(ArrayNodeDefinition $rootNode): void
    {
        $rootNode
            ->children()
            ->arrayNode('templating_engine')
            ->addDefaultsIfNotSet()
            ->children()
                ->arrayNode('twig')
                ->addDefaultsIfNotSet()
                ->children()
                    ->arrayNode('sandbox_security_policy')
                        ->info('Whitelist tags, filters & functions for evaluating twig 
                        templates in a sandbox environment e.g. used by Mailer & Text layout component.')
                        ->children()
                            ->arrayNode('tags')
                                ->scalarPrototype()->end()
                            ->end()
                            ->arrayNode('filters')
                                ->scalarPrototype()->end()
                            ->end()
                            ->arrayNode('functions')
                                ->scalarPrototype()->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end()
        ->end();
    }
        //twig security policy whitelist config
        $container->setParameter('pimcore.templating.twig.sandbox_security_policy.tags', $config['templating_engine']['twig']['sandbox_security_policy']['tags']);
        $container->setParameter('pimcore.templating.twig.sandbox_security_policy.filters', $config['templating_engine']['twig']['sandbox_security_policy']['filters']);
        $container->setParameter('pimcore.templating.twig.sandbox_security_policy.functions', $config['templating_engine']['twig']['sandbox_security_policy']['functions']);

    templating_engine:
        twig:
            sandbox_security_policy:
                tags: ['set']
                filters: ['escape', 'trans']
                functions: ['path', 'asset']
    Pimcore\Twig\Sandbox\SecurityPolicy:
        arguments:
            $allowedTags: '%pimcore.templating.twig.sandbox_security_policy.tags%'
            $allowedFilters: '%pimcore.templating.twig.sandbox_security_policy.filters%'
            $allowedFunctions: '%pimcore.templating.twig.sandbox_security_policy.functions%'

    Twig\Extension\SandboxExtension:
        class: Twig\Extension\SandboxExtension
        arguments:
            $policy: '@Pimcore\Twig\Sandbox\SecurityPolicy'


### Sandbox Restrictions
Dynamic Text renders user controlled twig templates in a sandbox with restrictive
security policies for tags, filters & functions. Please use following configuration to allow more in template rendering:

```yaml
    pimcore:
          templating_engine:
              twig:
                sandbox_security_policy:
                  tags: ['if']
                  filters: ['upper']
                  functions: ['include', 'path']
```

## Sandbox Restrictions
Sending mails renders user controlled twig templates in a sandbox with restrictive 
security policies for tags, filters & functions. Please use following configuration to allow more in template rendering:

```yaml
    pimcore:
          templating_engine:
              twig:
                sandbox_security_policy:
                  tags: ['if']
                  filters: ['upper']
                  functions: ['include', 'path']
```
- [Twig] Sending mails and Dataobject Text Layouts, which allow rendering user controlled twig templates are now executed in a sandbox with restrictive security policies for tags, filters, functions.
         Please use following configuration to allow more in template rendering:
  ```yaml
  pimcore:
        templating_engine:
            twig:
              sandbox_security_policy:
                tags: ['if']
                filters: ['upper']
                functions: ['include', 'path', 'range']
  ```

use Twig\Sandbox\SecurityError;
    private function renderParams(string $string, string $context): string
        try {
            $twig = $templatingEngine->getTwigEnvironment(true);
            $template = $twig->createTemplate($string);

            return $template->render($this->getParams());
        } catch (SecurityError $e) {
            Logger::err((string) $e);

            throw new \Exception(sprintf("Failed rendering the %s: %s. Please check your twig sandbox security policy or contact the administrator.",
            $context, substr($e->getMessage(),0, strpos($e->getMessage(), ' in "__string'))));
        } finally {
            $templatingEngine->disableSandboxExtensionFromTwigEnvironment();
        }
            return $this->renderParams($subject, 'subject');
            $content = $this->renderParams($html, 'body');
            $content = $this->renderParams($text, 'body');
use Pimcore\Config;
use Twig\Extension\SandboxExtension;
    public function __construct(protected Environment $twig, protected Config $config, array $engines = [])
    public function getTwigEnvironment(bool $sandboxed = false): Environment
        if ($sandboxed) {
            /** @var SandboxExtension $sandboxExtension */
            $sandboxExtension = $this->twig->getExtension(SandboxExtension::class);
            $sandboxExtension->enableSandbox();
        }

    public function disableSandboxExtensionFromTwigEnvironment(): void
    {
        /** @var SandboxExtension $sandboxExtension */
        $sandboxExtension = $this->twig->getExtension(SandboxExtension::class);
        $sandboxExtension->disableSandbox();
    }

<?php

declare(strict_types=1);

/**
 * Pimcore
 *
 * This source file is available under two different licenses:
 * - GNU General Public License version 3 (GPLv3)
 * - Pimcore Commercial License (PCL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
 *  @license    http://www.pimcore.org/license     GPLv3 and PCL
 */

namespace Pimcore\Twig\Sandbox;

use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityPolicyInterface;

/**
 * Note: Reused to disable checks on object methods and properties.
 *
 * Represents a security policy which need to be enforced when sandbox mode is enabled.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class SecurityPolicy implements SecurityPolicyInterface
{
    private array $allowedTags;
    private array $allowedFilters;
    private array $allowedFunctions;

    public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedFunctions = [])
    {
        $this->allowedTags = $allowedTags;
        $this->allowedFilters = $allowedFilters;
        $this->allowedFunctions = $allowedFunctions;
    }

    public function setAllowedTags(array $tags)
    {
        $this->allowedTags = $tags;
    }

    public function setAllowedFilters(array $filters)
    {
        $this->allowedFilters = $filters;
    }

    public function setAllowedFunctions(array $functions)
    {
        $this->allowedFunctions = $functions;
    }

    public function checkSecurity($tags, $filters, $functions): void
    {
        foreach ($tags as $tag) {
            if (!\in_array($tag, $this->allowedTags)) {
                throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag);
            }
        }

        foreach ($filters as $filter) {
            if (!\in_array($filter, $this->allowedFilters)) {
                throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter);
            }
        }

        foreach ($functions as $function) {
            //check if a function is allowed or a pimcore twig functions
            if (!\in_array($function, $this->allowedFunctions) && !str_starts_with($function, 'pimcore_')) {
                throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function);
            }
        }
    }

    public function checkMethodAllowed($obj, $method): void
    {
        //do not perform any checks
    }

    public function checkPropertyAllowed($obj, $method): void
    {
        //do not perform any checks
    }
}
use Pimcore\Logger;
use Twig\Sandbox\SecurityError;
        try {
            $twig = $templatingEngine->getTwigEnvironment(true);
            $template = $twig->createTemplate($this->html);
            $this->html = $template->render(array_merge($context,
                [
                    'object' => $object,
                ]
            ));
        } catch (SecurityError $e) {
            Logger::err((string) $e);

            $this->html = sprintf("<h2>Error</h2>Failed rendering the template: <b>%s</b>.
                Please check your twig sandbox security policy or contact the administrator.",
                substr($e->getMessage(),0, strpos($e->getMessage(), ' in "__string')));
        } finally {
            $templatingEngine->disableSandboxExtensionFromTwigEnvironment();
        }