diff --git a/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php index 72388ce89..84c58e4be 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Twig/AppExtension.php @@ -165,16 +165,53 @@ class AppExtension extends AbstractExtension //since 2.7.7 3.0.2 3.1.0 N°4867 "Twig content not allowed" error when use the extkey widget search icon in the user portal //overwrite native twig filter : disable use of 'system' filter $filters[] = new TwigFilter('filter', function ($array, $arrow) { - if ($arrow == 'system'){ - return json_encode($array); + $ret = $this->SanitizeFilter($array, $arrow); + if ($ret !== false) { + return [$ret]; } $oEnv = new Environment(new FilesystemLoader()); - return twig_array_filter($oEnv, $array, $arrow); + return twig_array_filter($oEnv, $array, $arrow); + }); + $filters[] = new TwigFilter('map', function ($array, $arrow) { + $ret = $this->SanitizeFilter($array, $arrow); + if ($ret !== false) { + return [$ret]; + } + $oEnv = new Environment(new FilesystemLoader()); + return twig_array_map($oEnv, $array, $arrow); + }); + $filters[] = new TwigFilter('reduce', function ($array, $arrow, $initial = null) { + $ret = $this->SanitizeFilter($array, $arrow); + if ($ret !== false) { + return $ret; + } + // reduce return mixed results not only arrays + $oEnv = new Environment(new FilesystemLoader()); + return twig_array_reduce($oEnv, $array, $arrow, $initial); + }); + $filters[] = new TwigFilter('sort', function ($array, $arrow, $initial = null) { + $ret = $this->SanitizeFilter($array, $arrow); + if ($ret !== false) { + return $ret; + } + // reduce return mixed results not only arrays + $oEnv = new Environment(new FilesystemLoader()); + return twig_array_reduce($oEnv, $array, $arrow, $initial); }); return $filters; } + private function SanitizeFilter($array, $arrow) + { + if (is_string($arrow)) { + if (in_array(strtolower($arrow), ['system', 'exec', 'passthru', 'popen'])) { + return json_encode($array); + } + } + return false; + } + /** * @return array|\Twig\TwigFunction[]|\Twig\TwigFunction[] */ diff --git a/test/twig/TwigTest.php b/test/twig/TwigTest.php index 2f8a212f1..f522a08d9 100644 --- a/test/twig/TwigTest.php +++ b/test/twig/TwigTest.php @@ -7,6 +7,11 @@ use Twig\Loader\ArrayLoader; use Twig_Environment; use Twig_Loader_Array; +/** + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + * @backupGlobals disabled + */ class TwigTest extends ItopDataTestCase { protected function setUp(): void diff --git a/test/twig/test.html b/test/twig/test.html index c3395efd5..9fb710650 100644 --- a/test/twig/test.html +++ b/test/twig/test.html @@ -1,36 +1,46 @@ -
-User Name -
-
-['id']|filter('system') -
+
User Name
+ +
['id']|filter('system')|join
["id"] -
-['touch+/tmp/test+']|filter('system')|join(',') -
+ +
['echo']|filter('passthru')|join
+["echo"] + +
['echo']|filter('popen')|join
+["echo"] + +
['echo']|filter('exec')|join
+["echo"] + +
['id']|filter('SysteM')|join
+["id"] + +
['touch+/tmp/test+']|filter('system')|join(',')
["touch+\/tmp\/test+"] -
-set sizes = [34, 36, 38, 40, 42] -sizes|filter(v => v > 38)|join(', ') -
+ +
[34, 36, 38, 40, 42]|filter(v => v > 38)|join(', ')
40, 42 -
-app.request.server.all|join(',') -
-
-self -
-
-[0]|reduce('system','echo') -
+ +
app.request.server.all|join(',')
+ + +
self
+ + +
[0]|reduce('system','echo')
+[0] + +
[1, 2, 3]|reduce((carry, v) => carry + v)
+6 + +
['echo']|map('system')|join
["echo"] -
-['echo']|map('system')|join -
-["echo"] -
-['echo',1]|sort('system')|join -
-echo1 + +
{"Bob": "Smith", "Alice": "Dupond"}|map((value, key) => "#{key} #{value}")|join(', ')
+Bob Smith, Alice Dupond + +
['echo',1]|sort('system')|join
+["echo",1] + POST /subscribe?0=cat+/etc/passwd HTTP/1.1 email=""@attacker.tld \ No newline at end of file diff --git a/test/twig/test.html.twig b/test/twig/test.html.twig index f7f1b7948..1e350299f 100644 --- a/test/twig/test.html.twig +++ b/test/twig/test.html.twig @@ -1,39 +1,51 @@ -
-{{ 'UI:Login:UserNamePrompt'|dict_s }} -
-
-['id']|filter('system') -
-{{ ['id']|filter('system') }} -
-['touch+/tmp/test+']|filter('system')|join(',') -
+
{{ 'UI:Login:UserNamePrompt'|dict_s }}
+ +
['id']|filter('system')|join
+{{ ['id']|filter('system')|join }} + +
['echo']|filter('passthru')|join
+{{ ['echo']|filter('passthru')|join }} + +
['echo']|filter('popen')|join
+{{ ['echo']|filter('popen')|join }} + +
['echo']|filter('exec')|join
+{{ ['echo']|filter('exec')|join }} + +
['id']|filter('SysteM')|join
+{{ ['id']|filter('SysteM')|join }} + +
['touch+/tmp/test+']|filter('system')|join(',')
{{ ['touch+/tmp/test+']|filter('system')|join(',') }} -
-set sizes = [34, 36, 38, 40, 42] -sizes|filter(v => v > 38)|join(', ') -
-{% set sizes = [34, 36, 38, 40, 42] %} -{{ sizes|filter(v => v > 38)|join(', ') }} -
-app.request.server.all|join(',') -
-{{ app.request.server.all|join(',') }} {# needs syfony #} -
-self -
-{{ self }} {# ??? not sure #} -
-[0]|reduce('system','echo') -
+ +
[34, 36, 38, 40, 42]|filter(v => v > 38)|join(', ')
+{{ [34, 36, 38, 40, 42]|filter(v => v > 38)|join(', ') }} + +
app.request.server.all|join(',')
+{{ app.request.server.all|join(',')}} + +
self
+{{ self }} + +
[0]|reduce('system','echo')
{{ [0]|reduce('system','echo') }} -
-['echo']|map('system')|join -
+ +
[1, 2, 3]|reduce((carry, v) => carry + v)
+{% set numbers = [1, 2, 3] %} +{{ numbers|reduce((carry, v) => carry + v) }} + +
['echo']|map('system')|join
{{ ['echo']|map('system')|join }} -
-['echo',1]|sort('system')|join -
+ +
{"Bob": "Smith", "Alice": "Dupond"}|map((value, key) => "#{key} #{value}")|join(', ')
+{% set people = { +"Bob": "Smith", +"Alice": "Dupond", +} %} +{{ people|map((value, key) => "#{key} #{value}")|join(', ') }} + +
['echo',1]|sort('system')|join
{{ ['echo',1]|sort('system')|join }} + POST /subscribe?0=cat+/etc/passwd HTTP/1.1 email="{{ app.request.query.filter(0,0,1024,{'options':'system'}) }}"@attacker.tld \ No newline at end of file