From b35fa739687a357108edaa0a57dcd033ecfcb9f2 Mon Sep 17 00:00:00 2001 From: Guillaume Weghsteen Date: Fri, 31 Mar 2023 10:09:28 +0000 Subject: [PATCH] feat(core): change the URL sanitization to only block javascript: URLs (#49659) In modern browsers, the 'javascript:' URL scheme is the only scheme that can execute JavaScript when passed in a navigation URL context (e.g. `a.href` value). Validate URL shemes to only contain characters allowed in the URL specification ([a-zA-Z-+.]), and that are not javascript (case insensitive). This is not a breaking change. The URL sanitization is loosen. PR Close #49659 --- packages/core/src/sanitization/url_sanitizer.ts | 12 ++++++------ .../core/test/sanitization/url_sanitizer_spec.ts | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/core/src/sanitization/url_sanitizer.ts b/packages/core/src/sanitization/url_sanitizer.ts index f90528529d3..59b73d2fe07 100644 --- a/packages/core/src/sanitization/url_sanitizer.ts +++ b/packages/core/src/sanitization/url_sanitizer.ts @@ -9,13 +9,14 @@ import {XSS_SECURITY_URL} from '../error_details_base_url'; /** - * A pattern that recognizes a commonly useful subset of URLs that are safe. + * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation + * contexts. * * This regular expression matches a subset of URLs that will not cause script * execution if used in URL context within a HTML document. Specifically, this - * regular expression matches if (comment from here on and regex copied from - * Soy's EscapingConventions): - * (1) Either an allowed protocol (http, https, mailto or ftp). + * regular expression matches if: + * (1) Either a protocol that is not javascript:, and that has valid characters + * (alphanumeric or [+-.]). * (2) or no protocol. A protocol must be followed by a colon. The below * allows that by allowing colons only after one of the characters [/?#]. * A colon after a hash (#) must be in the fragment. @@ -34,8 +35,7 @@ import {XSS_SECURITY_URL} from '../error_details_base_url'; * * This regular expression was taken from the Closure sanitization library. */ -const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|data|ftp|tel|file|sms):|[^&:/?#]*(?:[/?#]|$))/gi; - +const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i; export function _sanitizeUrl(url: string): string { url = String(url); if (url.match(SAFE_URL_PATTERN)) return url; diff --git a/packages/core/test/sanitization/url_sanitizer_spec.ts b/packages/core/test/sanitization/url_sanitizer_spec.ts index f734d9ae09a..7614a5a99b5 100644 --- a/packages/core/test/sanitization/url_sanitizer_spec.ts +++ b/packages/core/test/sanitization/url_sanitizer_spec.ts @@ -47,6 +47,7 @@ import {_sanitizeUrl} from '../../src/sanitization/url_sanitizer'; 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated. 'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', 'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', + 'unknown-scheme:abc', ]; for (const url of validUrls) { it(`valid ${url}`, () => expect(_sanitizeUrl(url)).toEqual(url)); @@ -57,7 +58,7 @@ import {_sanitizeUrl} from '../../src/sanitization/url_sanitizer'; const invalidUrls = [ 'javascript:evil()', 'JavaScript:abc', - 'evilNewProtocol:abc', + ' javascript:abc', ' \n Java\n Script:abc', 'javascript:', 'javascript:',