CVE-2017-1000248 (published 2017-11-17T04:29Z)

Redis-store <=v1.3.0 allows unsafe objects to be loaded from redis

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/redis-store/redis-store
commit_details: repo_url: https://github.com/redis-store/redis-store
author: Tom Scott
author_date: 2017-08-15 11:07:07-04:00
msg: Replace marshalling with pluggable serializers

This is in response to a vulnerability warning we received on Friday,
August 11th, 2017. While most users will not be affected by this
change, we recommend that developers of new applications use a different
serializer other than `Marshal`. This, along with the removal of the
`:marshalling` option, will enforce "sane defaults" in terms of securely
serializing/de-serializing data.

- Add `:serializer` option and deprecate `:marshalling`. Although you
  will still be able to enable/disable serialization with Marshal using
  `:marshalling` in the 1.x series, this will be removed by 2.0.

- Rename `Redis::Store::Marshalling` to `Redis::Store::Serialization` to
  reflect its new purpose.

Fixes #289
repository: repo_url: https://github.com/redis-store/redis-store
repo_name: redis-store/redis-store
description: Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks
repo_language: Ruby
owner: redis-store
file_changes: filename: redis-store.rb
change_type: ModificationType.MODIFY
diff: @@ -1,13 +1 @@
-require 'redis'
 require 'redis/store'
-require 'redis/store/factory'
-require 'redis/distributed_store'
-require 'redis/store/namespace'
-require 'redis/store/marshalling'
-require 'redis/store/version'
-require 'redis/store/redis_version'
-
-class Redis
-  class Store < self
-  end
-end

programming_language: SQL
filename: store.rb
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,10 @@
+require 'redis'
+require 'redis/store/factory'
+require 'redis/distributed_store'
+require 'redis/store/namespace'
+require 'redis/store/serialization'
+require 'redis/store/version'
+require 'redis/store/redis_version'
 require 'redis/store/ttl'
 require 'redis/store/interface'
 require 'redis/store/redis_version'
@@ -8,6 +15,24 @@ class Redis
 
     def initialize(options = { })
       super
+
+      unless options[:marshalling].nil?
+        puts %(
+          DEPRECATED: You are passing the :marshalling option, which has been
+          replaced with `serializer: Marshal` to support pluggable serialization
+          backends. To disable serialization (much like disabling marshalling),
+          pass `serializer: nil` in your configuration.
+
+          The :marshalling option will be removed for redis-store 2.0.
+        )
+      end
+
+      @serializer = options.key?(:serializer) ? options[:serializer] : Marshal
+
+      unless options[:marshalling].nil?
+        @serializer = options[:marshalling] ? Marshal : nil
+      end
+
       _extend_marshalling options
       _extend_namespace   options
     end
@@ -23,8 +48,7 @@ class Redis
 
     private
       def _extend_marshalling(options)
-        @marshalling = !(options[:marshalling] === false) # HACK - TODO delegate to Factory
-        extend Marshalling if @marshalling
+        extend Serialization unless @serializer.nil?
       end
 
       def _extend_namespace(options)

programming_language: Ruby
filename: factory.rb
change_type: ModificationType.MODIFY
diff: @@ -50,7 +50,14 @@ class Redis
         if options.key?(:key_prefix) && !options.key?(:namespace)
           options[:namespace] = options.delete(:key_prefix) # RailsSessionStore
         end
-        options[:raw] = !options[:marshalling]
+        options[:raw] = case
+                        when options.key?(:serializer)
+                          options[:serializer].nil?
+                        when options.key?(:marshalling)
+                          !options[:marshalling]
+                        else
+                          false
+                        end
         options
       end
 

programming_language: Ruby
filename: namespace.rb
change_type: ModificationType.MODIFY
diff: @@ -46,8 +46,8 @@ class Redis
       def mget(*keys)
         options = (keys.pop if keys.last.is_a? Hash) || {}
         if keys.any?
-          # Marshalling gets extended before Namespace does, so we need to pass options further
-          if singleton_class.ancestors.include? Marshalling
+          # Serialization gets extended before Namespace does, so we need to pass options further
+          if singleton_class.ancestors.include? Serialization
             super(*keys.map {|key| interpolate(key) }, options)
           else
             super(*keys.map {|key| interpolate(key) })

programming_language: Ruby
filename: serialization.rb
change_type: ModificationType.RENAME
diff: @@ -1,6 +1,6 @@
 class Redis
   class Store < self
-    module Marshalling
+    module Serialization
       def set(key, value, options = nil)
         _marshal(value, options) { |v| super encode(key), encode(v), options }
       end
@@ -36,11 +36,11 @@ class Redis
 
       private
         def _marshal(val, options)
-          yield marshal?(options) ? Marshal.dump(val) : val
+          yield marshal?(options) ? @serializer.dump(val) : val
         end
 
         def _unmarshal(val, options)
-          unmarshal?(val, options) ? Marshal.load(val) : val
+          unmarshal?(val, options) ? @serializer.load(val) : val
         end
 
         def marshal?(options)

programming_language: Ruby
filename: factory_test.rb
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,5 @@
 require 'test_helper'
+require 'json'
 
 describe "Redis::Store::Factory" do
   describe ".create" do
@@ -51,12 +52,45 @@ describe "Redis::Store::Factory" do
         assert_nil(store.instance_variable_get(:@client).password)
       end
 
-      it "allows/disable marshalling" do
-        store = Redis::Store::Factory.create :marshalling => false
-        store.instance_variable_get(:@marshalling).must_equal(false)
+      it "disables serialization" do
+        store = Redis::Store::Factory.create :serializer => nil
+        store.instance_variable_get(:@serializer).must_be_nil
         store.instance_variable_get(:@options)[:raw].must_equal(true)
       end
 
+      it "configures pluggable serialization backend" do
+        store = Redis::Store::Factory.create :serializer => JSON
+        store.instance_variable_get(:@serializer).must_equal(JSON)
+        store.instance_variable_get(:@options)[:raw].must_equal(false)
+      end
+
+      describe 'with stdout disabled' do
+        before do
+          @original_stderr = $stderr
+          @original_stdout = $stdout
+
+          $stderr = Tempfile.new('stderr')
+          $stdout = Tempfile.new('stdout')
+        end
+
+        it "disables marshalling and provides deprecation warning" do
+          store = Redis::Store::Factory.create :marshalling => false
+          store.instance_variable_get(:@serializer).must_be_nil
+          store.instance_variable_get(:@options)[:raw].must_equal(true)
+        end
+
+        it "enables marshalling but provides warning to use :serializer instead" do
+          store = Redis::Store::Factory.create :marshalling => true
+          store.instance_variable_get(:@serializer).must_equal(Marshal)
+          store.instance_variable_get(:@options)[:raw].must_equal(false)
+        end
+
+        after do
+          $stderr = @original_stderr
+          $stdout = @original_stdout
+        end
+      end
+
       it "should instantiate a Redis::DistributedStore store" do
         store = Redis::Store::Factory.create(
           {:host => "localhost", :port => 6379},

programming_language: Ruby
filename: namespace_test.rb
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,7 @@ require 'test_helper'
 describe "Redis::Store::Namespace" do
   def setup
     @namespace = "theplaylist"
-    @store  = Redis::Store.new :namespace => @namespace, :marshalling => false # TODO remove mashalling option
+    @store  = Redis::Store.new :namespace => @namespace, :serializer => nil
     @client = @store.instance_variable_get(:@client)
     @rabbit = "bunny"
     @default_store = Redis::Store.new
@@ -90,7 +90,7 @@ describe "Redis::Store::Namespace" do
   end
 
   describe 'method calls' do
-    let(:store){Redis::Store.new :namespace => @namespace, :marshalling => false}
+    let(:store){Redis::Store.new :namespace => @namespace, :serializer => nil}
     let(:client){store.instance_variable_get(:@client)}
 
     it "should namespace get" do

programming_language: Ruby
filename: serialization_test.rb
change_type: ModificationType.RENAME
diff: @@ -1,8 +1,8 @@
 require 'test_helper'
 
-describe "Redis::Marshalling" do
+describe "Redis::Serialization" do
   def setup
-    @store = Redis::Store.new :marshalling => true
+    @store = Redis::Store.new serializer: Marshal
     @rabbit = OpenStruct.new :name => "bunny"
     @white_rabbit = OpenStruct.new :color => "white"
     @store.set "rabbit", @rabbit

programming_language: Ruby