-
Notifications
You must be signed in to change notification settings - Fork 27.3k
feat($sce): handle URLs through the $sce service #16378
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| @ngdoc error | ||
| @name $compile:srcset | ||
| @fullName Invalid value passed to `attr.$set('srcset', value)` | ||
| @description | ||
|
|
||
| This error occurs if you try to programmatically set the `srcset` attribute with a non-string value. | ||
|
|
||
| This can be the case if you tried to avoid the automatic sanitization of the `srcset` value by | ||
| passing a "trusted" value provided by calls to `$sce.trustAsMediaUrl(value)`. | ||
|
|
||
| If you want to programmatically set explicitly trusted unsafe URLs, you should use `$sce.trustAsHtml` | ||
| on the whole `img` tag and inject it into the DOM using the `ng-bind-html` directive. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1528,9 +1528,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
|
|
||
| this.$get = [ | ||
| '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', | ||
| '$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri', | ||
| '$controller', '$rootScope', '$sce', '$animate', | ||
| function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, | ||
| $controller, $rootScope, $sce, $animate, $$sanitizeUri) { | ||
| $controller, $rootScope, $sce, $animate) { | ||
|
|
||
| var SIMPLE_ATTR_NAME = /^\w/; | ||
| var specialAttrHolder = window.document.createElement('div'); | ||
|
|
@@ -1679,8 +1679,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| */ | ||
| $set: function(key, value, writeAttr, attrName) { | ||
| // TODO: decide whether or not to throw an error if "class" | ||
| //is set through this function since it may cause $updateClass to | ||
| //become unstable. | ||
| // is set through this function since it may cause $updateClass to | ||
| // become unstable. | ||
|
|
||
| var node = this.$$element[0], | ||
| booleanKey = getBooleanAttrName(node, key), | ||
|
|
@@ -1710,13 +1710,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
|
|
||
| nodeName = nodeName_(this.$$element); | ||
|
|
||
| if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || | ||
| (nodeName === 'img' && key === 'src') || | ||
| (nodeName === 'image' && key === 'xlinkHref')) { | ||
| // sanitize a[href] and img[src] values | ||
| this[key] = value = $$sanitizeUri(value, nodeName === 'img' || nodeName === 'image'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, doesn't this leave people using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure what you mean here. Do you mean "more" vulnerable?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I meant "more" (everybody knows "less" is "more" 😛)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am pretty certain that |
||
| } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) { | ||
| // sanitize img[srcset] values | ||
| // Sanitize img[srcset] values. | ||
| if (nodeName === 'img' && key === 'srcset' && value) { | ||
| if (!isString(value)) { | ||
| throw $compileMinErr('srcset', 'Can\'t pass trusted values to `$set(\'srcset\', value)`: "{0}"', value.toString()); | ||
| } | ||
|
|
||
| // Such values are a bit too complex to handle automatically inside $sce. | ||
| // Instead, we sanitize each of the URIs individually, which works, even dynamically. | ||
|
|
||
| // It's not possible to work around this using `$sce.trustAsMediaUrl`. | ||
| // If you want to programmatically set explicitly trusted unsafe URLs, you should use | ||
| // `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the | ||
| // `ng-bind-html` directive. | ||
|
|
||
| var result = ''; | ||
|
|
||
| // first check if there are spaces because it's not the same pattern | ||
|
|
@@ -1733,16 +1740,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| for (var i = 0; i < nbrUrisWith2parts; i++) { | ||
| var innerIdx = i * 2; | ||
| // sanitize the uri | ||
| result += $$sanitizeUri(trim(rawUris[innerIdx]), true); | ||
| result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx])); | ||
| // add the descriptor | ||
| result += (' ' + trim(rawUris[innerIdx + 1])); | ||
| result += ' ' + trim(rawUris[innerIdx + 1]); | ||
| } | ||
|
|
||
| // split the last item into uri and descriptor | ||
| var lastTuple = trim(rawUris[i * 2]).split(/\s/); | ||
|
|
||
| // sanitize the last uri | ||
| result += $$sanitizeUri(trim(lastTuple[0]), true); | ||
| result += $sce.getTrustedMediaUrl(trim(lastTuple[0])); | ||
|
|
||
| // and add the last descriptor if any | ||
| if (lastTuple.length === 2) { | ||
|
|
@@ -3268,14 +3275,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| } | ||
| var tag = nodeName_(node); | ||
| // All tags with src attributes require a RESOURCE_URL value, except for | ||
| // img and various html5 media tags. | ||
| // img and various html5 media tags, which require the MEDIA_URL context. | ||
| if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { | ||
| if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) { | ||
| return $sce.RESOURCE_URL; | ||
| } | ||
| return $sce.MEDIA_URL; | ||
| } else if (attrNormalizedName === 'xlinkHref') { | ||
| // Some xlink:href are okay, most aren't | ||
| if (tag === 'image') return $sce.MEDIA_URL; | ||
| if (tag === 'a') return $sce.URL; | ||
| return $sce.RESOURCE_URL; | ||
| } else if ( | ||
| // Some xlink:href are okay, most aren't | ||
| (attrNormalizedName === 'xlinkHref' && (tag !== 'image' && tag !== 'a')) || | ||
| // Formaction | ||
| (tag === 'form' && attrNormalizedName === 'action') || | ||
| // If relative URLs can go where they are not expected to, then | ||
|
|
@@ -3285,6 +3296,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |
| (tag === 'link' && attrNormalizedName === 'href') | ||
| ) { | ||
| return $sce.RESOURCE_URL; | ||
| } else if (tag === 'a' && (attrNormalizedName === 'href' || | ||
| attrNormalizedName === 'ngHref')) { | ||
| return $sce.URL; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -436,7 +436,7 @@ forEach(['src', 'srcset', 'href'], function(attrName) { | |
| // On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist | ||
| // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need | ||
| // to set the property as well to achieve the desired effect. | ||
| // We use attr[attrName] value since $set can sanitize the url. | ||
| // We use attr[attrName] value since $set might have sanitized the url. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on the commit message, I thought
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still sanitize |
||
| if (msie && propName) element.prop(propName, attr[name]); | ||
| }); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| * Private service to sanitize uris for links and images. Used by $compile and $sanitize. | ||
| */ | ||
| function $$SanitizeUriProvider() { | ||
|
|
||
| var aHrefSanitizationWhitelist = /^\s*(https?|s?ftp|mailto|tel|file):/, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a tiny quirk hiding there, which is probably without consequences: mailto and tel are in the URL whitelist, but not in the MEDIA_URL. This makes sense, but "safe under URL" is supposed to be a subset of "safe under MEDIA_URL" in my interpretation of the subclassing in the $sce. I don't think anyone cares either way.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I wondered about that. One is not really a "super-set" of the other but I needed both to be covered by |
||
| imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; | ||
|
|
||
|
|
@@ -14,12 +15,16 @@ function $$SanitizeUriProvider() { | |
| * Retrieves or overrides the default regular expression that is used for whitelisting of safe | ||
| * urls during a[href] sanitization. | ||
| * | ||
| * The sanitization is a security measure aimed at prevent XSS attacks via html links. | ||
| * The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links. | ||
| * | ||
| * Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring | ||
| * the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)` | ||
| * which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL. | ||
| * | ||
| * If the URL matches the `aHrefSanitizationWhitelist` regular expression, it is returned unchanged. | ||
| * | ||
| * Any url about to be assigned to a[href] via data-binding is first normalized and turned into | ||
| * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` | ||
| * regular expression. If a match is found, the original url is written into the dom. Otherwise, | ||
| * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. | ||
| * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written | ||
| * to the DOM it is inactive and potentially malicious code will not be executed. | ||
| * | ||
| * @param {RegExp=} regexp New regexp to whitelist urls with. | ||
| * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for | ||
|
|
@@ -39,12 +44,17 @@ function $$SanitizeUriProvider() { | |
| * Retrieves or overrides the default regular expression that is used for whitelisting of safe | ||
| * urls during img[src] sanitization. | ||
| * | ||
| * The sanitization is a security measure aimed at prevent XSS attacks via html links. | ||
| * The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links. | ||
| * | ||
| * Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring | ||
| * the $sce.MEDIA_URL security context. When interpolation occurs a call is made to | ||
| * `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize | ||
| * the potentially malicious URL. | ||
| * | ||
| * If the URL matches the `aImgSanitizationWhitelist` regular expression, it is returned unchanged. | ||
| * | ||
| * Any url about to be assigned to img[src] via data-binding is first normalized and turned into | ||
| * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` | ||
| * regular expression. If a match is found, the original url is written into the dom. Otherwise, | ||
| * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. | ||
| * If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written | ||
| * to the DOM it is inactive and potentially malicious code will not be executed. | ||
| * | ||
| * @param {RegExp=} regexp New regexp to whitelist urls with. | ||
| * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for | ||
|
|
@@ -59,10 +69,10 @@ function $$SanitizeUriProvider() { | |
| }; | ||
|
|
||
| this.$get = function() { | ||
| return function sanitizeUri(uri, isImage) { | ||
| var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; | ||
| var normalizedVal; | ||
| normalizedVal = urlResolve(uri && uri.trim()).href; | ||
| return function sanitizeUri(uri, isMediaUrl) { | ||
| // if (!uri) return uri; | ||
| var regex = isMediaUrl ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; | ||
| var normalizedVal = urlResolve(uri && uri.trim()).href; | ||
| if (normalizedVal !== '' && !normalizedVal.match(regex)) { | ||
| return 'unsafe:' + normalizedVal; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😱