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 ID

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.

Example

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:

  • a source block
  • the :source option

For demonstration purposes, let’s assume we only want to install the bar gem from the secondary (private.com) source. The foo and baz gems should be installed from our primary (public.org) source.

The :source Option

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 foo and baz installed from my primary source (public.org) while 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.

source Blocks

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"

Same problem.

So what?

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.

Fixing Bundler

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!

Photo of Steve Richert

Steve is a Senior Developer working with Ruby/Rails and JavaScript. He’s an active open source contributor and the lead developer for Interactor. Steve is also involved in documenting and improving Collective Idea’s software development practices.

Comments

  1. shad0wrunner@gmx.de
    slowjack
    October 07, 2016 at 5:34 AM

    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.

  2. November 16, 2016 at 0:42 AM

    @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-x to 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.