CVE-2024-28117: Insufficient Information (NVD-CWE-noinfo)
value: Grav is an open-source, flat-file content management system. Prior to version 1.7.45, Grav validates accessible functions through the Utils::isDangerousFunction function, but does not impose restrictions on twig functions like twig_array_map, allowing attackers to bypass the validation and execute arbitrary commands. Twig processing of static pages can be enabled in the front matter by any administrative user allowed to create or edit pages. As the Twig processor runs unsandboxed, this behavior can be used to gain arbitrary code execution and elevate privileges on the instance. Upgrading to patched version 1.7.45 can mitigate this issue.

CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-21T22:15Z
Repository: https://github.com/getgrav/grav
Language: PHP
Files changed: CHANGELOG.md, system/src/Grav/Common/Security.php, system/src/Grav/Common/Twig/Twig.php
Commit date: 2024-03-04 15:41:30 -0700
Commit message: Mitigate various SSTI injections

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16ac059060..cdb7a2d1bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,11 +4,12 @@
 1. [](#news)
    * Added new Image trait for `decoding` attribute [#3796](https://github.com/getgrav/grav/pull/3796)
 1. [](#bugfix)
    * Fixed some multibyte issues in Inflector class [#732](https://github.com/getgrav/grav/issues/732)
    * Fallback to page modified date if Page date provided is invalid and can't be parsed [getgrav/grav-plugin-admin#2394](https://github.com/getgrav/grav-plugin-admin/issues/2394)
-   * Fixed a path traversal vulnerability with file uploads [GHSA-m7hx-hw6h-mqmc](https://github.com/getgrav/grav/security/advisories/GHSA-m7hx-hw6h-mqmc)
+   * Fixed a path traversal vulnerability with file uploads [#GHSA-m7hx-hw6h-mqmc](https://github.com/getgrav/grav/security/advisories/GHSA-m7hx-hw6h-mqmc)
+   * Fixed a security issue with insecure Twig functions be processed [#GHSA-2m7x-c7px-hp58](https://github.com/getgrav/grav/security/advisories/GHSA-2m7x-c7px-hp58) [#GHSA-r6vw-8v8r-pmp4](https://github.com/getgrav/grav/security/advisories/GHSA-r6vw-8v8r-pmp4) [#GHSA-qfv4-q44r-g7rv](https://github.com/getgrav/grav/security/advisories/GHSA-qfv4-q44r-g7rv)
 
 # v1.7.44
 ## 01/05/2024
 
 1. [](#new)
diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php
index 55bcde5b47..e363a28ff9 100644
--- a/system/src/Grav/Common/Security.php
+++ b/system/src/Grav/Common/Security.php
@@ -261,6 +261,25 @@ class Security
             'enabled_rules' => $config->get('security.xss_enabled'),
             'dangerous_tags' => array_map('trim', $config->get('security.xss_dangerous_tags')),
             'invalid_protocols' => array_map('trim', $config->get('security.xss_invalid_protocols')),
         ];
     }
+
+    public static function cleanDangerousTwig(string $string): string
+    {
+        if ($string === '') {
+            return $string;
+        }
+
+        $bad_twig = [
+            'twig_array_map',
+            'twig_array_filter',
+            'call_user_func',
+            'registerUndefinedFunctionCallback',
+            'undefined_functions',
+            'twig.getFunction',
+            'core.setEscaper',
+        ];
+        $string = preg_replace('/(({{\s*|{%\s*)[^}]*?(' . implode('|', $bad_twig) . ')[^}]*?(\s*}}|\s*%}))/i', '{# $1 #}', $string);
+        return $string;
+    }
 }
diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php
index 14f33f0840..6e50916fb3 100644
--- a/system/src/Grav/Common/Twig/Twig.php
+++ b/system/src/Grav/Common/Twig/Twig.php
@@ -14,10 +14,11 @@ use Grav\Common\Grav;
 use Grav\Common\Config\Config;
 use Grav\Common\Language\Language;
 use Grav\Common\Language\LanguageCodes;
 use Grav\Common\Page\Interfaces\PageInterface;
 use Grav\Common\Page\Pages;
+use Grav\Common\Security;
 use Grav\Common\Twig\Exception\TwigException;
 use Grav\Common\Twig\Extension\FilesystemExtension;
 use Grav\Common\Twig\Extension\GravExtension;
 use Grav\Common\Utils;
 use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
@@ -317,10 +318,11 @@ class Twig
      * @return string          The rendered output
      */
     public function processPage(PageInterface $item, $content = null)
     {
         $content = $content ?? $item->content();
+        $content = Security::cleanDangerousTwig($content);
 
         // override the twig header vars for local resolution
         $this->grav->fireEvent('onTwigPageVariables', new Event(['page' => $item]));
         $twig_vars = $this->twig_vars;
 
@@ -390,10 +392,12 @@ class Twig
     {
         // override the twig header vars for local resolution
         $this->grav->fireEvent('onTwigStringVariables');
         $vars += $this->twig_vars;
 
+        $string = Security::cleanDangerousTwig($string);
+
         $name = '@Var:' . $string;
         $this->setTemplate($name, $string);
 
         try {
             $output = $this->twig->render($name, $vars);
@@ -416,26 +420,28 @@ class Twig
     public function processSite($format = null, array $vars = [])
     {
         try {
             $grav = $this->grav;
 
-            // set the page now its been processed
+            // set the page now it's been processed
             $grav->fireEvent('onTwigSiteVariables');
 
             /** @var Pages $pages */
             $pages = $grav['pages'];
 
             /** @var PageInterface $page */
             $page = $grav['page'];
 
+            $content = Security::cleanDangerousTwig($page->content());
+
             $twig_vars = $this->twig_vars;
             $twig_vars['theme'] = $grav['config']->get('theme');
             $twig_vars['pages'] = $pages->root();
             $twig_vars['page'] = $page;
             $twig_vars['header'] = $page->header();
             $twig_vars['media'] = $page->media();
-            $twig_vars['content'] = $page->content();
+            $twig_vars['content'] = $content;
 
             // determine if params are set, if so disable twig cache
             $params = $grav['uri']->params(null, true);
             if (!empty($params)) {
                 $this->twig->setCache(false);
@@ -566,6 +572,7 @@ class Twig
             user_error(__CLASS__ . '::' . __FUNCTION__ . '(false) is deprecated since Grav 1.5', E_USER_DEPRECATED);
         }
 
         $this->autoescape = (bool) $state;
     }
+
 }

Vulnerable code:    * Fixed a path traversal vulnerability with file uploads [GHSA-m7hx-hw6h-mqmc](https://github.com/getgrav/grav/security/advisories/GHSA-m7hx-hw6h-mqmc)
            // set the page now its been processed
            $twig_vars['content'] = $page->content();
Fixed code:    * Fixed a path traversal vulnerability with file uploads [#GHSA-m7hx-hw6h-mqmc](https://github.com/getgrav/grav/security/advisories/GHSA-m7hx-hw6h-mqmc)
   * Fixed a security issue with insecure Twig functions be processed [#GHSA-2m7x-c7px-hp58](https://github.com/getgrav/grav/security/advisories/GHSA-2m7x-c7px-hp58) [#GHSA-r6vw-8v8r-pmp4](https://github.com/getgrav/grav/security/advisories/GHSA-r6vw-8v8r-pmp4) [#GHSA-qfv4-q44r-g7rv](https://github.com/getgrav/grav/security/advisories/GHSA-qfv4-q44r-g7rv)

    public static function cleanDangerousTwig(string $string): string
    {
        if ($string === '') {
            return $string;
        }

        $bad_twig = [
            'twig_array_map',
            'twig_array_filter',
            'call_user_func',
            'registerUndefinedFunctionCallback',
            'undefined_functions',
            'twig.getFunction',
            'core.setEscaper',
        ];
        $string = preg_replace('/(({{\s*|{%\s*)[^}]*?(' . implode('|', $bad_twig) . ')[^}]*?(\s*}}|\s*%}))/i', '{# $1 #}', $string);
        return $string;
    }
use Grav\Common\Security;
        $content = Security::cleanDangerousTwig($content);
        $string = Security::cleanDangerousTwig($string);

            // set the page now it's been processed
            $content = Security::cleanDangerousTwig($page->content());

            $twig_vars['content'] = $content;