CVE-2011-2930: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: Multiple SQL injection vulnerabilities in the quote_table_name method in the ActiveRecord adapters in activerecord/lib/active_record/connection_adapters/ in Ruby on Rails before 2.3.13, 3.0.x before 3.0.10, and 3.1.x before 3.1.0.rc5 allow remote attackers to execute arbitrary SQL commands via a crafted column name.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
Published: 2011-08-29T18:55Z
Repository: https://github.com/rails/rails
Language: Ruby
Files changed: activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb, activerecord/lib/active_record/connection_adapters/mysql_adapter.rb, activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb, activerecord/test/cases/base_test.rb
Commit date: 2011-08-16 15:24:42 -0700
Commit message: prevent sql injection attacks by escaping quotes in column names

Diff: diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index ae5ae16dc..ef51f5ebc 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -167,11 +167,11 @@ def quote(value, column = nil)
           super
         end
       end
 
       def quote_column_name(name) #:nodoc:
-        @quoted_column_names[name] ||= "`#{name}`"
+        @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
       end
 
       def quote_table_name(name) #:nodoc:
         @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
       end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index a44ac08ce..b844e5ab1 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -248,11 +248,11 @@ def type_cast(value, column)
 
         value ? 1 : 0
       end
 
       def quote_column_name(name) #:nodoc:
-        @quoted_column_names[name] ||= "`#{name}`"
+        @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
       end
 
       def quote_table_name(name) #:nodoc:
         @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
       end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 486efc5ba..da8695702 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -146,11 +146,11 @@ def native_database_types #:nodoc:
       def quote_string(s) #:nodoc:
         @connection.class.quote(s)
       end
 
       def quote_column_name(name) #:nodoc:
-        %Q("#{name}")
+        %Q("#{name.to_s.gsub('"', '""')}")
       end
 
       # Quote date/time values for use in SQL input. Includes microseconds
       # if the value is a Time responding to usec.
       def quoted_date(value) #:nodoc:
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index b8ebabfe7..fe46c00b4 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -65,10 +65,27 @@ def setup
 end
 
 class BasicsTest < ActiveRecord::TestCase
   fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
 
+  def test_column_names_are_escaped
+    conn      = ActiveRecord::Base.connection
+    classname = conn.class.name[/[^:]*$/]
+    badchar   = {
+      'SQLite3Adapter'    => '"',
+      'MysqlAdapter'      => '`',
+      'Mysql2Adapter'     => '`',
+      'PostgreSQLAdapter' => '"',
+      'OracleAdapter'     => '"',
+    }.fetch(classname) {
+      raise "need a bad char for #{classname}"
+    }
+
+    quoted = conn.quote_column_name "foo#{badchar}bar"
+    assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
+  end
+
   def test_columns_should_obey_set_primary_key
     pk = Subscriber.columns.find { |x| x.name == 'nick' }
     assert pk.primary, 'nick should be primary key'
   end
 

Vulnerable code:         @quoted_column_names[name] ||= "`#{name}`"
        @quoted_column_names[name] ||= "`#{name}`"
        %Q("#{name}")
Fixed code:         @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
        @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
        %Q("#{name.to_s.gsub('"', '""')}")
  def test_column_names_are_escaped
    conn      = ActiveRecord::Base.connection
    classname = conn.class.name[/[^:]*$/]
    badchar   = {
      'SQLite3Adapter'    => '"',
      'MysqlAdapter'      => '`',
      'Mysql2Adapter'     => '`',
      'PostgreSQLAdapter' => '"',
      'OracleAdapter'     => '"',
    }.fetch(classname) {
      raise "need a bad char for #{classname}"
    }

    quoted = conn.quote_column_name "foo#{badchar}bar"
    assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
  end