diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a52e090..b1e692ee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,11 +20,14 @@ 1. [](#new) * Added `Deprecated` tab to DebugBar to catch future incompatibilities with later Grav versions * Added deprecation notices for features which will be removed in Grav 2.0 + * Added `Utils::detectXssFromArray()` and `Utils::detectXss()` methods + * Added `onHttpPostFilter` event to allow plugins to globally clean up XSS in the forms and tasks 1. [](#bugfix) * Allow `$page->slug()` to be called before `$page->init()` without breaking the page * Fix for `Page::translatedLanguages()` to use routes always [#2163](https://github.com/getgrav/grav/issues/2163) * Fixed `nicetime()` twig function * Allow twig tags `{% script %}`, `{% style %}` and `{% switch %}` to be placed outside of blocks + * Session expires in 30 mins independent from config settings [login#178](https://github.com/getgrav/grav-plugin-login/issues/178) # v1.5.1 ## 08/23/2018 diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index 35e9d3b3f..9ce2d423d 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -15,6 +15,7 @@ use Grav\Common\Page\Pages; use Grav\Framework\Route\RouteFactory; use Grav\Framework\Uri\UriFactory; use Grav\Framework\Uri\UriPartsFilter; +use RocketTheme\Toolbox\Event\Event; class Uri { @@ -1284,6 +1285,9 @@ class Uri } elseif (!empty($_POST)) { $this->post = (array)$_POST; } + + $event = new Event(['post' => &$this->post]); + Grav::instance()->fireEvent('onHttpPostFilter', $event); } if ($this->post && null !== $element) { diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index 772702276..09ebf45eb 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -253,6 +253,90 @@ abstract class Utils return $date_formats; } + /** + * @param array $array Array such as $_POST or $_GET + * @param string $prefix Prefix for returned values. + * @return array Returns flatten list of potentially dangerous input values, such as 'data.content'. + */ + public static function detectXssFromArray(array $array, $prefix = '') + { + $list = []; + + foreach ($array as $key => $value) { + if (\is_array($value)) { + $list[] = static::detectXssFromArray($value, $prefix . $key . '.'); + } + if (static::detectXss($value)) { + $list[] = [$prefix . $key]; + } + } + + return array_merge(...$list); + } + + /** + * Determine if string potentially has a XSS attack. This simple function does not catch all XSS and it is likely to + * return false positives because of it tags all potentially dangerous HTML tags and attributes without looking into + * their content. + * + * @param string $string The string to run XSS detection logic on + * @return boolean True if the given `$string` may contain XSS, false otherwise. + * + * Copies the code from: https://github.com/symphonycms/xssfilter/blob/master/extension.driver.php#L138 + */ + public static function detectXss($string) + { + // Skip any null or non string values + if (null === $string || !\is_string($string) || empty($string)) { + return false; + } + + // Keep a copy of the original string before cleaning up + $orig = $string; + + // URL decode + $string = urldecode($string); + + // Convert Hexadecimals + $string = (string)preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!u', function($m) { + return \chr(hexdec($m[2])); + }, $string); + + // Clean up entities + $string = preg_replace('!(�+[0-9]+)!u','$1;', $string); + + // Decode entities + $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); + + // Strip whitespace characters + $string = preg_replace('!\s!u','', $string); + + // Set the patterns we'll test against + $patterns = [ + // Match any attribute starting with "on" or xmlns + '#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>?#iUu', + + // Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols + '!((java|live|vb)script|mocha|feed|data):(\w)*!iUu', + + // Match -moz-bindings + '#-moz-binding[\x00-\x20]*:#u', + + // Match style attributes + '#(<[^>]+[\x00-\x20\"\'\/])style=[^>]*>?#iUu', + + // Match potentially dangerous tags + '#]*>?#ui' + ]; + + foreach ($patterns as $pattern) { + // Test both the original string and clean string + if (preg_match($pattern, $string) || preg_match($pattern, $orig)) { + return true; + } + } + return false; + } /** * Truncate text by number of characters but can cut off words. * diff --git a/system/src/Grav/Framework/Session/Session.php b/system/src/Grav/Framework/Session/Session.php index e7c6f1fce..e66c211a9 100644 --- a/system/src/Grav/Framework/Session/Session.php +++ b/system/src/Grav/Framework/Session/Session.php @@ -15,6 +15,8 @@ namespace Grav\Framework\Session; */ class Session implements SessionInterface { + protected $options; + /** * @var bool */ @@ -178,7 +180,10 @@ class Session implements SessionInterface unset($_COOKIE[session_name()]); } - $options = $readonly ? ['read_and_close' => '1'] : []; + $options = $this->options; + if ($readonly) { + $options['read_and_close'] = '1'; + } $success = @session_start($options); if (!$success) { @@ -332,6 +337,7 @@ class Session implements SessionInterface } } + $this->options[$key] = $value; ini_set($key, $value); } }