diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md
index 5a261d439826d..7ef838934633d 100644
--- a/CHANGELOG-4.4.md
+++ b/CHANGELOG-4.4.md
@@ -7,6 +7,10 @@ in 4.4 minor versions.
To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash
To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1
+* 4.4.51 (2023-11-10)
+
+ * security #cve-2023-46734 [TwigBridge] Ensure CodeExtension's filters properly escape their input (nicolas-grekas, GromNaN)
+
* 4.4.50 (2023-02-01)
* security #cve-2022-24895 [Security/Http] Remove CSRF tokens from storage on successful login (nicolas-grekas)
diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php
index eeea01d84532e..6f50d5a578d07 100644
--- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php
@@ -48,8 +48,8 @@ public function __construct($fileLinkFormat, string $projectDir, string $charset
public function getFilters()
{
return [
- new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html']]),
- new TwigFilter('abbr_method', [$this, 'abbrMethod'], ['is_safe' => ['html']]),
+ new TwigFilter('abbr_class', [$this, 'abbrClass'], ['is_safe' => ['html'], 'pre_escape' => 'html']),
+ new TwigFilter('abbr_method', [$this, 'abbrMethod'], ['is_safe' => ['html'], 'pre_escape' => 'html']),
new TwigFilter('format_args', [$this, 'formatArgs'], ['is_safe' => ['html']]),
new TwigFilter('format_args_as_text', [$this, 'formatArgsAsText']),
new TwigFilter('file_excerpt', [$this, 'fileExcerpt'], ['is_safe' => ['html']]),
@@ -95,22 +95,23 @@ public function formatArgs($args)
$result = [];
foreach ($args as $key => $item) {
if ('object' === $item[0]) {
+ $item[1] = htmlspecialchars($item[1], \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset);
$parts = explode('\\', $item[1]);
$short = array_pop($parts);
$formattedValue = sprintf('object(%s)', $item[1], $short);
} elseif ('array' === $item[0]) {
- $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
+ $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset));
} elseif ('null' === $item[0]) {
$formattedValue = 'null';
} elseif ('boolean' === $item[0]) {
- $formattedValue = ''.strtolower(var_export($item[1], true)).'';
+ $formattedValue = ''.strtolower(htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)).'';
} elseif ('resource' === $item[0]) {
$formattedValue = 'resource';
} else {
$formattedValue = str_replace("\n", '', htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset));
}
- $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue);
+ $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", htmlspecialchars($key, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $formattedValue);
}
return implode(', ', $result);
@@ -178,13 +179,17 @@ public function fileExcerpt($file, $line, $srcContext = 3)
public function formatFile($file, $line, $text = null)
{
$file = trim($file);
+ $line = (int) $line;
if (null === $text) {
- $text = $file;
- if (null !== $rel = $this->getFileRelative($text)) {
- $rel = explode('/', $rel, 2);
- $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? ''));
+ if (null !== $rel = $this->getFileRelative($file)) {
+ $rel = explode('/', htmlspecialchars($rel, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), 2);
+ $text = sprintf('%s%s', htmlspecialchars($this->projectDir, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $rel[0], '/'.($rel[1] ?? ''));
+ } else {
+ $text = htmlspecialchars($file, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset);
}
+ } else {
+ $text = htmlspecialchars($text, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset);
}
if (0 < $line) {
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php
index 874faeeb99955..fc0891e118810 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php
@@ -14,6 +14,8 @@
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Twig\Extension\CodeExtension;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
+use Twig\Environment;
+use Twig\Loader\ArrayLoader;
class CodeExtensionTest extends TestCase
{
@@ -28,38 +30,123 @@ public function testFileRelative()
$this->assertEquals('file.txt', $this->getExtension()->getFileRelative(\DIRECTORY_SEPARATOR.'project'.\DIRECTORY_SEPARATOR.'file.txt'));
}
- /**
- * @dataProvider getClassNameProvider
- */
- public function testGettingClassAbbreviation($class, $abbr)
+ public function testClassAbbreviationIntegration()
{
- $this->assertEquals($this->getExtension()->abbrClass($class), $abbr);
+ $data = [
+ 'fqcn' => 'F\Q\N\Foo',
+ 'xss' => '