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;
}