diff --git a/CHANGELOG b/CHANGELOG index d73cc83cad7..f66d5842899 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ # 3.3.8 (2022-XX-XX) -* n/a + * Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter # 3.3.7 (2022-01-03) diff --git a/src/Extension/CoreExtension.php b/src/Extension/CoreExtension.php index c2c04e95ef4..88cd7545842 100644 --- a/src/Extension/CoreExtension.php +++ b/src/Extension/CoreExtension.php @@ -201,7 +201,7 @@ public function getFilters(): array // array helpers new TwigFilter('join', 'twig_join_filter'), new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), - new TwigFilter('sort', 'twig_sort_filter'), + new TwigFilter('sort', 'twig_sort_filter', ['needs_environment' => true]), new TwigFilter('merge', 'twig_array_merge'), new TwigFilter('batch', 'twig_array_batch'), new TwigFilter('column', 'twig_array_column'), @@ -887,7 +887,7 @@ function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) * * @return array */ -function twig_sort_filter($array, $arrow = null) +function twig_sort_filter(Environment $env, $array, $arrow = null) { if ($array instanceof \Traversable) { $array = iterator_to_array($array); @@ -896,6 +896,8 @@ function twig_sort_filter($array, $arrow = null) } if (null !== $arrow) { + twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter'); + uasort($array, $arrow); } else { asort($array); @@ -1639,9 +1641,7 @@ function twig_array_filter(Environment $env, $array, $arrow) throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); } - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.'); - } + twig_check_arrow_in_sandbox($env, $arrow, 'filter', 'filter'); if (\is_array($array)) { return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); @@ -1653,9 +1653,7 @@ function twig_array_filter(Environment $env, $array, $arrow) function twig_array_map(Environment $env, $array, $arrow) { - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError('The callable passed to the "map" filter must be a Closure in sandbox mode.'); - } + twig_check_arrow_in_sandbox($env, $arrow, 'map', 'filter'); $r = []; foreach ($array as $k => $v) { @@ -1667,9 +1665,7 @@ function twig_array_map(Environment $env, $array, $arrow) function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) { - if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { - throw new RuntimeError('The callable passed to the "reduce" filter must be a Closure in sandbox mode.'); - } + twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); if (!\is_array($array)) { if (!$array instanceof \Traversable) { @@ -1681,4 +1677,11 @@ function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) return array_reduce($array, $arrow, $initial); } + +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +{ + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); + } +} } diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index 0d9bc0afed7..e365da63280 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -390,7 +390,7 @@ public function testSandboxDisabledAfterIncludeFunctionError() public function testSandboxWithNoClosureFilter() { $this->expectException('\Twig\Error\RuntimeError'); - $this->expectExceptionMessage('The callable passed to "filter" filter must be a Closure in sandbox mode in "index" at line 1.'); + $this->expectExceptionMessage('The callable passed to the "filter" filter must be a Closure in sandbox mode in "index" at line 1.'); $twig = $this->getEnvironment(true, ['autoescape' => 'html'], ['index' => <<