Skip to content

Commit 676484c

Browse files
mixonicrwjblue
authored andcommitted
[BUGFIX beta] Sanitize body, link, img attributes
1 parent 724142b commit 676484c

File tree

8 files changed

+210
-108
lines changed

8 files changed

+210
-108
lines changed

packages/ember-htmlbars/lib/attr_nodes.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66
import QuotedAttrNode from "ember-htmlbars/attr_nodes/quoted";
77
import UnquotedAttrNode from "ember-htmlbars/attr_nodes/unquoted";
88
import UnquotedNonpropertyAttrNode from "ember-htmlbars/attr_nodes/unquoted_nonproperty";
9-
import HrefAttrNode from "ember-htmlbars/attr_nodes/href";
9+
import { badAttributes } from "ember-views/system/sanitize_attribute_value";
10+
import SanitizedAttrNode from "ember-htmlbars/attr_nodes/sanitized";
1011
import { create as o_create } from "ember-metal/platform";
1112
import { normalizeProperty } from "ember-htmlbars/attr_nodes/utils";
1213

1314
var svgNamespaceURI = 'http://www.w3.org/2000/svg';
1415

1516
var unquotedAttrNodeTypes = o_create(null);
1617
unquotedAttrNodeTypes['class'] = UnquotedNonpropertyAttrNode;
17-
unquotedAttrNodeTypes['href'] = HrefAttrNode;
1818

1919
var quotedAttrNodeTypes = o_create(null);
20-
quotedAttrNodeTypes['href'] = HrefAttrNode;
20+
21+
for (var attrName in badAttributes) {
22+
unquotedAttrNodeTypes[attrName] = SanitizedAttrNode;
23+
quotedAttrNodeTypes[attrName] = SanitizedAttrNode;
24+
}
2125

2226
export default function attrNodeTypeFor(attrName, element, quoted) {
2327
var result;

packages/ember-htmlbars/lib/attr_nodes/href.js renamed to packages/ember-htmlbars/lib/attr_nodes/sanitized.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { create as o_create } from "ember-metal/platform";
88
import { chainStream, read } from "ember-metal/streams/utils";
99
import sanitizeAttributeValue from "ember-views/system/sanitize_attribute_value";
1010

11-
function HrefAttrNode(element, attrName, attrValue, dom) {
11+
function SanitizedAttrNode(element, attrName, attrValue, dom) {
1212
var sanitizedValue = chainStream(attrValue, function(){
1313
var unsafeValue = read(attrValue);
1414
var safeValue = sanitizeAttributeValue(element, attrName, unsafeValue);
@@ -19,6 +19,6 @@ function HrefAttrNode(element, attrName, attrValue, dom) {
1919
this.init(element, attrName, sanitizedValue, dom);
2020
}
2121

22-
HrefAttrNode.prototype = o_create(SimpleAttrNode.prototype);
22+
SanitizedAttrNode.prototype = o_create(SimpleAttrNode.prototype);
2323

24-
export default HrefAttrNode;
24+
export default SanitizedAttrNode;

packages/ember-htmlbars/lib/helpers/bind-attr.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import Ember from "ember-metal/core"; // Ember.assert
88
import { fmt } from "ember-runtime/system/string";
99
import QuotedAttrNode from "ember-htmlbars/attr_nodes/quoted";
1010
import LegacyBindAttrNode from "ember-htmlbars/attr_nodes/legacy_bind";
11-
import HrefAttrNode from "ember-htmlbars/attr_nodes/href";
11+
import { badAttributes } from "ember-views/system/sanitize_attribute_value";
12+
import SanitizedAttrNode from "ember-htmlbars/attr_nodes/sanitized";
1213
import keys from "ember-metal/keys";
1314
import helpers from "ember-htmlbars/helpers";
1415
import { map } from 'ember-metal/enumerable_utils';
@@ -178,8 +179,8 @@ function bindAttrHelper(params, hash, options, env) {
178179
);
179180
lazyValue = view.getStream(path);
180181
}
181-
if (attr === 'href') {
182-
new HrefAttrNode(element, attr, lazyValue, env.dom);
182+
if (badAttributes[attr]) {
183+
new SanitizedAttrNode(element, attr, lazyValue, env.dom);
183184
} else {
184185
new LegacyBindAttrNode(element, attr, lazyValue, env.dom);
185186
}
Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
/* jshint scripturl:true */
2-
31
import EmberView from "ember-views/views/view";
42
import run from "ember-metal/run_loop";
53
import compile from "ember-template-compiler/system/compile";
64
import { equalInnerHTML } from "htmlbars-test-helpers";
7-
import { SafeString } from "ember-htmlbars/utils/string";
85

96
var view;
107

@@ -33,48 +30,4 @@ test("href is set", function() {
3330
"attribute is output");
3431
});
3532

36-
test("href is sanitized when using blacklisted protocol", function() {
37-
view = EmberView.create({
38-
context: {url: 'javascript://example.com'},
39-
template: compile("<a href={{url}}></a>")
40-
});
41-
appendView(view);
42-
43-
equalInnerHTML(view.element, '<a href="unsafe:javascript://example.com"></a>',
44-
"attribute is output");
45-
});
46-
47-
test("href is sanitized when using quoted non-whitelisted protocol", function() {
48-
view = EmberView.create({
49-
context: {url: 'javascript://example.com'},
50-
template: compile("<a href='{{url}}'></a>")
51-
});
52-
appendView(view);
53-
54-
equalInnerHTML(view.element, '<a href="unsafe:javascript://example.com"></a>',
55-
"attribute is output");
56-
});
57-
58-
test("href is not sanitized when using non-whitelisted protocol with a SafeString", function() {
59-
view = EmberView.create({
60-
context: {url: new SafeString('javascript://example.com')},
61-
template: compile("<a href={{url}}></a>")
62-
});
63-
appendView(view);
64-
65-
equalInnerHTML(view.element, '<a href="javascript://example.com"></a>',
66-
"attribute is output");
67-
});
68-
69-
test("href is sanitized when using quoted+concat non-whitelisted protocol", function() {
70-
view = EmberView.create({
71-
context: {protocol: 'javascript:', path: '//example.com'},
72-
template: compile("<a href='{{protocol}}{{path}}'></a>")
73-
});
74-
appendView(view);
75-
76-
equalInnerHTML(view.element, '<a href="unsafe:javascript://example.com"></a>',
77-
"attribute is output");
78-
});
79-
8033
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/* jshint scripturl:true */
2+
3+
import EmberView from "ember-views/views/view";
4+
import compile from "ember-htmlbars/system/compile";
5+
import { SafeString } from "ember-htmlbars/utils/string";
6+
import { runAppend, runDestroy } from "ember-runtime/tests/utils";
7+
8+
var view;
9+
10+
QUnit.module("ember-htmlbars: sanitized attribute", {
11+
teardown: function(){
12+
runDestroy(view);
13+
}
14+
});
15+
16+
if (Ember.FEATURES.isEnabled('ember-htmlbars-attribute-syntax')) {
17+
18+
var badTags = [
19+
{ tag: 'a', attr: 'href',
20+
unquotedTemplate: compile("<a href={{url}}></a>"),
21+
quotedTemplate: compile("<a href='{{url}}'></a>"),
22+
multipartTemplate: compile("<a href='{{protocol}}{{path}}'></a>") },
23+
{ tag: 'body', attr: 'background',
24+
unquotedTemplate: compile("<body background={{url}}></body>"),
25+
quotedTemplate: compile("<body background='{{url}}'></body>"),
26+
multipartTemplate: compile("<body background='{{protocol}}{{path}}'></body>") },
27+
{ tag: 'link', attr: 'href',
28+
unquotedTemplate: compile("<link href={{url}}>"),
29+
quotedTemplate: compile("<link href='{{url}}'>"),
30+
multipartTemplate: compile("<link href='{{protocol}}{{path}}'>") },
31+
{ tag: 'img', attr: 'src',
32+
unquotedTemplate: compile("<img src={{url}}>"),
33+
quotedTemplate: compile("<img src='{{url}}'>"),
34+
multipartTemplate: compile("<img src='{{protocol}}{{path}}'>") },
35+
];
36+
37+
for (var i=0, l=badTags.length; i<l; i++) {
38+
(function(){
39+
var subject = badTags[i];
40+
41+
test(subject.tag +" "+subject.attr+" is sanitized when using blacklisted protocol", function() {
42+
view = EmberView.create({
43+
context: {url: 'javascript://example.com'},
44+
template: subject.unquotedTemplate
45+
});
46+
runAppend(view);
47+
48+
equal( view.element.firstChild.getAttribute(subject.attr),
49+
"unsafe:javascript://example.com",
50+
"attribute is output" );
51+
});
52+
53+
test(subject.tag +" "+subject.attr+" is sanitized when using quoted non-whitelisted protocol", function() {
54+
view = EmberView.create({
55+
context: {url: 'javascript://example.com'},
56+
template: subject.quotedTemplate
57+
});
58+
runAppend(view);
59+
60+
equal( view.element.firstChild.getAttribute(subject.attr),
61+
"unsafe:javascript://example.com",
62+
"attribute is output" );
63+
});
64+
65+
test(subject.tag +" "+subject.attr+" is not sanitized when using non-whitelisted protocol with a SafeString", function() {
66+
view = EmberView.create({
67+
context: {url: new SafeString('javascript://example.com')},
68+
template: subject.unquotedTemplate
69+
});
70+
71+
try {
72+
runAppend(view);
73+
74+
equal( view.element.firstChild.getAttribute(subject.attr),
75+
"javascript://example.com",
76+
"attribute is output" );
77+
} catch(e) {
78+
// IE does not allow javascript: to be set on img src
79+
ok(true, 'caught exception '+e);
80+
}
81+
});
82+
83+
test(subject.tag+" "+subject.attr+" is sanitized when using quoted+concat non-whitelisted protocol", function() {
84+
view = EmberView.create({
85+
context: {protocol: 'javascript:', path: '//example.com'},
86+
template: subject.multipartTemplate
87+
});
88+
runAppend(view);
89+
90+
equal( view.element.firstChild.getAttribute(subject.attr),
91+
"unsafe:javascript://example.com",
92+
"attribute is output" );
93+
});
94+
95+
})(); //jshint ignore:line
96+
}
97+
98+
}

packages/ember-htmlbars/tests/helpers/bind_attr_test.js

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import { equalInnerHTML } from "htmlbars-test-helpers";
1515

1616
import helpers from "ember-htmlbars/helpers";
1717
import compile from "ember-template-compiler/system/compile";
18-
import { SafeString } from "ember-htmlbars/utils/string";
19-
2018
var view;
2119

2220
var originalLookup = Ember.lookup;
@@ -530,52 +528,3 @@ test("property before didInsertElement", function() {
530528
runAppend(view);
531529
equal(matchingElement.length, 1, 'element is in the DOM when didInsertElement');
532530
});
533-
534-
test("XSS - should not bind unsafe href values", function() {
535-
/* jshint scripturl:true */
536-
537-
view = EmberView.create({
538-
template: compile('<a {{bind-attr href=view.badValue}}>wat</a>'),
539-
badValue: "javascript:(function() { window.bar = 'NOOOOOOOOO!!!!!!!';})();"
540-
});
541-
542-
runAppend(view);
543-
544-
var element = view.$('a')[0];
545-
546-
notEqual(element.protocol, 'javascript:');
547-
});
548-
549-
test("XSS - should not bind unsafe href values on rerender", function() {
550-
/* jshint scripturl:true */
551-
552-
view = EmberView.create({
553-
template: compile('<a {{bind-attr href=view.badValue}}>wat</a>'),
554-
badValue: "/sunshine/and/rainbows"
555-
});
556-
557-
runAppend(view);
558-
559-
var element = view.$('a')[0];
560-
561-
notEqual(element.protocol, 'javascript:', 'precond - badValue is normal');
562-
563-
run(view, 'set', 'badValue', 'javascript:alert("XSS")');
564-
565-
notEqual(element.protocol, 'javascript:');
566-
});
567-
568-
test("should bind unsafe href values if they are SafeString", function() {
569-
/* jshint scripturl:true */
570-
571-
view = EmberView.create({
572-
template: compile('<a {{bind-attr href=view.badValue}}>wat</a>'),
573-
badValue: new SafeString("javascript:(function() { window.bar = 'NOOOOOOOOO!!!!!!!';})();")
574-
});
575-
576-
runAppend(view);
577-
578-
var element = view.$('a')[0];
579-
580-
equal(element.protocol, 'javascript:');
581-
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* jshint scripturl:true */
2+
3+
import EmberView from "ember-views/views/view";
4+
import compile from "ember-htmlbars/system/compile";
5+
import run from "ember-metal/run_loop";
6+
import { SafeString } from "ember-htmlbars/utils/string";
7+
import { runAppend, runDestroy } from "ember-runtime/tests/utils";
8+
9+
var view;
10+
11+
QUnit.module("ember-htmlbars: sanitized attribute", {
12+
teardown: function(){
13+
runDestroy(view);
14+
}
15+
});
16+
17+
var badTags = [
18+
{ tag: 'a', attr: 'href',
19+
template: compile('<a {{bind-attr href=view.badValue}}></a>') },
20+
{ tag: 'body', attr: 'background',
21+
template: compile('<body {{bind-attr background=view.badValue}}></body>') },
22+
{ tag: 'link', attr: 'href',
23+
template: compile('<link {{bind-attr href=view.badValue}}>') },
24+
{ tag: 'img', attr: 'src',
25+
template: compile('<img {{bind-attr src=view.badValue}}>') }
26+
];
27+
28+
for (var i=0, l=badTags.length; i<l; i++) {
29+
(function(){
30+
var tagName = badTags[i].tag;
31+
var attr = badTags[i].attr;
32+
var template = badTags[i].template;
33+
34+
test("XSS - should not bind unsafe "+tagName+" "+attr+" values", function() {
35+
view = EmberView.create({
36+
template: template,
37+
badValue: "javascript:alert('XSS')"
38+
});
39+
40+
runAppend(view);
41+
42+
equal( view.element.firstChild.getAttribute(attr),
43+
"unsafe:javascript:alert('XSS')",
44+
"attribute is output" );
45+
});
46+
47+
test("XSS - should not bind unsafe "+tagName+" "+attr+" values on rerender", function() {
48+
view = EmberView.create({
49+
template: template,
50+
badValue: "/sunshine/and/rainbows"
51+
});
52+
53+
runAppend(view);
54+
55+
equal( view.element.firstChild.getAttribute(attr),
56+
"/sunshine/and/rainbows",
57+
"attribute is output" );
58+
59+
run(view, 'set', 'badValue', "javascript:alert('XSS')");
60+
61+
equal( view.element.firstChild.getAttribute(attr),
62+
"unsafe:javascript:alert('XSS')",
63+
"attribute is output" );
64+
});
65+
66+
test("should bind unsafe "+tagName+" "+attr+" values if they are SafeString", function() {
67+
view = EmberView.create({
68+
template: template,
69+
badValue: new SafeString("javascript:alert('XSS')")
70+
});
71+
72+
try {
73+
runAppend(view);
74+
75+
equal( view.element.firstChild.getAttribute(attr),
76+
"javascript:alert('XSS')",
77+
"attribute is output" );
78+
} catch(e) {
79+
// IE does not allow javascript: to be set on img src
80+
ok(true, 'caught exception '+e);
81+
}
82+
});
83+
})(); //jshint ignore:line
84+
}

packages/ember-views/lib/system/sanitize_attribute_value.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ var badProtocols = {
66
'vbscript:': true
77
};
88

9+
var badTags = {
10+
'A': true,
11+
'BODY': true,
12+
'LINK': true,
13+
'IMG': true
14+
};
15+
16+
export var badAttributes = {
17+
'href': true,
18+
'src': true,
19+
'background': true
20+
};
21+
922
export default function sanitizeAttributeValue(element, attribute, value) {
1023
var tagName;
1124

@@ -23,7 +36,7 @@ export default function sanitizeAttributeValue(element, attribute, value) {
2336
return value.toHTML();
2437
}
2538

26-
if ((tagName === null || tagName === 'A') && attribute === 'href') {
39+
if ((tagName === null || badTags[tagName]) && badAttributes[attribute]) {
2740
parsingNode.href = value;
2841

2942
if (badProtocols[parsingNode.protocol] === true) {

0 commit comments

Comments
 (0)