123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- <?php
- /**
- * @package Grav.Common
- *
- * @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Common;
- class Security
- {
- public static function detectXssFromPages($pages, callable $status = null)
- {
- $routes = $pages->routes();
- // Remove duplicate for homepage
- unset($routes['/']);
- $list = [];
- // // This needs Symfony 4.1 to work
- // $status && $status([
- // 'type' => 'count',
- // 'steps' => count($routes),
- // ]);
- foreach ($routes as $path) {
- $status && $status([
- 'type' => 'progress',
- ]);
- try {
- $page = $pages->get($path);
- // call the content to load/cache it
- $header = (array) $page->header();
- $content = $page->value('content');
- $data = ['header' => $header, 'content' => $content];
- $results = Security::detectXssFromArray($data);
- if (!empty($results)) {
- $list[$page->filePathClean()] = $results;
- }
- } catch (\Exception $e) {
- continue;
- }
- }
- return $list;
- }
- /**
- * @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 ($result = static::detectXss($value)) {
- $list[] = [$prefix . $key => $result];
- }
- }
- if (!empty($list)) {
- return array_merge(...$list);
- }
- return $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|string Type of XSS vector 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);
- $config = Grav::instance()['config'];
- $dangerous_tags = $config->get('security.xss_dangerous_tags');
- $dangerous_tags = array_map('preg_quote', array_map("trim", $dangerous_tags));
- $enabled_rules = $config->get('security.xss_enabled');
- // Set the patterns we'll test against
- $patterns = [
- // Match any attribute starting with "on" or xmlns
- 'on_events' => '#(<[^>]+[[a-z\x00-\x20\"\'\/])(\son|\sxmlns)[a-z].*=>?#iUu',
- // Match javascript:, livescript:, vbscript:, mocha:, feed: and data: protocols
- 'invalid_protocols' => '#((java|live|vb)script|mocha|feed|data):.*?#iUu',
- // Match -moz-bindings
- 'moz_binding' => '#-moz-binding[a-z\x00-\x20]*:#u',
- // Match style attributes
- 'html_inline_styles' => '#(<[^>]+[a-z\x00-\x20\"\'\/])(style=[^>]*(url\:|x\:expression).*)>?#iUu',
- // Match potentially dangerous tags
- 'dangerous_tags' => '#</*(' . implode('|', $dangerous_tags ) . ')[^>]*>?#ui'
- ];
- // Iterate over rules and return label if fail
- foreach ((array) $patterns as $name => $regex) {
- if ($enabled_rules[$name] === true) {
- if (preg_match($regex, $string) || preg_match($regex, $orig)) {
- return $name;
- }
- }
- }
- return false;
- }
- }
|