Bundler's Multiple Source Security Vulnerability
How Bundler is broken and what you can do about it
Bundler has a major security vulnerability that affects all stable versions. The vulnerability allows an attacker to inject arbitrary code into your application via any secondary gem source declared in your gemfile, whether or not that source is scoped to specific gems.
CVE-2016-7954 is assigned to this vulnerability.
Over two years ago, a similar vulnerability (CVE-2013-0334) was discovered and is described in a post on Bundler’s blog. The proposed resolution was to upgrade to Bundler 1.7+. This no longer resolves the issue and in fact, never did. All of the broken behaviors described below also apply to Bundler 1.7.
Bundler allows you to specify multiple sources for your gem dependencies. In some cases, which source to use for a given gem is unclear. To maximize that ambiguity for our examples, let’s consider a gemfile with two sources and three gems, all three of which are available from both sources.
source "http://public.org" source "http://private.com" gem "foo" gem "bar" gem "baz"
Source Ambiguity Warnings
Ever since version 1.7.0, Bundler displays helpful warning messages whenever a gem could have been installed from any one of the multiple sources available. Bundler’s default behavior is to honor the last source. The output of
bundle install for the gemfile above (using Bundler 1.13.2) is as follows:
Fetching source index from http://private.com/ Fetching source index from http://public.org/ Resolving dependencies... Installing foo 1.0.0 Installing bar 1.0.0 Installing baz 1.0.0 Using bundler 1.13.2 Bundle complete! 3 Gemfile dependencies, 4 gems now installed. Use `bundle show [gemname]` to see where a bundled gem is installed. Warning: the gem 'foo' was found in multiple sources. Installed from: http://private.com/ Also found in: * http://public.org/ You should add a source requirement to restrict this gem to your preferred source. For example: gem 'foo', :source => 'http://private.com/' Then uninstall the gem 'foo' (or delete all bundled gems) and then install again. Warning: the gem 'bar' was found in multiple sources. Installed from: http://private.com/ Also found in: * http://public.org/ You should add a source requirement to restrict this gem to your preferred source. For example: gem 'bar', :source => 'http://private.com/' Then uninstall the gem 'bar' (or delete all bundled gems) and then install again. Warning: the gem 'baz' was found in multiple sources. Installed from: http://private.com/ Also found in: * http://public.org/ You should add a source requirement to restrict this gem to your preferred source. For example: gem 'baz', :source => 'http://private.com/' Then uninstall the gem 'baz' (or delete all bundled gems) and then install again.
These warning messages point out two ways to resolve the source ambiguity:
For demonstration purposes, let’s assume we only want to install the
bar gem from the secondary (
private.com) source. The
baz gems should be installed from our primary (
Let’s start with trying to use the
:source option since the warning messages give precise examples of how to do so. The updated gemfile is as follows:
source "http://public.org" gem "foo" gem "bar", source: "http://private.com" gem "baz"
When I run
bundle install with this new gemfile (after uninstalling all gems), here’s the output I get:
Fetching source index from http://private.com/ Fetching source index from http://public.org/ Resolving dependencies... Installing foo 1.0.0 Installing bar 1.0.0 Installing baz 1.0.0 Using bundler 1.13.2 Bundle complete! 3 Gemfile dependencies, 4 gems now installed. Use `bundle show [gemname]` to see where a bundled gem is installed.
I didn’t receive any warnings, so my assumption is that
baz installed from my primary source (
bar installed from my secondary source (
private.com). Unfortunately, that is not the case.
All three gems are installed from the secondary source.
I was able to test this behavior by starting two gem servers on separate ports, each with all three gems available. Then I ran
bundle from a clean slate and watched the gem server logs to determine which gems were requested of each server.
As you can see from the server logs of the two gem servers, all three gems were installed from the secondary gem server. This is not the expected behavior.
Another potential solution is to wrap gem declarations within a
source block for the secondary source. Here’s the updated gemfile:
source "http://public.org" gem "newcomb" source "http://private.com" do gem "fair_dice_roll" end gem "insecure_random"
Bundler’s unexpected behavior means that the addition of a secondary source opens all of your gems up to being installed from an unknown or unexpected location. At its worst, an attacker can introduce arbitrary code to phone home with your sensitive configuration values or data.
Consider the following gemfile:
source "https://rubygems.org" gem "rails" group :development, :test, :console do gem "handy_dev_helpers", source: "https://helpfuldeveloper.org" end
You may already be wary of secondary gem sources, so perhaps you
bundle open the
"handy_dev_helpers" gem to inspect its contents and determine that it’s safe to use.
However, if the
helpfuldeveloper.org gem server also makes a
"rails" gem available, that
"rails" gem will be used instead of the gem from
rubygems.org. The imposter gem may behave exactly like Rails, but it may also have additional code meant to compromise your information.
With the gemfile above, Bundler would have given no warning or indication that the source for
"rails" is ambiguous.
How To Protect Yourself
Use multiple source blocks.
The only way I was able to properly map each gem to its proper source was to put every gem declaration within its own source block rather than having any concept of a primary source.
source "http://public.org" do gem "foo" gem "baz" end source "http://private.com" do gem "bar" end
This is currently the only way (besides having a single source) to ensure that all of your gems install from their expected sources.
I discovered this vulnerability in March, 2016. After running and documenting my intitial tests, I wrote a report (much like this blog post) and sent it to the Bundler team on April 1.
It seems as though this vulnerability has been patched in the development of Bundler 2. However, I’m told that there is no plan to backport the fix to Bundler 1 since that requires backwards incompatible changes. As of this writing, I have received no response to my suggestions to update warning message recommendations and/or to add warning messages for the silent problematic cases.
Fifteen versions of Bundler have been released since my initial report in April, four since my latest email with the team.
Bundle safely! Steer clear of global sources!
In my point of view thats only a minor issue. If you trust http://public.org to deliver gem ‘foo’, then you have to expand your trust that http://public.org wont offer you other gems.
In my point of view it’s more likely that an attacker replaces gem ‘foo’ with malicous code or intercepts your http-connection then that someone offers you another gem ‘bar’. That gem ‘foo’ exists an attacker knows. That you use gem ‘bar’ an attacker does only know when your software is opensource.
All this boils down to if you trust an external source you realy have to trust it. As long as gems are not signed.
@slowjack, Maybe I’m misunderstanding things, but I believe the problem is pretty real. If I have a private gem named ‘mycompany-x’ on Gemfury and an attacker figures that out and publishes
mycompany-xto RubyGems since it isn’t taken yet. In this case depending on how I setup my Gemfile I could be installing the attackers source code.
In that scenario this has nothing to do with whether or not I trust RubyGems. Anyone can publish an unpublished gem name to RubyGems.
The scenario may be unlikely but not impossible. To be honest though we’ve been looking at using private gems and modularizing our code more and we’ve found Bundler and Rubygems infuriating. We’re actually seriously considering using NPM for this instead.