Skip to content
Permalink
Browse files Browse the repository at this point in the history
BAP-20848: Unwanted script execution possible in email template previ…
…ew (#31379)

- added preview sanitizing in EmailRenderer
  • Loading branch information
webevt committed Dec 20, 2021
1 parent d9d0210 commit 2a089c9
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 48 deletions.
17 changes: 16 additions & 1 deletion src/Oro/Bundle/EmailBundle/Provider/EmailRenderer.php
Expand Up @@ -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;

Expand All @@ -20,6 +21,9 @@ class EmailRenderer extends TemplateRenderer
/** @var TranslatorInterface */
private $translator;

/** @var HtmlTagHelper|null */
private $htmlTagHelper;

public function __construct(
Environment $environment,
TemplateRendererConfigProviderInterface $configProvider,
Expand All @@ -31,6 +35,11 @@ public function __construct(
$this->translator = $translator;
}

public function setHtmlTagHelper(HtmlTagHelper $htmlTagHelper): void
{
$this->htmlTagHelper = $htmlTagHelper;
}

/**
* Compiles the given email template.
*
Expand Down Expand Up @@ -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();
}

Expand Down
1 change: 1 addition & 0 deletions src/Oro/Bundle/EmailBundle/Resources/config/services.yml
Expand Up @@ -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:
Expand Down
Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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' => [],
Expand All @@ -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');
Expand All @@ -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' => [],
Expand All @@ -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);
Expand All @@ -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"
Expand All @@ -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' => [],
Expand All @@ -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);
Expand All @@ -208,6 +194,6 @@ public function testRenderTemplate()
'test '
. "\n"
. '10-12-2019';
$this->assertSame($expectedRenderedResult, $result);
self::assertSame($expectedRenderedResult, $result);
}
}

0 comments on commit 2a089c9

Please sign in to comment.