-
Notifications
You must be signed in to change notification settings - Fork 27.4k
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'); | ||
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 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? 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" 😛) 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. | ||
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 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):/, | ||
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. 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.
😱