XSS vulnerability in oro/platform
Description
OroPlatform email template preview is vulnerable to stored XSS, allowing attackers with edit permissions to execute arbitrary JavaScript when a victim previews the template.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OroPlatform email template preview is vulnerable to stored XSS, allowing attackers with edit permissions to execute arbitrary JavaScript when a victim previews the template.
Vulnerability
The email template preview functionality in OroPlatform is vulnerable to stored cross-site scripting (XSS). An attacker with permission to create or edit email templates can inject malicious JavaScript into the template content. When a user with sufficient privileges previews the template, the payload executes. The vulnerability exists in all versions prior to the commit that introduced sanitization (2a089c9) [1][2][3].
Exploitation
An attacker must be authenticated and have the 'create' or 'edit' permission on email templates. They craft an email template containing a malicious XSS payload. The attacker then needs to convince another user (e.g., an administrator) to preview that template. The payload executes in the victim's browser within the context of the OroPlatform application [1][3].
Impact
Successful exploitation allows the attacker to execute arbitrary JavaScript in the victim's session. This can lead to data theft, session hijacking, or unauthorized actions performed on behalf of the victim. The impact is on confidentiality and integrity, potentially escalating the attacker's privileges to those of the victim [1][3].
Mitigation
The fix is available in commit 2a089c9 which introduces HTML sanitization for email template content before rendering. Users are advised to upgrade to a version containing this fix as soon as possible. No workarounds are available [2][3]. The vulnerability is not listed on the CISA Known Exploited Vulnerabilities (KEV) catalog.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
oro/platformPackagist | >= 3.1.0, < 3.1.21 | 3.1.21 |
oro/platformPackagist | >= 4.1.0, < 4.1.14 | 4.1.14 |
oro/platformPackagist | >= 4.2.0, < 4.2.8 | 4.2.8 |
Affected products
2- oroinc/platformv5Range: >= 3.1.0, < 3.1.21
Patches
12a089c971fc7BAP-20848: Unwanted script execution possible in email template preview (#31379)
3 files changed · +50 −48
src/Oro/Bundle/EmailBundle/Provider/EmailRenderer.php+16 −1 modified@@ -7,6 +7,7 @@ use Oro\Bundle\EntityBundle\Twig\Sandbox\TemplateRenderer; use Oro\Bundle\EntityBundle\Twig\Sandbox\TemplateRendererConfigProviderInterface; use Oro\Bundle\EntityBundle\Twig\Sandbox\VariableProcessorRegistry; +use Oro\Bundle\UIBundle\Tools\HtmlTagHelper; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; @@ -20,6 +21,9 @@ class EmailRenderer extends TemplateRenderer /** @var TranslatorInterface */ private $translator; + /** @var HtmlTagHelper|null */ + private $htmlTagHelper; + public function __construct( Environment $environment, TemplateRendererConfigProviderInterface $configProvider, @@ -31,6 +35,11 @@ public function __construct( $this->translator = $translator; } + public function setHtmlTagHelper(HtmlTagHelper $htmlTagHelper): void + { + $this->htmlTagHelper = $htmlTagHelper; + } + /** * Compiles the given email template. * @@ -61,8 +70,14 @@ public function compilePreview(EmailTemplateInterface $template): string { $this->ensureSandboxConfigured(); + if ($this->htmlTagHelper) { + $content = $this->htmlTagHelper->sanitize($template->getContent(), 'default', false); + } else { + $content = $template->getContent(); + } + return $this->environment - ->createTemplate('{% verbatim %}' . $template->getContent() . '{% endverbatim %}') + ->createTemplate('{% verbatim %}' . $content . '{% endverbatim %}') ->render(); }
src/Oro/Bundle/EmailBundle/Resources/config/services.yml+1 −0 modified@@ -338,6 +338,7 @@ services: - '@Doctrine\Inflector\Inflector' calls: - [addSystemVariableDefaultFilter, ['userSignature', 'oro_html_sanitize']] + - [setHtmlTagHelper, ['@oro_ui.html_tag_helper']] lazy: true oro_email.email_renderer_configuration:
src/Oro/Bundle/EmailBundle/Tests/Unit/Provider/EmailRendererTest.php+33 −47 modified@@ -22,22 +22,9 @@ class EmailRendererTest extends \PHPUnit\Framework\TestCase { - private const ENTITY_VARIABLE_TEMPLATE = - '{% if %val% is defined %}' - . '{{ _entity_var("%name%", %val%, %parent%) }}' - . '{% else %}' - . '{{ "oro.email.variable.not.found" }}' - . '{% endif %}'; - /** @var TemplateRendererConfigProviderInterface|\PHPUnit\Framework\MockObject\MockObject */ private $configProvider; - /** @var VariableProcessorRegistry|\PHPUnit\Framework\MockObject\MockObject */ - private $variablesProcessorRegistry; - - /** @var TranslatorInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $translation; - /** @var ContainerInterface|\PHPUnit\Framework\MockObject\MockObject */ private $container; @@ -46,27 +33,38 @@ class EmailRendererTest extends \PHPUnit\Framework\TestCase protected function setUp(): void { - $environment = new Environment(new ArrayLoader(), ['strict_variables' => true]); $this->configProvider = $this->createMock(TemplateRendererConfigProviderInterface::class); - $this->variablesProcessorRegistry = $this->createMock(VariableProcessorRegistry::class); - $this->translation = $this->createMock(TranslatorInterface::class); $this->container = $this->createMock(ContainerInterface::class); - $this->translation->expects($this->any()) + $variablesProcessorRegistry = $this->createMock(VariableProcessorRegistry::class); + + $htmlTagHelper = $this->createMock(HtmlTagHelper::class); + $htmlTagHelper->expects(self::any()) + ->method('sanitize') + ->with(self::isType(\PHPUnit\Framework\Constraint\IsType::TYPE_STRING), 'default', false) + ->willReturnCallback(static function (string $content) { + return $content . '(sanitized)'; + }); + + $translation = $this->createMock(TranslatorInterface::class); + $translation->expects(self::any()) ->method('trans') ->willReturnArgument(0); + $environment = new Environment(new ArrayLoader(), ['strict_variables' => true]); $environment->addExtension(new SandboxExtension(new SecurityPolicy())); $environment->addExtension(new HttpKernelExtension()); $environment->addExtension(new HtmlTagExtension($this->container)); $this->renderer = new EmailRenderer( $environment, $this->configProvider, - $this->variablesProcessorRegistry, - $this->translation, + $variablesProcessorRegistry, + $translation, (new InflectorFactory())->build() ); + + $this->renderer->setHtmlTagHelper($htmlTagHelper); } private function getEmailTemplate(string $content, string $subject = ''): EmailTemplateInterface @@ -78,37 +76,25 @@ private function getEmailTemplate(string $content, string $subject = ''): EmailT return $emailTemplate; } - private function getEntityVariableTemplate(string $propertyName, string $path, string $parentPath): string - { - return strtr( - self::ENTITY_VARIABLE_TEMPLATE, - [ - '%name%' => $propertyName, - '%val%' => $path, - '%parent%' => $parentPath, - ] - ); - } - private function expectVariables(array $entityVariableProcessors = [], array $systemVariableValues = []): void { $entityVariableProcessorsMap = []; foreach ($entityVariableProcessors as $entityClass => $processors) { $entityVariableProcessorsMap[] = [$entityClass, $processors]; } - $this->configProvider->expects($this->any()) + $this->configProvider->expects(self::any()) ->method('getEntityVariableProcessors') ->willReturnMap($entityVariableProcessorsMap); - $this->configProvider->expects($this->any()) + $this->configProvider->expects(self::any()) ->method('getSystemVariableValues') ->willReturn($systemVariableValues); } - public function testCompilePreview() + public function testCompilePreview(): void { $entity = new TestEntityForVariableProvider(); - $this->configProvider->expects($this->any()) + $this->configProvider->expects(self::any()) ->method('getConfiguration') ->willReturn([ 'properties' => [], @@ -123,10 +109,10 @@ public function testCompilePreview() $emailTemplate->setContent($template); $emailTemplate->setSubject(''); - $this->assertSame($template, $this->renderer->compilePreview($emailTemplate)); + self::assertSame($template.'(sanitized)', $this->renderer->compilePreview($emailTemplate)); } - public function testCompileMessage() + public function testCompileMessage(): void { $entity = new TestEntityForVariableProvider(); $entity->setField1('Test'); @@ -144,7 +130,7 @@ public function testCompileMessage() . ' {{ system.testVar }}' . ' N/A'; - $this->configProvider->expects($this->any()) + $this->configProvider->expects(self::any()) ->method('getConfiguration') ->willReturn([ 'properties' => [], @@ -155,7 +141,7 @@ public function testCompileMessage() $this->expectVariables($entityVariableProcessors, $systemVars); $htmlTagHelper = $this->createMock(HtmlTagHelper::class); - $this->container->expects($this->once()) + $this->container->expects(self::once()) ->method('get') ->with('oro_ui.html_tag_helper') ->willReturn($htmlTagHelper); @@ -169,13 +155,13 @@ public function testCompileMessage() $result = $this->renderer->compileMessage($emailTemplate, $templateParams); $expectedContent = 'test <a href="http://example.com">test</a> 2 test_system N/A'; - $this->assertIsArray($result); - $this->assertCount(2, $result); - $this->assertSame($subject, $result[0]); - $this->assertSame($expectedContent, $result[1]); + self::assertIsArray($result); + self::assertCount(2, $result); + self::assertSame($subject, $result[0]); + self::assertSame($expectedContent, $result[1]); } - public function testRenderTemplate() + public function testRenderTemplate(): void { $template = 'test ' . "\n" @@ -184,7 +170,7 @@ public function testRenderTemplate() . '{{ system.currentDate }}'; $entity = new TestEntityForVariableProvider(); - $this->configProvider->expects($this->any()) + $this->configProvider->expects(self::any()) ->method('getConfiguration') ->willReturn([ 'properties' => [], @@ -197,7 +183,7 @@ public function testRenderTemplate() ], ['currentDate' => '10-12-2019']); $htmlTagHelper = $this->createMock(HtmlTagHelper::class); - $this->container->expects($this->once()) + $this->container->expects(self::once()) ->method('get') ->with('oro_ui.html_tag_helper') ->willReturn($htmlTagHelper); @@ -208,6 +194,6 @@ public function testRenderTemplate() 'test ' . "\n" . '10-12-2019'; - $this->assertSame($expectedRenderedResult, $result); + self::assertSame($expectedRenderedResult, $result); } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-qv7g-j98v-8pp7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-41236ghsaADVISORY
- github.com/oroinc/platform/commit/2a089c971fc70bc63baf8770d29ee515ce5a415aghsax_refsource_MISCWEB
- github.com/oroinc/platform/security/advisories/GHSA-qv7g-j98v-8pp7ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.