diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 3ef16d7a19..7d1480a72c 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -31,6 +31,9 @@ v27.0.00 System: updated JQuery(2.2.4 -> 3.7.1) and JQuery Migrate(1.4.1 -> 3.4.0) files to latest versions System: in you use Gmail SMTP relay, be sure to update your settings to use an App Password + Security + System: improved the input sanitization and output encoding or URLs + Tweaks & Additions System: automatically hyperlink any urls included in Custom Field descriptions System: removed raw exception message output from the interface diff --git a/src/Data/Validator.php b/src/Data/Validator.php index 12408eab76..11d1eb86c7 100644 --- a/src/Data/Validator.php +++ b/src/Data/Validator.php @@ -62,13 +62,15 @@ public function getAllowableIframeSources() public function sanitize($input, $allowableTags = [], $utf8_encode = true) { $output = []; + $urls = []; // Default allowable tags $allowableTags['*CustomEditor'] = 'HTML'; // Match wildcard * in allowable tags and add these fields to the list foreach ($allowableTags as $field => $value) { - if (stripos($field, '*') === false) continue; + if (mb_stripos($field, '*') === false) continue; + if ($keys = $this->getWildcardArrayKeyMatches($input, $field)) { foreach ($keys as $key) { $allowableTags[$key] = $value; @@ -76,6 +78,13 @@ public function sanitize($input, $allowableTags = [], $utf8_encode = true) } } + // Check allowable fields for URLs + foreach ($allowableTags as $field => $value) { + if (mb_stripos($value, 'URL') !== false) { + $urls[$field] = $field; + } + } + // Process the input foreach (array_keys($input) as $field) { $value = $input[$field]; @@ -89,8 +98,11 @@ public function sanitize($input, $allowableTags = [], $utf8_encode = true) $value = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', '', $value); $value = preg_replace('/\\\\+0+/', '', $value); - // Sanitize HTML - if (!empty($allowableTags[$field])) { + if (!empty($urls[$field])) { + // Sanitize URL + $value = $this->sanitizeUrl($value); + } elseif (!empty($allowableTags[$field])) { + // Sanitize HTML if (strtoupper($allowableTags[$field]) == 'RAW') { $output[$field] = $value; continue; @@ -110,6 +122,7 @@ public function sanitize($input, $allowableTags = [], $utf8_encode = true) } } } else { + // Sanitize all $value = strip_tags($value); } @@ -175,6 +188,30 @@ public function sanitizeRichText($value) return $this->sanitizeHTML($value, $this->allowableHTML); } + /** + * Sanitize invalid characters in a URL. + * + * @param string $url + * @return string + */ + public function sanitizeUrl($url) + { + if ($url === '') return $url; + + // Replace and remove disallowed characters + $url = str_replace(' ', '%20', ltrim($url)); + $url = str_replace('"', '%22', $url); + $url = preg_replace('|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url); + $url = str_replace("'", ''', $url); + + // If there is no protocol, add a default one + if (mb_stripos($url, '://') === false) { + $url = 'https://'.$url; + } + + return $url; + } + /** * Sanitize values used in URL parameters. *