Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [day] [month] [year] [list]
Date: Tue, 14 Apr 2015 14:51:13 -0400
From: "Matthew Beale" <mbeale@...tlabs.com>
To: ember-security@...glegroups.com, oss-security@...ts.openwall.com
Subject: [CVE-2015-1866] Ember.js XSS Vulnerability With {{view "select"}}
 Options

[CVE-2015-1866] Ember.js XSS Vulnerability With {{view "select"}} 
Options

Data passed as the label of select options may not be escaped before 
being passed to the browser.

* Versions Affected: 1.10.0, 1.11.0, 1.11.1, 1.12.0 beta
* Not affected: Versions prior to 1.10.0
* Fixed Versions: 1.10.1, 1.11.2

Impact
-------

In general, Ember.js escapes or strips any user-supplied content before 
inserting it in strings that will be sent to innerHTML.  However, a 
change made to the implementation of the select view means that any 
user-supplied data bound to an option's label will not be escaped 
correctly.

In applications that use Ember's select view and pass user-supplied 
content to the label, a specially-crafted payload could execute 
arbitrary JavaScript in the context of the current domain ("XSS").

All users running an affected release and binding user-supplied data to 
the select options should either upgrade or use one of the workarounds 
immediately.

Releases
--------

Releases are available on emberjs.com/builds/#/tagged

Workarounds
-----------

Ensure that you escape any user-supplied value that you bind to an 
option label. For example, if you bind a label:

    {{view 'select' content=people optionLabelPath='content.name'}}

Ensure that you escape the `name` value of each item `people` using 
Ember.Handlebars.Utils.escapeExpression:

    var people = this.get('people');
    var peopleForSelect = people.map(function(person){
      var newPerson = Object.create(person);
      newPerson.name = Ember.Handlebars.escapeExpression(person.name);
      return newPerson;
    });
    this.set('peopleForSelect', peopleForSelect);

Credits
-------

This vulnerability was reported to us by Phillip Haines of Zestia. Many 
thanks for working with us on identifying the issue and on the advisory 
process.

Best,

-Matthew (Ember.js Core Team member)

http://madhatted.com :: @mixonic

diff --git a/packages/ember-htmlbars/lib/templates/select-option.hbs b/pa=
ckages/ember-htmlbars/lib/templates/select-option.hbs
new file mode 100644
index 0000000..6471e4e
--- /dev/null
+++ b/packages/ember-htmlbars/lib/templates/select-option.hbs
@@ -0,0 +1 @@
+{{~view.label~}}
diff --git a/packages/ember-views/lib/views/select.js b/packages/ember-vi=
ews/lib/views/select.js
index a68b58b..6a203ac 100644
--- a/packages/ember-views/lib/views/select.js
+++ b/packages/ember-views/lib/views/select.js
@@ -20,25 +20,12 @@ import { computed } from "ember-metal/computed";
 import { A as emberA } from "ember-runtime/system/native_array";
 import { observer } from "ember-metal/mixin";
 import { defineProperty } from "ember-metal/properties";
-import run from "ember-metal/run_loop";
 =

 import htmlbarsTemplate from "ember-htmlbars/templates/select";
+import selectOptionDefaultTemplate from "ember-htmlbars/templates/select=
-option";
 =

 var defaultTemplate =3D htmlbarsTemplate;
 =

-var selectOptionDefaultTemplate =3D {
-  isHTMLBars: true,
-  render: function(context, env, contextualElement) {
-    var lazyValue =3D context.getStream('view.label');
-
-    lazyValue.subscribe(context._wrapAsScheduled(function() {
-      run.scheduleOnce('render', context, 'rerender');
-    }));
-
-    return lazyValue.value();
-  }
-};
-
 var SelectOption =3D View.extend({
   instrumentDisplay: 'Ember.SelectOption',
 =

diff --git a/packages/ember-views/tests/views/select_test.js b/packages/e=
mber-views/tests/views/select_test.js
index 0452770..53762db 100644
--- a/packages/ember-views/tests/views/select_test.js
+++ b/packages/ember-views/tests/views/select_test.js
@@ -4,6 +4,7 @@ import run from "ember-metal/run_loop";
 import jQuery from "ember-views/system/jquery";
 import { map } from "ember-metal/enumerable_utils";
 import EventDispatcher from "ember-views/system/event_dispatcher";
+import SafeString from 'htmlbars-util/safe-string';
 =

 var trim =3D jQuery.trim;
 =

@@ -133,6 +134,44 @@ test("can specify the property path for an option's =
label and value", function()
   deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
 });
 =

+QUnit.test("XSS: does not escape label value when it is a SafeString", f=
unction() {
+  select.set('content', Ember.A([
+    { id: 1, firstName: new SafeString('<p>Yehuda</p>') },
+    { id: 2, firstName: new SafeString('<p>Tom</p>') }
+  ]));
+
+  select.set('optionLabelPath', 'content.firstName');
+  select.set('optionValuePath', 'content.id');
+
+  append();
+
+  equal(select.$('option').length, 2, "Should have two options");
+  equal(select.$('option[value=3D1] b').length, 1, "Should have child el=
ements");
+
+  // IE 8 adds whitespace
+  equal(trim(select.$().text()), "YehudaTom", "Options should have conte=
nt");
+  deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
+});
+
+QUnit.test("XSS: escapes label value content", function() {
+  select.set('content', Ember.A([
+    { id: 1, firstName: '<p>Yehuda</p>' },
+    { id: 2, firstName: '<p>Tom</p>' }
+  ]));
+
+  select.set('optionLabelPath', 'content.firstName');
+  select.set('optionValuePath', 'content.id');
+
+  append();
+
+  equal(select.$('option').length, 2, "Should have two options");
+  equal(select.$('option[value=3D1] b').length, 0, "Should have no child=
 elements");
+
+  // IE 8 adds whitespace
+  equal(trim(select.$().text()), "<p>Yehuda</p><p>Tom</p>", "Options sho=
uld have content");
+  deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
+});
+
 test("can retrieve the current selected option when multiple=3Dfalse", f=
unction() {
   var yehuda =3D { id: 1, firstName: 'Yehuda' };
   var tom =3D { id: 2, firstName: 'Tom' };


diff --git a/packages/ember-htmlbars/lib/templates/select-option.hbs b/pa=
ckages/ember-htmlbars/lib/templates/select-option.hbs
new file mode 100644
index 0000000..6471e4e
--- /dev/null
+++ b/packages/ember-htmlbars/lib/templates/select-option.hbs
@@ -0,0 +1 @@
+{{~view.label~}}
diff --git a/packages/ember-views/lib/views/select.js b/packages/ember-vi=
ews/lib/views/select.js
index 721da86..3583904 100644
--- a/packages/ember-views/lib/views/select.js
+++ b/packages/ember-views/lib/views/select.js
@@ -21,26 +21,12 @@ import { computed } from "ember-metal/computed";
 import { A as emberA } from "ember-runtime/system/native_array";
 import { observer } from "ember-metal/mixin";
 import { defineProperty } from "ember-metal/properties";
-import run from "ember-metal/run_loop";
 =

 import htmlbarsTemplate from "ember-htmlbars/templates/select";
+import selectOptionDefaultTemplate from "ember-htmlbars/templates/select=
-option";
 =

 var defaultTemplate =3D htmlbarsTemplate;
 =

-var selectOptionDefaultTemplate =3D {
-  isHTMLBars: true,
-  revision: 'Ember@...SION_STRING_PLACEHOLDER',
-  render: function(context, env, contextualElement) {
-    var lazyValue =3D context.getStream('view.label');
-
-    lazyValue.subscribe(context._wrapAsScheduled(function() {
-      run.scheduleOnce('render', context, 'rerender');
-    }));
-
-    return lazyValue.value();
-  }
-};
-
 var SelectOption =3D View.extend({
   instrumentDisplay: 'Ember.SelectOption',
 =

diff --git a/packages/ember-views/tests/views/select_test.js b/packages/e=
mber-views/tests/views/select_test.js
index eda11bd..8150e31 100644
--- a/packages/ember-views/tests/views/select_test.js
+++ b/packages/ember-views/tests/views/select_test.js
@@ -4,6 +4,7 @@ import run from "ember-metal/run_loop";
 import jQuery from "ember-views/system/jquery";
 import { map } from "ember-metal/enumerable_utils";
 import EventDispatcher from "ember-views/system/event_dispatcher";
+import SafeString from 'htmlbars-util/safe-string';
 =

 var trim =3D jQuery.trim;
 =

@@ -133,6 +134,44 @@ QUnit.test("can specify the property path for an opt=
ion's label and value", func
   deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
 });
 =

+QUnit.test("XSS: does not escape label value when it is a SafeString", f=
unction() {
+  select.set('content', Ember.A([
+    { id: 1, firstName: new SafeString('<p>Yehuda</p>') },
+    { id: 2, firstName: new SafeString('<p>Tom</p>') }
+  ]));
+
+  select.set('optionLabelPath', 'content.firstName');
+  select.set('optionValuePath', 'content.id');
+
+  append();
+
+  equal(select.$('option').length, 2, "Should have two options");
+  equal(select.$('option[value=3D1] b').length, 1, "Should have child el=
ements");
+
+  // IE 8 adds whitespace
+  equal(trim(select.$().text()), "YehudaTom", "Options should have conte=
nt");
+  deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
+});
+
+QUnit.test("XSS: escapes label value content", function() {
+  select.set('content', Ember.A([
+    { id: 1, firstName: '<p>Yehuda</p>' },
+    { id: 2, firstName: '<p>Tom</p>' }
+  ]));
+
+  select.set('optionLabelPath', 'content.firstName');
+  select.set('optionValuePath', 'content.id');
+
+  append();
+
+  equal(select.$('option').length, 2, "Should have two options");
+  equal(select.$('option[value=3D1] b').length, 0, "Should have no child=
 elements");
+
+  // IE 8 adds whitespace
+  equal(trim(select.$().text()), "<p>Yehuda</p><p>Tom</p>", "Options sho=
uld have content");
+  deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
+});
+
 QUnit.test("can retrieve the current selected option when multiple=3Dfal=
se", function() {
   var yehuda =3D { id: 1, firstName: 'Yehuda' };
   var tom =3D { id: 2, firstName: 'Tom' };


diff --git a/packages/ember-htmlbars/lib/templates/select-option.hbs b/pa=
ckages/ember-htmlbars/lib/templates/select-option.hbs
new file mode 100644
index 0000000..6471e4e
--- /dev/null
+++ b/packages/ember-htmlbars/lib/templates/select-option.hbs
@@ -0,0 +1 @@
+{{~view.label~}}
diff --git a/packages/ember-views/lib/views/select.js b/packages/ember-vi=
ews/lib/views/select.js
index f5de69d..191e813 100644
--- a/packages/ember-views/lib/views/select.js
+++ b/packages/ember-views/lib/views/select.js
@@ -21,26 +21,12 @@ import { computed } from "ember-metal/computed";
 import { A as emberA } from "ember-runtime/system/native_array";
 import { observer } from "ember-metal/mixin";
 import { defineProperty } from "ember-metal/properties";
-import run from "ember-metal/run_loop";
 =

 import htmlbarsTemplate from "ember-htmlbars/templates/select";
+import selectOptionDefaultTemplate from "ember-htmlbars/templates/select=
-option";
 =

 var defaultTemplate =3D htmlbarsTemplate;
 =

-var selectOptionDefaultTemplate =3D {
-  isHTMLBars: true,
-  revision: 'Ember@...SION_STRING_PLACEHOLDER',
-  render(context, env, contextualElement) {
-    var lazyValue =3D context.getStream('view.label');
-
-    lazyValue.subscribe(context._wrapAsScheduled(function() {
-      run.scheduleOnce('render', context, 'rerender');
-    }));
-
-    return lazyValue.value();
-  }
-};
-
 var SelectOption =3D View.extend({
   instrumentDisplay: 'Ember.SelectOption',
 =

diff --git a/packages/ember-views/tests/views/select_test.js b/packages/e=
mber-views/tests/views/select_test.js
index d9fb500..af50933 100644
--- a/packages/ember-views/tests/views/select_test.js
+++ b/packages/ember-views/tests/views/select_test.js
@@ -4,6 +4,7 @@ import run from "ember-metal/run_loop";
 import jQuery from "ember-views/system/jquery";
 import { map } from "ember-metal/enumerable_utils";
 import EventDispatcher from "ember-views/system/event_dispatcher";
+import SafeString from 'htmlbars-util/safe-string';
 =

 var trim =3D jQuery.trim;
 =

@@ -133,6 +134,44 @@ QUnit.test("can specify the property path for an opt=
ion's label and value", func
   deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
 });
 =

+QUnit.test("XSS: does not escape label value when it is a SafeString", f=
unction() {
+  select.set('content', Ember.A([
+    { id: 1, firstName: new SafeString('<p>Yehuda</p>') },
+    { id: 2, firstName: new SafeString('<p>Tom</p>') }
+  ]));
+
+  select.set('optionLabelPath', 'content.firstName');
+  select.set('optionValuePath', 'content.id');
+
+  append();
+
+  equal(select.$('option').length, 2, "Should have two options");
+  equal(select.$('option[value=3D1] b').length, 1, "Should have child el=
ements");
+
+  // IE 8 adds whitespace
+  equal(trim(select.$().text()), "YehudaTom", "Options should have conte=
nt");
+  deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
+});
+
+QUnit.test("XSS: escapes label value content", function() {
+  select.set('content', Ember.A([
+    { id: 1, firstName: '<p>Yehuda</p>' },
+    { id: 2, firstName: '<p>Tom</p>' }
+  ]));
+
+  select.set('optionLabelPath', 'content.firstName');
+  select.set('optionValuePath', 'content.id');
+
+  append();
+
+  equal(select.$('option').length, 2, "Should have two options");
+  equal(select.$('option[value=3D1] b').length, 0, "Should have no child=
 elements");
+
+  // IE 8 adds whitespace
+  equal(trim(select.$().text()), "<p>Yehuda</p><p>Tom</p>", "Options sho=
uld have content");
+  deepEqual(map(select.$('option').toArray(), function(el) { return jQue=
ry(el).attr('value'); }), ["1", "2"], "Options should have values");
+});
+
 QUnit.test("can retrieve the current selected option when multiple=3Dfal=
se", function() {
   var yehuda =3D { id: 1, firstName: 'Yehuda' };
   var tom =3D { id: 2, firstName: 'Tom' };

Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.