2013-03-18

Symbol DoS vulnerability in Active Record

There is a symbol DoS vulnerability in Active Record. This vulnerability has been assigned the CVE identifier
CVE-2013-1854.

Versions Affected:  3.2.x, 3.1.x, 2.3.x
Not affected:       3.0.x
Fixed Versions:     3.2.13, 3.1.12, 2.3.18

Impact
------
When a hash is provided as the find value for a query, the keys of the hash may be converted to symbols.  In this
example,

User.where(:name => { 'foo' => 'bar' })

the string 'foo' will be converted to a symbol.  Impacted code will look something like this:

User.where(:name => params[:name])

Carefully crafted requests can coerce `params[:name]` to return a hash, and the keys to that hash may be converted to
symbols.

All users running an affected release should either upgrade or use one of the work arounds immediately.

Releases
--------
The 3.2.13 and 3.1.12 releases are available at the normal locations.

Workarounds
-----------
To work around this problem, change code that looks like this:

User.where(:name => params[:name])

to code like this:

User.where(:name => params[:name].to_s)

Patches
-------
To aid users who aren't able to upgrade immediately we have provided patches for the two supported release series.
They are in git-am format and consist of a single changeset.

* 3-2-attribute_symbols.patch - Patch for 3.2 series
* 3-1-attribute_symbols.patch - Patch for 3.1 series
* 2-3-attribute_symbols.patch - Patch for 2.3 series

Please note that only the 3.1.x and 3.2.x series are supported at present.  Users of earlier unsupported releases are
advised to upgrade as soon as possible as we cannot guarantee the continued availability of security fixes for
unsupported releases.

Credits
-------

Thanks to Ben Murphy for reporting this!

--
Aaron Patterson
http://tenderlovemaking.com/

2-3-attribute_symbols.patch
Description:

From be764d2c9ce1c8f980f2cf3bf021bdbd5d05f605 Mon Sep 17 00:00:00 2001
From: Aaron Patterson

Date: Tue, 5 Mar 2013 14:52:08 -0800
Subject: [PATCH] stop calling to_sym when building arel nodes

---
activerecord/lib/active_record/base.rb                               | 2 +-
activerecord/lib/active_record/reflection.rb                         | 2 +-
.../lib/active_support/core_ext/class/inheritable_attributes.rb      | 5 +++++
3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c11b702..894ca6b 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2307,7 +2307,7 @@ module ActiveRecord #:nodoc:
def expand_hash_conditions_for_aggregates(attrs)
expanded_attrs = {}
attrs.each do |attr, value|
-            unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
+            unless (aggregation = reflect_on_aggregation(attr)).nil?
mapping = aggregate_mapping(aggregation)
mapping.each do |field_attr, aggregate_attr|
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 13bcb8b..a07d442 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -18,7 +18,7 @@ module ActiveRecord
when :composed_of
reflection = AggregateReflection.new(macro, name, options, active_record)
end
-        write_inheritable_hash :reflections, name => reflection
+        write_inheritable_hiwa :reflections, name => reflection
reflection
end

diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index 1794afe..d86eab8 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -109,6 +109,11 @@ class Class # :nodoc:
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
end

+  def write_inheritable_hiwa(key, hash)
+    write_inheritable_attribute(key, {}.with_indifferent_access) if read_inheritable_attribute(key).nil?
+    write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
+  end
+
def read_inheritable_attribute(key)
inheritable_attributes[key]
end
--
1.8.1.1

3-1-attribute_symbols.patch
Description:

From c74fba21f708422dc818d1f999d0d7577a73eff2 Mon Sep 17 00:00:00 2001
From: Aaron Patterson

Date: Tue, 5 Mar 2013 14:52:08 -0800
Subject: [PATCH] stop calling to_sym when building arel nodes

---
activerecord/lib/active_record/relation.rb                   |  2 +-
activerecord/lib/active_record/relation/predicate_builder.rb |  2 +-
activerecord/test/cases/method_scoping_test.rb               | 10 +++++-----
activerecord/test/cases/relation_test.rb                     |  6 +++---
4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index b0c9ef6..a9fd5ee 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -403,7 +403,7 @@ module ActiveRecord
node.left.relation.name == table_name
}

-      Hash[equalities.map { |where| [where.left.name, where.right] }]
+      Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
end

def scope_for_create
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 21d7589..8487e0f 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,7 +20,7 @@ module ActiveRecord
table = Arel::Table.new(table_name, engine)
end

-          attribute = table[column.to_sym]
+          attribute = table[column]

case value
when ActiveRecord::Relation
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 0ab4f30..ac84306 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -212,14 +212,14 @@ class MethodScopingTest
"VerySpecialComment"}, relation.send(:scope_for_create))
+    assert_equal({'type' => "VerySpecialComment"}, relation.send(:scope_for_create))
end

def test_scoped_create
new_comment = nil

VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do
-      assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
+      assert_equal({'post_id' => 1, 'type' => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
new_comment = VerySpecialComment.create :body => "Wonderful world"
end

@@ -228,7 +228,7 @@ class MethodScopingTest
"but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do
-      assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create)
+      assert_equal({'body' => "but Who's Buying?"}, Comment.scoped.scope_for_create)
end
end

@@ -441,7 +441,7 @@ class NestedScopingTest
{ :post_id => 1}) do
Comment.send(:with_scope, :create => { :post_id => 2}) do
-        assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create))
+        assert_equal({'post_id' => 2}, Comment.scoped.send(:scope_for_create))
comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
end
end
@@ -453,7 +453,7 @@ class NestedScopingTest
{ :body => "Hey guys, nested scopes are broken. Please fix!" }) do
Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do
-        assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create))
+        assert_equal({'post_id' => 1}, Comment.scoped.send(:scope_for_create))
assert_blank Comment.new.body
comment = Comment.create :body => "Hey guys"
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index b23ead6..eee2979 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -71,7 +71,7 @@ module ActiveRecord
def test_has_values
relation = Relation.new Post, Post.arel_table
relation.where_values
10}, relation.where_values_hash)
+      assert_equal({'id' => 10}, relation.where_values_hash)
end

def test_values_wrong_table
@@ -101,7 +101,7 @@ module ActiveRecord

def test_create_with_value
relation = Relation.new Post, Post.arel_table
-      hash = { :hello => 'world' }
+      hash = { 'hello' => 'world' }
relation.create_with_value = hash
assert_equal hash, relation.scope_for_create
end
@@ -110,7 +110,7 @@ module ActiveRecord
relation = Relation.new Post, Post.arel_table
relation.where_values
'world'}
-      assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
+      assert_equal({'hello' => 'world', 'id' => 10}, relation.scope_for_create)
end

# FIXME: is this really wanted or expected behavior?
--
1.8.1.1

3-2-attribute_symbols.patch
Description:

From 35fc5e67961e7242a426f0a38f618e6e595ceec6 Mon Sep 17 00:00:00 2001
From: Aaron Patterson

Date: Tue, 5 Mar 2013 14:52:08 -0800
Subject: [PATCH] stop calling to_sym when building arel nodes

---
activerecord/lib/active_record/relation.rb                   |  2 +-
activerecord/lib/active_record/relation/predicate_builder.rb |  2 +-
activerecord/test/cases/method_scoping_test.rb               | 10 +++++-----
activerecord/test/cases/relation_test.rb                     |  6 +++---
4 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 4b3b30d..ae1a575 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -464,7 +464,7 @@ module ActiveRecord
node.left.relation.name == table_name
}

-      Hash[equalities.map { |where| [where.left.name, where.right] }]
+      Hash[equalities.map { |where| [where.left.name, where.right] }].with_indifferent_access
end

def scope_for_create
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index b31fdfd..413b81c 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,7 +20,7 @@ module ActiveRecord
table = Arel::Table.new(table_name, engine)
end

-          attribute = table[column.to_sym]
+          attribute = table[column]

case value
when ActiveRecord::Relation
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 0ab4f30..ac84306 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -212,14 +212,14 @@ class MethodScopingTest
"VerySpecialComment"}, relation.send(:scope_for_create))
+    assert_equal({'type' => "VerySpecialComment"}, relation.send(:scope_for_create))
end

def test_scoped_create
new_comment = nil

VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do
-      assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
+      assert_equal({'post_id' => 1, 'type' => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
new_comment = VerySpecialComment.create :body => "Wonderful world"
end

@@ -228,7 +228,7 @@ class MethodScopingTest
"but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do
-      assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create)
+      assert_equal({'body' => "but Who's Buying?"}, Comment.scoped.scope_for_create)
end
end

@@ -441,7 +441,7 @@ class NestedScopingTest
{ :post_id => 1}) do
Comment.send(:with_scope, :create => { :post_id => 2}) do
-        assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create))
+        assert_equal({'post_id' => 2}, Comment.scoped.send(:scope_for_create))
comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
end
end
@@ -453,7 +453,7 @@ class NestedScopingTest
{ :body => "Hey guys, nested scopes are broken. Please fix!" }) do
Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do
-        assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create))
+        assert_equal({'post_id' => 1}, Comment.scoped.send(:scope_for_create))
assert_blank Comment.new.body
comment = Comment.create :body => "Hey guys"
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 7a75a84..6efdeac 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -71,7 +71,7 @@ module ActiveRecord
def test_has_values
relation = Relation.new Post, Post.arel_table
relation.where_values
10}, relation.where_values_hash)
+      assert_equal({'id' => 10}, relation.where_values_hash)
end

def test_values_wrong_table
@@ -101,7 +101,7 @@ module ActiveRecord

def test_create_with_value
relation = Relation.new Post, Post.arel_table
-      hash = { :hello => 'world' }
+      hash = { 'hello' => 'world' }
relation.create_with_value = hash
assert_equal hash, relation.scope_for_create
end
@@ -110,7 +110,7 @@ module ActiveRecord
relation = Relation.new Post, Post.arel_table
relation.where_values
'world'}
-      assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
+      assert_equal({'hello' => 'world', 'id' => 10}, relation.scope_for_create)
end

# FIXME: is this really wanted or expected behavior?
--
1.8.1.1

Show more