'/blueprints/events/', 'delegate' => 'AppendEventFilterDocumentation', 'callback' => 'appendEventFilterDocumentation' ), array( 'page' => '/blueprints/events/new/', 'delegate' => 'AppendEventFilter', 'callback' => 'appendEventFilter' ), array( 'page' => '/blueprints/events/edit/', 'delegate' => 'AppendEventFilter', 'callback' => 'appendEventFilter' ), array( 'page' => '/frontend/', 'delegate' => 'EventPreSaveFilter', 'callback' => 'eventPreSaveFilter' ), array( 'page' => '/frontend/', 'delegate' => 'FrontendParamsResolve', 'callback' => 'frontendParamsResolve' ) ); } public function appendEventFilterDocumentation(array $context) { if(in_array('validate-xsrf', $context['selected'])) { $context['documentation'][] = new XMLElement('p', __('To validate a XSRF token, ensure it is passed in the form.')); $context['documentation'][] = contentAjaxEventDocumentation::processDocumentationCode(Widget::Input('xsrf', '{$cookie-xsrf-token}', 'hidden')); } } public function appendEventFilter(array $context) { $context['options'][] = array( 'xss-fail', is_array($context['selected']) ? in_array('xss-fail', $context['selected']) : false, 'Filter XSS: Fail if malicious input is detected' ); $context['options'][] = array( 'validate-xsrf', is_array($context['selected']) ? in_array('validate-xsrf', $context['selected']) : false, 'Validate XSRF: Ensure request was passed with a XSRF token' ); } public function eventPreSaveFilter(array $context) { if(!in_array('xss-fail', $context['event']->eParamFILTERS) && !in_array('validate-xsrf', $context['event']->eParamFILTERS)) return; $contains_xss = FALSE; // Loop over the fields to check for XSS, this loop will // break as soon as XSS is detected foreach($context['fields'] as $field => $value) { if(is_array($value)) { if(self::detectXSSInArray($value) === FALSE) continue; $contains_xss = TRUE; break; } else { if(self::detectXSS($value) === FALSE) continue; $contains_xss = TRUE; break; } } // Detect XSS filter if(in_array('xss-fail', $context['event']->eParamFILTERS) && $contains_xss === TRUE) { $context['messages'][] = array( 'xss', FALSE, __("Possible XSS attack detected in submitted data") ); } // Validate XSRF token filter if(in_array('validate-xsrf', $context['event']->eParamFILTERS)) { if(Symphony::Engine()->isXSRFEnabled() && is_session_empty() === false && XSRF::validateRequest(true) === false) { $context['messages'][] = array( 'xsrf', FALSE, __("Request was rejected for having an invalid cross-site request forgery token.") ); } } } public static function FrontendParamsResolve(array &$context) { Frontend::Page()->registerPHPFunction(array( 'htmlContextCleaner', 'scriptContextCleaner', 'attributeContextCleaner', 'styleContextCleaner', 'urlContextCleaner' )); } /** * A utility function to manage nested array structures, checking * each value for possible XSS. Function returns boolean if XSS is * found. * * @param array $array * An array of data to check, this can be nested arrays. * @return boolean * True if XSS is detected, false otherwise */ public static function detectXSSInArray(array $array) { foreach($array as $value) { if(is_array($value)) { return self::detectXSSInArray($value); } else { if(self::detectXSS($value) === TRUE) return TRUE; } } return FALSE; } /** * Given a string, this function will determine if it potentially an * XSS attack and return boolean. * * @param string $string * The string to run XSS detection logic on * @return boolean * True if the given `$string` contains XSS, false otherwise. */ public static function detectXSS($string) { $contains_xss = FALSE; // Skip any null or non string values if(is_null($string) || !is_string($string)) { return $contains_xss; } // Keep a copy of the original string before cleaning up $orig = $string; // URL decode $string = urldecode($string); // Convert Hexadecimals $string = preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!', function($m) { return chr(hexdec($m[2])); }, $string); // Clean up entities $string = preg_replace('!(�+[0-9]+)!','$1;',$string); // Decode entities $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8'); // Strip whitespace characters $string = preg_replace('!\s!','',$string); // Set the patterns we'll test against $patterns = array( // Match any attribute starting with "on" or xmlns '#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>?#iUu', // Match javascript:, livescript:, vbscript: and mocha: protocols '!((java|live|vb)script|mocha|feed|data):(\w)*!iUu', '#-moz-binding[\x00-\x20]*:#u', // Match style attributes '#(<[^>]+[\x00-\x20\"\'\/])style=[^>]*>?#iUu', // Match unneeded tags '#]*>?#i' ); foreach($patterns as $pattern) { // Test both the original string and clean string if(preg_match($pattern, $string) || preg_match($pattern, $orig)){ $contains_xss = TRUE; } if ($contains_xss === TRUE) return TRUE; } return FALSE; } }