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 b44f62e869..c4f51f9c76 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
@@ -100,15 +100,41 @@ 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 Twig_SimpleFilter('filter', function ($array, $arrow) {
- if ($arrow == 'system'){
- return json_encode($array);
+ $ret = $this->SanitizeFilter($array, $arrow);
+ if ($ret !== false) {
+ return [$ret];
}
- return twig_array_filter($array, $arrow);
+ return twig_array_filter($array, $arrow);
+ });
+ $filters[] = new Twig_SimpleFilter('map', function ($array, $arrow) {
+ $ret = $this->SanitizeFilter($array, $arrow);
+ if ($ret !== false) {
+ return [$ret];
+ }
+ return twig_array_map($array, $arrow);
+ });
+ $filters[] = new Twig_SimpleFilter('reduce', function ($array, $arrow, $initial = null) {
+ $ret = $this->SanitizeFilter($array, $arrow);
+ if ($ret !== false) {
+ return $ret;
+ }
+ // reduce return mixed results not only arrays
+ return twig_array_reduce($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_SimpleFunction[]
*/
diff --git a/test/twig/TwigTest.php b/test/twig/TwigTest.php
index f47bc10bed..f14420c817 100644
--- a/test/twig/TwigTest.php
+++ b/test/twig/TwigTest.php
@@ -5,6 +5,11 @@ use Combodo\iTop\Portal\Twig\AppExtension;
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 2406bbfd4f..3fb370a80a 100644
--- a/test/twig/test.html
+++ b/test/twig/test.html
@@ -1,33 +1,46 @@
-
- User Name
-
- ['id']|filter('system')
-
- ["id"]
-
- ['touch+/tmp/test+']|filter('system')|join(',')
-
- ["touch+\/tmp\/test+"]
-
- set sizes = [34, 36, 38, 40, 42]
- sizes|filter(v => v > 38)|join(', ')
-
- 40, 42
-
- app.request.server.all|join(',')
-
- self
-
- [0]|reduce('system','echo')
-
- ["echo"]
-
- ['echo']|map('system')|join
-
- ["echo"]
-
- ['echo',1]|sort('system')|join
-
- echo1
- POST /subscribe?0=cat+/etc/passwd HTTP/1.1
- email=""@attacker.tld
\ No newline at end of file
+User Name
+
+['id']|filter('system')|join
+["id"]
+
+['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+"]
+
+[34, 36, 38, 40, 42]|filter(v => v > 38)|join(', ')
+40, 42
+
+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"]
+
+{"Bob": "Smith", "Alice": "Dupond"}|map((value, key) => "#{key} #{value}")|join(', ')
+Bob Smith, Alice Dupond
+
+['echo',1]|sort('system')|join
+echo1
+
+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 5825249c30..1e350299f0 100644
--- a/test/twig/test.html.twig
+++ b/test/twig/test.html.twig
@@ -1,41 +1,51 @@
-{% spaceless %}
-
- {{ 'UI:Login:UserNamePrompt'|dict_s }}
-
-
- ['id']|filter('system')
-
- {{ ['id']|filter('system') }}
-
- ['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')
-
- {{ [0]|reduce('system','echo') }}
-
- ['echo']|map('system')|join
-
- {{ ['echo']|map('system')|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
-{% endspaceless %}
\ No newline at end of file
+{{ '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(',') }}
+
+[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') }}
+
+[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 }}
+
+{"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