Visitar URL original
fix(core): block dangerous data: and vbscript: URLs in URL sanitizer · angular/angular@7c634ec · GitHub
Skip to content

Commit 7c634ec

Browse files
KevinZhaoatscott
authored andcommitted
fix(core): block dangerous data: and vbscript: URLs in URL sanitizer
The URL sanitizer previously only blocked `javascript:` URLs via a negative lookahead. This left other potentially dangerous URL schemes unblocked, including `data:text/html` (which can execute scripts in some environments), `vbscript:` (script execution in legacy IE), and bare `data:` URIs with no explicit media type. This change extends the URL sanitizer to also block: - `vbscript:` URLs - `data:` URLs except for safe media subtypes (image/*, video/*, audio/*) Safe media data: URIs (e.g. `data:image/png;base64,...`) continue to be allowed as they are commonly used for inline images and do not execute scripts when loaded via `<img src>`. This resolves a longstanding TODO in html_sanitizer.ts to special-case `data:image/` URIs, which has been open since the sanitizer was first written. Applications that rely on non-media `data:` URLs (e.g. `data:text/plain` or `data:application/pdf`) in sanitized contexts should use `bypassSecurityTrustUrl()` to explicitly mark them as trusted. (cherry picked from commit e96936a)
1 parent 39ef1dc commit 7c634ec

3 files changed

Lines changed: 44 additions & 6 deletions

File tree

‎packages/core/src/sanitization/html_sanitizer.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ class SanitizingHtmlSerializer {
195195
continue;
196196
}
197197
let value = elAttr!.value;
198-
// TODO(martinprobst): Special case image URIs for data:image/...
198+
// Note: data: URIs with dangerous subtypes (e.g. data:text/html) are now blocked
199+
// by _sanitizeUrl, while safe media subtypes (data:image/*, data:video/*, data:audio/*)
200+
// are still allowed.
199201
if (URI_ATTRS[lower]) value = _sanitizeUrl(value);
200202
this.buf.push(' ', attrName, '="', encodeEntities(value), '"');
201203
}

‎packages/core/src/sanitization/url_sanitizer.ts‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ import {XSS_SECURITY_URL} from '../error_details_base_url';
1515
* This regular expression matches a subset of URLs that will not cause script
1616
* execution if used in URL context within a HTML document. Specifically, this
1717
* regular expression matches if:
18-
* (1) Either a protocol that is not javascript:, and that has valid characters
19-
* (alphanumeric or [+-.]).
18+
* (1) Either a protocol that is not javascript: or vbscript:, and that has
19+
* valid characters (alphanumeric or [+-.]).
20+
* For data: URIs, only safe media subtypes (image/*, video/*, audio/*)
21+
* are allowed. Other data: subtypes (e.g. data:text/html) are blocked
22+
* as they can lead to script execution in some environments.
2023
* (2) or no protocol. A protocol must be followed by a colon. The below
2124
* allows that by allowing colons only after one of the characters [/?#].
2225
* A colon after a hash (#) must be in the fragment.
@@ -33,9 +36,16 @@ import {XSS_SECURITY_URL} from '../error_details_base_url';
3336
* that. More importantly, it disallows masking of a colon,
3437
* e.g. "javascript&#58;...".
3538
*
36-
* This regular expression was taken from the Closure sanitization library.
39+
* This regular expression was originally taken from the Closure sanitization
40+
* library and extended to also block vbscript: and dangerous data: subtypes.
41+
*
42+
* Note: data:image/svg+xml is allowed because SVG loaded via <img src> is
43+
* sandboxed by browsers (scripts do not execute). For <a href> contexts,
44+
* modern browsers (Chrome 60+, Firefox 59+) block top-level navigation to
45+
* data: URLs entirely, providing an additional layer of defense.
3746
*/
38-
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;
47+
const SAFE_URL_PATTERN =
48+
/^(?!javascript:)(?!vbscript:)(?!data:(?!image\/|video\/|audio\/))(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;
3949
export function _sanitizeUrl(url: string): string {
4050
url = String(url);
4151
if (url.match(SAFE_URL_PATTERN)) return url;

‎packages/core/test/sanitization/url_sanitizer_spec.ts‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ describe('URL sanitizer', () => {
4646
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
4747
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
4848
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
49+
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==',
50+
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
4951
'unknown-scheme:abc',
5052
];
5153
for (const url of validUrls) {
@@ -68,7 +70,31 @@ describe('URL sanitizer', () => {
6870
'jav\u0000ascript:alert();',
6971
];
7072
for (const url of invalidUrls) {
71-
it(`valid ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
73+
it(`invalid ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
74+
}
75+
});
76+
77+
describe('vbscript URLs', () => {
78+
const vbscriptUrls = ['vbscript:MsgBox("XSS")', 'VBScript:alert()', 'VBSCRIPT:MsgBox("XSS")'];
79+
for (const url of vbscriptUrls) {
80+
it(`blocks ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
81+
}
82+
});
83+
84+
describe('dangerous data: URLs', () => {
85+
const dangerousDataUrls = [
86+
'data:text/html,<script>alert(1)</script>',
87+
'data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==',
88+
'data:application/xhtml+xml,<script>alert(1)</script>',
89+
'data:text/xml,<script>alert(1)</script>',
90+
'DATA:text/html,<script>alert(1)</script>',
91+
'data:,<script>alert(1)</script>',
92+
'data:application/javascript,alert(1)',
93+
'data:text/plain,hello',
94+
'data:application/pdf;base64,abc',
95+
];
96+
for (const url of dangerousDataUrls) {
97+
it(`blocks ${url}`, () => expect(_sanitizeUrl(url)).toMatch(/^unsafe:/));
7298
}
7399
});
74100
});

0 commit comments

Comments
 (0)