RubyGems is the package manager for Ruby libraries, called gems. In it’s latest release (2.6.13) the developers fixed 4 security issues, as noted in the update’s complementary blog post. The post doesn’t give any details besides the vulnerabilities titles. Therefore, in this post I will try to explain the issues and the threat they pose.

In case you are reading this for a solution, please run gem update --system or otherwise see the instructions on upgrading RubyGems.

DNS Hijacking Vulnerability (CVE-2017-0902)

“Fix a DNS request hijacking vulnerability. Discovered by Jonathan Claudius, fix by Samuel Giddins.”

This is the first issue that was patched this update. Although not mentioned, I found out the issue was actually a result of an insufficient fix to a previously patched vulnerability.

To fully understand the issue, it is necessary to first learn about the history of the bug. Back in May 2015, Trustwave researchers disclosed a vulnerability in RubyGems, under the name “Request hijacking vulnerability in RubyGems”. The issue was assigned CVE-2015-3900, and the RubyGems maintainers released a blog post about it and its fix.

In summary, the issue is as follows: RubyGems supports an API for gem server discovery, used when pushing or pulling gems from a gem distribution server (such as rubygems.org). This is done by sending a SRV DNS request to the user’s gem source (rubygems.org by default), prepended by “_rubygems._tcp.”.

RubyGems did not validate the SRV response, and immediately started using the domain in it. A man-in-the-middle attacker could have responded with his own SRV record1 and the client would pull all gems from the attacker’s domain. This would ultimately lead to the attacker executing code on the target machine by serving modified packages to the client (or otherwise exploiting other vulnerabilities in RubyGems, like the other ones in this post).

In the patch, code was added to verify the domain in response, by matching it against a regular expression of the host, in the following manner:

if /#{host}\z/ =~ target
  return URI.parse "#{uri.scheme}://#{target}#{uri.path}"
end

This was not a real fix whatsoever because this regular expression would accept any domain ending with rubygems.org. An attacker could register any domain as such (e.g. notrubygems.org) and continue exploiting RubyGems with DNS hijacking.

This new bug was assigned CVE-2015-4020. It was fixed three days later, with the verification as follows:

if /\.#{Regexp.quote(host)}\z/ =~ target
  return URI.parse "#{uri.scheme}://#{target}#{uri.path}"
end

All was well in the world of RubyGems DNS Hijacking, until May 2017, when a researcher (Jonathan Claudius) found a workaround for the previous fix, and was once again able to serve hijacked SRV requests and spread malicious gems.

To bypass the regex verification, Claudius uses the “evil.com/api.rubygems.com” string in his SRV response, which matches the regex while having evil.com as the host.

The details of his report can be found under the fix commit message. As suggested, in the fix the code verifies the domain using a URI object instead of matching it to a regex, as such:

if URI("http://" + target).host.end_with?(".#{host}")
return URI.parse "#{uri.scheme}://#{target}#{uri.path}"
end

This third cycle of the issue was recently assigned CVE-2017-09022.

ANSI escape sequence vulnerability (CVE-2017-0899)

“Fix an ANSI escape sequence vulnerability. Discovered by Yusuke Endoh, fix by Evan Phoenix.”

This bug is an interesting one. Information for gems is defined in a specification class (gem spec). The gemspec is formatted and printed to the terminal when showing information about a gem. Before the update, the text fields of the gemspec were unsanitized, thus a malicious gem owner could insert ANSI escape codes to one of the fields of his gemspec, and when printed, mess up an unsuspicious user’s terminal.

From a quick experimentation I found this can only be triggered with the query command, when given the -d flag. There may be other ways to trigger this I am not aware of, though.

To reproduce this, I first needed to set up an environment with the old RubyGems. The official Ruby Docker image was already updated to use the patched RubyGems, so I had to download its source and revert the patch.

I cloned the image source and ran git checkout to the older commit (5f1f635) with RubyGems 2.6.12. I built the image and I was good to go with the old ruby image. I ran a container with bash as an entrypoint (--entrypoint bash).

Inside the container, I created a gem (following the RubyGems instructions) and built it. To test the vulnerability, I added ANSI codes in the author string. My gemspec was as following:

Gem::Specification.new do |s|
  s.name = 'ansi-test'
  s.version = '0.0.0'
  s.date = '2017-08-30'
  s.summary = "Ansi Test"
  s.description = "A simple ANSI test"
  s.authors = ["\e[34mBlue name\e[0m"]
  s.email = 'bla@bla.bla'
  s.files = ["lib/test.rb"]
  s.license = 'MIT'
end

I installed the gem (gem install ansi-test-0.0.0.gem) and then ran the query command on it. The result was just as expected, the ANSI escape codes were parsed by my terminal:

Trying the same fails on the updated version (the backslashes are replaced by dots instead).

By this point, you may probably wonder why this is a threat at all. The answer is that ANSI escape codes can do much more than changing colors. The actual capabilities of ANSI codes are dependent on your terminal implementation (i.e. bash may not do the same as rxvt). In this article by Dejan Lukan you can find a useful summary of the various capabilities of ANSI codes, and a PoC demonstration of exploiting an ANSI vulnerability on the rxvt terminal.

Regardless, even basic ANSI codes allow unexpected behaviors such as clearing the terminal (try \033), so even without actual file modification or code execution this is a threat for unsuspecting users running query -q.

DoS in query command (CVE-2017-0900)

“Fix a DOS vulnerability in the query command. Discovered by Yusuke Endoh, fix by Samuel Giddins.“

This issue is very straightforward. When given a gem with a huge summary string, RubyGems would hang. There is not much to add on this. I can understand why it was considered a vulnerability but in my opinion it could easily pass as a normal (non security) issue.

The fix truncates the gem summary field to 100,000 characters.

Gem arbitrary file overwrite (CVE-2017-0901)

“Fix a vulnerability in the gem installer that allowed a malicious gem to overwrite arbitrary files. Discovered by Yusuke Endoh, fix by Samuel Giddins.”

This is a classic directory traversal attack. In a similar manner to the ANSI vulnerability, the name field of gemspecs was not sanitized. A malicious package name could contain dots and slashes, resulting in the ability to write the gem’s files anywhere on the system (with appropriate permissions, if not running as root).

The following happens when installing a regular gem (this is a simple gem I built based on the previous example):

In this case, the gem’s name was “okay”. It was installed to /usr/local/bundle/gems. The file test.rb was installed inside the lib directory, as instructed by the gemspec.

The vulnerability is that by adding “../” to the name an attacker could navigate to any directory where the gem’s files would be installed.

Nevertheless, in my experimentations the files were always installed to a directory with the gem’s name and it’s version. For instance, see what happens when building and installing the following gemspec:

Gem::Specification.new do |s|
  s.name = '../../../../bad'
  s.version = ''
  s.date = '2017-08-30'
  s.summary = "Ansi Test"
  s.description = "A simple ANSI test"
  s.authors = ["name"]
  s.email = 'bla@bla.bla'
  s.files = ["bad"]
  s.license = 'MIT'
end

The build would result in a gem file in the root directory, named /bad-0.gem (yes, the build command also follows the directory traversal). Installing it gives:

So I could install the /bad-0 directory anywhere, but not overwrite files outside it. And it appears to be impossible to build a gem without the version field. For that reason, I don’t consider this a real arbitrary file overwrite.

This is dangerous anyhow, as it still may be used to overwrite the files of other gems (using the directory traversal to end up in the gems directory with another gem’s name/version).

This issue was fixed by sanitizing the package name. It seems, however, that gems exploiting this issue could not have been accepted to rubygems.org (the commit message implies not, so does the rubygems.org source). It might also be worth noting that most production environments shouldn’t have to run gem as root anyway.

Summary

In this post I explained the details of the latest vulnerabilities that were published in the RubyGems blog post, that rushed all users to update their clients (seriously, please do update it). I hope this gives you an idea of what kind of threats there are to something like a Ruby package manager.

If you like our security alerts, follow us on Twitter, or contact us for a demo today. Until next time!


  1. This practice is commonly referred to as DNS Hijacking
  2. It is unclear why the publication of this issue and the CVE assignment only happened recently.