From aa5dca68eb3cb398da312bf6b0cb9949434770c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 5 Jun 2014 14:08:40 -0300 Subject: [PATCH] Fix SQL injection when querying against ranges and bitstrings Fix CVE-2014-3483 and protect against CVE-2014-3482. --- .../connection_adapters/postgresql/quoting.rb | 7 ++++--- .../connection_adapters/postgresql_adapter.rb | 2 +- .../test/cases/adapters/postgresql/quoting_test.rb | 6 ++++++ .../test/cases/adapters/postgresql/range_test.rb | 18 ++++++++++++++++++ 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index f45274d..880d0ee 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -23,7 +23,8 @@ module ActiveRecord case value when Range if /range$/ =~ sql_type - "'#{PostgreSQLColumn.range_to_string(value)}'::#{sql_type}" + escaped = quote_string(PostgreSQLColumn.range_to_string(value)) + "'#{escaped}'::#{sql_type}" else super end @@ -70,8 +71,8 @@ module ActiveRecord when 'xml' then "xml '#{quote_string(value)}'" when /^bit/ case value - when /^[01]*$/ then "B'#{value}'" # Bit-string notation - when /^[0-9A-F]*$/i then "X'#{value}'" # Hexadecimal notation + when /\A[01]*\Z/ then "B'#{value}'" # Bit-string notation + when /\A[0-9A-F]*\Z/i then "X'#{value}'" # Hexadecimal notation end else super diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 4d8fcda..b64d6da 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -819,7 +819,7 @@ module ActiveRecord FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: def exec_no_cache(sql, name, binds) - log(sql, name, binds) { @connection.async_exec(sql) } + log(sql, name, binds) { @connection.async_exec(sql, []) } end def exec_cache(sql, name, binds) diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index 1122f8b..0eb6a26 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -57,6 +57,12 @@ module ActiveRecord assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0)) assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime) end + + def test_quote_range + range = "1,2]'; SELECT * FROM users; --".."a" + c = PostgreSQLColumn.new(nil, nil, OID::Range.new(:integer), 'int8range') + assert_equal "'[1,2]''; SELECT * FROM users; --,a]'::int8range", @conn.quote(range, c) + end end end end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index bd8317f..36ad78d 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -216,6 +216,24 @@ if ActiveRecord::Base.connection.supports_ranges? assert_equal Date.new(2012, 1, 3)..Date.new(2012, 1, 4), range.date_range end + def test_update_all_with_ranges + PostgresqlRange.create! + + PostgresqlRange.update_all(int8_range: 1..100) + + assert_equal 1...101, PostgresqlRange.first.int8_range + end + + def test_ranges_correctly_escape_input + e = assert_raises(ActiveRecord::StatementInvalid) do + range = "1,2]'; SELECT * FROM users; --".."a" + PostgresqlRange.update_all(int8_range: range) + end + + assert e.message.starts_with?("PG::InvalidTextRepresentation") + ActiveRecord::Base.connection.rollback_transaction + end + private def assert_equal_round_trip(range, attribute, value) round_trip(range, attribute, value) -- 2.0.0