Unsafe Object Deserialization Vulnerability in RubyGems
by Aaron Patterson
Hello everyone! An unsafe object deserialization vulnerability was found in RubyGems. Unfortunately this vulnerability can be used as a way to escalate to a remote code execution exploit. The good news is that this issue was responsibly reported to the RubyGems team by Max Justicz, and we were able to promptly fix it. The RubyGems team audited all Gems, and using the data available to us we can say we have high confidence that no recently published Gems have been impacted, but due to the amount of time this bug has been in production, we cannot say for sure that zero Gems have been impacted.
You can read the CVE announcement for RubyGems here.
What was the bug?
This bug was an "unsafe object deserialization vulnerability". What that means is that an attacker is able to inject an instance of an object of their choosing in the target system. A clever attacker can inject an object that is able to interact with the system in such a way that will allow the attacker to execute arbitrary code.
This particular case has to do with the way RubyGems stores checksums inside Gem files. Checksums are stored in YAML inside the Gem, and before this fix, they were read with just a bare YAML.load. YAML, like Marshal, is meant to serialize and deserialize arbitrary Ruby objects. When RubyGems.org processes a Gem upload, it reads the Gem specification, which in turn reads the checksums in the Gem. A clever attacker could write a checksum file to a Gem that contains YAML formatted in such a way as to inject an arbitrary Ruby object and use that object as an escalation point.
What is the impact?
The impact of this bug is that an attacker could execute arbitrary Ruby code on RubyGems.org. However, after several audits of the Gem files, we found no tampering with the data in the database or any tampering with the Gem files in S3 storage. Gem files are stored in a versioned S3 bucket, so we were able to compare checksums stored in the database to versions stored in the S3 buckets. Any Gem tampering would be indicated by new Gem versions as well as checksum differences.
What was the timeline?
October 6th: Max Justicz reported the issue
October 6th: David Radcliffe verified the issue and hotpatched the servers
October 7th & 8th: RubyGems team conducted an audit of all Gems
October 9th: RubyGems is released with fixes and announcement
How long has RubyGems.org been vulnerable?
The bug was introduced to RubyGems in this commit in 2012, so it's possible that RubyGems.org was vulnerable since then.
What did we do to fix it?
To fix this issue we added some code to monkey patch RubyGems such that only safe types could be deserialized. Since this monkey patch is patching RubyGems and not RubyGems.org, we decided it would be better to patch RubyGems itself. This is why the CVE was issued for RubyGems, and a patch created for RubyGems.
How did we verify gems?
Gems are stored in versioned S3 buckets. We also store checksums of each gem, but checksums weren't stored before Feb 8th, 2015. We were able to verify that checksums matched for Gems created after Feb 8th, 2015. We were also able to verify that new versions weren't created for older Gems.
Since this vulnerability has possibly been around since 2012, in concert with the dates from which RubyGems.org started recording Gem checksums, it's impossible for us to say with 100% confidence that not one single Gem has been compromised. We can only say with very high confidence that no recently published Gems have been compromised.
How will we prevent more issues?
In the past, when similar issues arose, RubyGems was not treated as "the vulnerable code" and RubyGems.org was patched. The reason for this is because RubyGems is typically used as a client library, and a fundamental part of Gem installation is executing arbitrary Ruby code. So this type of vulnerability was seen as a problem with RubyGems.org and not RubyGems even though the vulnerable code is in RubyGems itself.
To prevent further issues like this, we are now treating security issues in RubyGems.org that are triggered by code in RubyGems as security issues in RubyGems itself. In other words, if a problem arises in RubyGems.org due to a problem in RubyGems, we will patch RubyGems. This policy will help to keep RubyGems, RubyGems.org, and any services that are using RubyGems safe.
Aaron Patterson