Remote Code Execution via Ruby on Rails Active Storage Insecure Deserialization
June 20, 2019 | Trend Micro Research TeamIn this excerpt of a Trend Micro Vulnerability Research Service vulnerability report, Sivathmican Sivakumaran and Pengsu Cheng of the Trend Micro Security Research Team detail a recent code execution vulnerability in Ruby on Rails. The bug was originally discovered and reported by the researcher known as ooooooo_q. The following is a portion of their write-up covering CVE-2019-5420, with a few minimal modifications.
An insecure deserialization vulnerability has been reported in the ActiveStorage component of Ruby on Rails. This vulnerability is due to deserializing a Ruby object within an HTTP URL using Marshal.load() without sufficient validation.
The Vulnerability
Rails is an open source web application Model View Controller (MVC) framework written in the Ruby language. Rails is built to encourage software engineering patterns and paradigms such as convention over configuration (CoC), don't repeat yourself (DRY), and the active record pattern. Rails ships as the following individual components:
Rails 5.2 also ships with Active Storage, which is the component of interest for this vulnerability. Active Storage is used to store files and associate those files to Active Record. It is compatible with cloud storage services such as Amazon S3, Google Cloud Storage, and Microsoft Azure Storage.
Ruby supports serialization of Objects to JSON, YAML or the Marshal serialization format. The Marshal serialization format is implemented by the Marshal
class. Objects can be serialized and deserialized via the load()
and dump()
methods respectively.
As shown above, the Marshal serialization format uses a type-length-value representation to serialize objects.
Active Storage adds a few routes by default to the Rails application. Of interest to this report are the following two routes which are responsible for downloading and uploading files respectively:
An insecure deserialization vulnerability exists in the ActiveStorage component of Ruby on Rails. This component uses ActiveSupport::MessageVerifier
to ensure the integrity of the above :encoded_key
and :encoded_token
variables. In normal use, these variables are generated by MessageVerifier.generate()
, and their structure is as follows:
<base64-message>
contains a base64 encoded version of the following JSON object:
When a GET or PUT Request is sent to a URI that contains “/rails/active_storage/disk/”, the :encoded_key
and :encoded_token
variables are extracted. These variables are expected to be generated by MessageVerifier.generate()
, hence decode_verified_key
and decode_verified_token
call MessageVerifier.verified()
to check the integrity of
ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
The digest is generated by signing data with a MessageVerifier secret. For Rails applications in development, this secret is always the application name, which is publicly known. For Rails applications in production, the secret is stored in a credentials.yml.enc
file, which is encrypted using a key in the master.key.
The contents of these files can be disclosed using CVE-2019-5418. Once the integrity check passes, Marshal.load()
is called on the resulting byte stream without any further validation. An attacker can exploit this condition by embedding a dangerous object, such as ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy
, to achieve remote code execution. CVE-2019-5418 needs to be chained to CVE-2019-5420 to ensure all conditions are met to achieve code execution.
A remote unauthenticated attacker can exploit this vulnerability by sending a crafted HTTP request embedding malicious serialized objects to a vulnerable application. Successful exploitation would result in arbitrary code execution under the security context of the affected Ruby on Rails application.
Source Code Walkthrough
The following code snippet was taken from Rails version 5.2.1. Comments added by Trend Micro have been highlighted.
From activesupport/lib/active_support/message_verifier.rb
:
From activestorage/app/controllers/active_storage/disk_controller.rb
:
python poc.py <host> [<port>]
Please note our Python PoC assumes that the application name is "Demo::Application".
The Patch
This vulnerability received a patch from the vendor in March 2019. In addition to this bug, the patch also provides fixes for CVE-2019-5418, a file content disclosure bug, and CVE-2019-5419, a denial-of-service bug in Action View.
If you are not able to immediately apply the patch, this issue can be mitigated by specifying a secret key in development mode. In config/environments/development.rb
file, add the following:
config.secret_key_base = SecureRandom.hex(64)
The only other salient mitigation is to restrict access to the affected ports.
Conclusion
This bug exists in versions 6.0.0.X and 5.2.X of Rails. Given that this vulnerability received a CVSS v3 score of 9.8, users of Rails should definitely look to upgrade or apply the mitigations soon.
Special thanks to Sivathmican Sivakumaran and Pengsu Cheng of the Trend Micro Security Research Team for providing such a thorough analysis of this vulnerability. For an overview of Trend Micro Security Research services please visit http://go.trendmicro.com/tis/.
The threat research team will be back with other great vulnerability analysis reports in the future. Until then, follow the ZDI team for the latest in exploit techniques and security patches.