diff --git a/CHANGELOG.md b/CHANGELOG.md index 7956dc3b02..50aca24d3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# v1.7.42 +## 06/14/2023 + +1. [](#new) + * Added a new `system.languages.debug` option that adds a `` around strings translated with `|t`. This can be styled by the theme as needed. +1. [](#improved) + * More robust SSTI handling in `filter`, `map`, and `reduce` Twig filters and functions + * Various SSTI improvements `Utils::isDangerousFunction()` +1. [](#bugfix) + * Fixed Twig `|map()` allowing code execution + * Fixed Twig `|reduce()` allowing code execution + # v1.7.41.2 ## 06/01/2023 diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index 14fef03ce9..182457cbbd 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -448,6 +448,17 @@ form: validate: type: bool + languages.debug: + type: toggle + label: PLUGIN_ADMIN.LANGUAGE_DEBUG + help: PLUGIN_ADMIN.LANGUAGE_DEBUG_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + http_headers: type: tab title: PLUGIN_ADMIN.HTTP_HEADERS diff --git a/system/config/system.yaml b/system/config/system.yaml index 2ffd02a8c9..8bcc280758 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -28,6 +28,7 @@ languages: override_locale: false # Override the default or system locale with language specific one content_fallback: {} # Custom language fallbacks. eg: {fr: ['fr', 'en']} pages_fallback_only: false # DEPRECATED: Use `content_fallback` instead + debug: false # Debug language detection home: alias: '/home' # Default path for home, ie / diff --git a/system/defines.php b/system/defines.php index 02b37c9010..634cb524ae 100644 --- a/system/defines.php +++ b/system/defines.php @@ -9,7 +9,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.7.41.2'); +define('GRAV_VERSION', '1.7.42'); define('GRAV_SCHEMA', '1.7.0_2020-11-20_1'); define('GRAV_TESTING', false); diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php index 9f54a1733d..2ef5364a38 100644 --- a/system/src/Grav/Common/Twig/Extension/GravExtension.php +++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php @@ -46,6 +46,7 @@ use Twig\Extension\AbstractExtension; use Twig\Extension\GlobalsInterface; use Twig\Loader\FilesystemLoader; +use Twig\Markup; use Twig\TwigFilter; use Twig\TwigFunction; use function array_slice; @@ -170,8 +171,10 @@ public function getFilters(): array new TwigFilter('count', 'count'), new TwigFilter('array_diff', 'array_diff'), - // Security fix - new TwigFilter('filter', [$this, 'filterFilter'], ['needs_environment' => true]), + // Security fixes + new TwigFilter('filter', [$this, 'filterFunc'], ['needs_environment' => true]), + new TwigFilter('map', [$this, 'mapFunc'], ['needs_environment' => true]), + new TwigFilter('reduce', [$this, 'reduceFunc'], ['needs_environment' => true]), ]; } @@ -248,6 +251,11 @@ public function getFunctions(): array new TwigFunction('count', 'count'), new TwigFunction('array_diff', 'array_diff'), new TwigFunction('parse_url', 'parse_url'), + + // Security fixes + new TwigFunction('filter', [$this, 'filterFunc'], ['needs_environment' => true]), + new TwigFunction('map', [$this, 'mapFunc'], ['needs_environment' => true]), + new TwigFunction('reduce', [$this, 'reduceFunc'], ['needs_environment' => true]), ]; } @@ -905,8 +913,13 @@ public function translate(Environment $twig, ...$args) return $this->grav['admin']->translate($args, $lang); } - // else use the default grav translate functionality - return $this->grav['language']->translate($args); + $translation = $this->grav['language']->translate($args); + + if ($this->config->get('system.languages.debug', false)) { + return new Markup("$translation", 'UTF-8'); + } else { + return $translation; + } } /** @@ -1699,12 +1712,44 @@ public function ofTypeFunc($var, $typeTest = null, $className = null) * @return array|CallbackFilterIterator * @throws RuntimeError */ - function filterFilter(Environment $env, $array, $arrow) + function filterFunc(Environment $env, $array, $arrow) { - if (is_string($arrow) && Utils::isDangerousFunction($arrow)) { + if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { throw new RuntimeError('Twig |filter("' . $arrow . '") is not allowed.'); } return twig_array_filter($env, $array, $arrow); } + + /** + * @param Environment $env + * @param array $array + * @param callable|string $arrow + * @return array|CallbackFilterIterator + * @throws RuntimeError + */ + function mapFunc(Environment $env, $array, $arrow) + { + if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { + throw new RuntimeError('Twig |map("' . $arrow . '") is not allowed.'); + } + + return twig_array_map($env, $array, $arrow); + } + + /** + * @param Environment $env + * @param array $array + * @param callable|string $arrow + * @return array|CallbackFilterIterator + * @throws RuntimeError + */ + function reduceFunc(Environment $env, $array, $arrow) + { + if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) { + throw new RuntimeError('Twig |reduce("' . $arrow . '") is not allowed.'); + } + + return twig_array_map($env, $array, $arrow); + } } diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index cadb787189..14748a558d 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -1950,10 +1950,10 @@ public static function getSupportPageTypes(array $defaults = null) } /** - * @param string $name + * @param string|array|Closure $name * @return bool */ - public static function isDangerousFunction(string $name): bool + public static function isDangerousFunction($name): bool { static $commandExecutionFunctions = [ 'exec', @@ -2048,8 +2048,28 @@ public static function isDangerousFunction(string $name): bool 'posix_setpgid', 'posix_setsid', 'posix_setuid', + 'unserialize', + 'ini_alter', + 'simplexml_load_file', + 'simplexml_load_string', + 'forward_static_call', + 'forward_static_call_array', ]; + $name = strtolower($name); + + if ($name instanceof \Closure) { + return false; + } + + if (strpos($name, "\\") !== false) { + return false; + } + + if (is_array($name) || strpos($name, ":") !== false) { + return false; + } + if (in_array($name, $commandExecutionFunctions)) { return true; }