[prev in list] [next in list] [prev in thread] [next in thread] 

List:       oss-security
Subject:    [oss-security] SQL Injection Vulnerability in Ruby on Rails (CVE-2012-2661)
From:       Aaron Patterson <tenderlove () ruby-lang ! org>
Date:       2012-05-31 19:16:56
Message-ID: 20120531191656.GC79783 () higgins ! local
[Download RAW message or body]

[Attachment #2 (multipart/mixed)]


SQL Injection Vulnerability in Ruby on Rails

There is a SQL injection vulnerability in Active Record, version 3.0 and later. This \
vulnerability has been assigned the CVE identifier CVE-2012-2661.

Versions Affected:  3.0.0 and ALL later versions
Not affected:       2.3.14
Fixed Versions:     3.2.4, 3.1.5, 3.0.13

Impact 
------ 
Due to the way Active Record handles nested query parameters, an attacker can use a specially \
crafted request to inject some forms of SQL into your application's SQL queries.

All users running an affected release should upgrade immediately.

Impacted code directly passes request params to the `where` method of an ActiveRecord class \
like this:

    Post.where(:id => params[:id]).all

An attacker can make a request that causes `params[:id]` to return a specially crafted hash \
that will cause the WHERE clause of the SQL statement to query an arbitrary table with some \
value.

Releases 
-------- 
The FIXED releases are available at the normal locations. 

Workarounds 
----------- 
This issue can be mitigated by casting the parameter to an expected value.  For example, change \
this:

    Post.where(:id => params[:id]).all

to this:

    Post.where(:id => params[:id].to_s).all

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.  We \
have also provided a patch for the 3.0 series despite the fact it is unmaintained.

* 3-0-params_sql_injection.patch - Patch for 3.0 series 
* 3-1-params_sql_injection.patch - Patch for 3.1 series 
* 3-2-params_sql_injection.patch - Patch for 3.2 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 the vulnerability to us, and to Chad Pyne of thoughtbot for \
helping us verify the fix.

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


["3-0-params_sql_injection.patch" (text/plain)]

From 99f030934eb8341db333cb6783d0f42bfa57358f Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patterson@gmail.com>
Date: Wed, 30 May 2012 15:06:12 -0700
Subject: [PATCH] predicate builder should not recurse for determining where
 columns. Thanks to Ben Murphy for reporting this

CVE-2012-2661
---
 .../active_record/relation/predicate_builder.rb    |    6 +++---
 activerecord/test/cases/relation/where_test.rb     |   19 +++++++++++++++++++
 2 files changed, 22 insertions(+), 3 deletions(-)
 create mode 100644 activerecord/test/cases/relation/where_test.rb

diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb \
b/activerecord/lib/active_record/relation/predicate_builder.rb index 505c3f4..84e88cf 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -5,17 +5,17 @@ module ActiveRecord
       @engine = engine
     end
 
-    def build_from_hash(attributes, default_table)
+    def build_from_hash(attributes, default_table, check_column = true)
       predicates = attributes.map do |column, value|
         table = default_table
 
         if value.is_a?(Hash)
           table = Arel::Table.new(column, :engine => @engine)
-          build_from_hash(value, table)
+          build_from_hash(value, table, false)
         else
           column = column.to_s
 
-          if column.include?('.')
+          if check_column && column.include?('.')
             table_name, column = column.split('.', 2)
             table = Arel::Table.new(table_name, :engine => @engine)
           end
diff --git a/activerecord/test/cases/relation/where_test.rb \
b/activerecord/test/cases/relation/where_test.rb new file mode 100644
index 0000000..90c690e
--- /dev/null
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -0,0 +1,19 @@
+require "cases/helper"
+require 'models/post'
+
+module ActiveRecord
+  class WhereTest < ActiveRecord::TestCase
+    fixtures :posts
+
+    def test_where_error
+      assert_raises(ActiveRecord::StatementInvalid) do
+        Post.where(:id => { 'posts.author_id' => 10 }).first
+      end
+    end
+
+    def test_where_with_table_name
+      post = Post.first
+      assert_equal post, Post.where(:posts => { 'id' => post.id }).first
+    end
+  end
+end
-- 
1.7.5.4


["3-1-params_sql_injection.patch" (text/plain)]

From b71d4ab9d7d61ebe3411a8754e9fe93d3587704e Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patterson@gmail.com>
Date: Wed, 30 May 2012 15:05:19 -0700
Subject: [PATCH] predicate builder should not recurse for determining where
 columns. Thanks to Ben Murphy for reporting this

CVE-2012-2661
---
 .../associations/association_scope.rb              |   17 ++++++++++++++++-
 .../active_record/relation/predicate_builder.rb    |    6 +++---
 activerecord/test/cases/relation/where_test.rb     |   19 +++++++++++++++++++
 3 files changed, 38 insertions(+), 4 deletions(-)
 create mode 100644 activerecord/test/cases/relation/where_test.rb

diff --git a/activerecord/lib/active_record/associations/association_scope.rb \
b/activerecord/lib/active_record/associations/association_scope.rb index 6cc401e..8e1df35 \
                100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -87,7 +87,7 @@ module ActiveRecord
 
             conditions.each do |condition|
               if options[:through] && condition.is_a?(Hash)
-                condition = { table.name => condition }
+                condition = disambiguate_condition(table, condition)
               end
 
               scope = scope.where(interpolate(condition))
@@ -126,6 +126,21 @@ module ActiveRecord
         end
       end
 
+      def disambiguate_condition(table, condition)
+        if condition.is_a?(Hash)
+          Hash[
+            condition.map do |k, v|
+              if v.is_a?(Hash)
+                [k, v]
+              else
+                [table.table_alias || table.name, { k => v }]
+              end
+            end
+          ]
+        else
+          condition
+        end
+      end
     end
   end
 end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb \
b/activerecord/lib/active_record/relation/predicate_builder.rb index 7e8ddd1..0e436e8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,16 +1,16 @@
 module ActiveRecord
   class PredicateBuilder # :nodoc:
-    def self.build_from_hash(engine, attributes, default_table)
+    def self.build_from_hash(engine, attributes, default_table, check_column = true)
       predicates = attributes.map do |column, value|
         table = default_table
 
         if value.is_a?(Hash)
           table = Arel::Table.new(column, engine)
-          build_from_hash(engine, value, table)
+          build_from_hash(engine, value, table, false)
         else
           column = column.to_s
 
-          if column.include?('.')
+          if check_column && column.include?('.')
             table_name, column = column.split('.', 2)
             table = Arel::Table.new(table_name, engine)
           end
diff --git a/activerecord/test/cases/relation/where_test.rb \
b/activerecord/test/cases/relation/where_test.rb new file mode 100644
index 0000000..90c690e
--- /dev/null
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -0,0 +1,19 @@
+require "cases/helper"
+require 'models/post'
+
+module ActiveRecord
+  class WhereTest < ActiveRecord::TestCase
+    fixtures :posts
+
+    def test_where_error
+      assert_raises(ActiveRecord::StatementInvalid) do
+        Post.where(:id => { 'posts.author_id' => 10 }).first
+      end
+    end
+
+    def test_where_with_table_name
+      post = Post.first
+      assert_equal post, Post.where(:posts => { 'id' => post.id }).first
+    end
+  end
+end
-- 
1.7.5.4


["3-2-params_sql_injection.patch" (text/plain)]

From 71f7917c553cdc9a0ee49e87af0efb7429759718 Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patterson@gmail.com>
Date: Wed, 30 May 2012 15:04:11 -0700
Subject: [PATCH] predicate builder should not recurse for determining where
 columns. Thanks to Ben Murphy for reporting this

CVE-2012-2661
---
 .../associations/association_scope.rb              |   17 ++++++++++++++++-
 .../active_record/relation/predicate_builder.rb    |    6 +++---
 activerecord/test/cases/relation/where_test.rb     |   19 +++++++++++++++++++
 3 files changed, 38 insertions(+), 4 deletions(-)
 create mode 100644 activerecord/test/cases/relation/where_test.rb

diff --git a/activerecord/lib/active_record/associations/association_scope.rb \
b/activerecord/lib/active_record/associations/association_scope.rb index b3819e3..f9cffa4 \
                100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -75,7 +75,7 @@ module ActiveRecord
 
             conditions.each do |condition|
               if options[:through] && condition.is_a?(Hash)
-                condition = { table.name => condition }
+                condition = disambiguate_condition(table, condition)
               end
 
               scope = scope.where(interpolate(condition))
@@ -114,6 +114,21 @@ module ActiveRecord
         end
       end
 
+      def disambiguate_condition(table, condition)
+        if condition.is_a?(Hash)
+          Hash[
+            condition.map do |k, v|
+              if v.is_a?(Hash)
+                [k, v]
+              else
+                [table.table_alias || table.name, { k => v }]
+              end
+            end
+          ]
+        else
+          condition
+        end
+      end
     end
   end
 end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb \
b/activerecord/lib/active_record/relation/predicate_builder.rb index a789f48..9c84d8a 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,16 +1,16 @@
 module ActiveRecord
   class PredicateBuilder # :nodoc:
-    def self.build_from_hash(engine, attributes, default_table)
+    def self.build_from_hash(engine, attributes, default_table, check_column = true)
       predicates = attributes.map do |column, value|
         table = default_table
 
         if value.is_a?(Hash)
           table = Arel::Table.new(column, engine)
-          build_from_hash(engine, value, table)
+          build_from_hash(engine, value, table, false)
         else
           column = column.to_s
 
-          if column.include?('.')
+          if check_column && column.include?('.')
             table_name, column = column.split('.', 2)
             table = Arel::Table.new(table_name, engine)
           end
diff --git a/activerecord/test/cases/relation/where_test.rb \
b/activerecord/test/cases/relation/where_test.rb new file mode 100644
index 0000000..90c690e
--- /dev/null
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -0,0 +1,19 @@
+require "cases/helper"
+require 'models/post'
+
+module ActiveRecord
+  class WhereTest < ActiveRecord::TestCase
+    fixtures :posts
+
+    def test_where_error
+      assert_raises(ActiveRecord::StatementInvalid) do
+        Post.where(:id => { 'posts.author_id' => 10 }).first
+      end
+    end
+
+    def test_where_with_table_name
+      post = Post.first
+      assert_equal post, Post.where(:posts => { 'id' => post.id }).first
+    end
+  end
+end
-- 
1.7.5.4


[Attachment #8 (application/pgp-signature)]

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic