PoC for a persistent code injection in RubyGems
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


RubyGems PWN

All versions of RubyGems are vulnerable to Persistent Code Injection via the gemspecs, which RubyGems generates when installing a Gem.



When building a .gem file, RubyGems will load your pure-Ruby gemspec and YAML.dump the gemspec object. The YAML-dump gemspec is then compressed and included into the .gem file. Upon installing a Gem, RubyGems will extract the YAML-dumped gemspec, YAML.load the gemspec and then build a new pure-Ruby representation of the attributes within Gem::Specification. RubyGems installs this re-generated pure-Ruby gemspec into the specifications/ directory within the GEM_HOME.

The rational for dumping the gemspec to YAML, then building another Ruby gemspec file from the dumped YAML, is that eval()ing Ruby is faster than calling YAML.load. Since RubyGems loads every gemspec at start-up, being fast matters.

RubyGems builds this pure-Ruby gemspec using the to_ruby method. to_ruby merely concatenates Ruby code into a big String, and embeds the data from the Gem::Specification. to_ruby relies on the ruby_code method, for wrap the gemspec data, so that they can be safely embedded into Ruby code.

Unfortunately, the ruby_code method naively wraps Strings in %q{ }, and performs no character-escaping. Security connoisseurs will immediately recognize this mistake as the same one which makes SQL Injection possible.

To exploit this bug, one simply needs to place a }; in a Gem::Specification field (summary is a good hiding spot) to escape the %q{, then add the malicious Ruby code, and ignore the trailing } with a # comment.

s.summary = "A Ruby API for TF2. }; puts "Geeeeentlemen" #"

Proof Of Concept (PoC)

gem install rubygems-pwn


As far as I can tell, the ruby_code method was introduced around RubyGems 0.8.0. All previous versions of RubyGems also appear to be vulnerable, since they directly inline the Gem::Specification attributes in to_ruby:

def to_ruby
  result =  "Gem::Specification.new do |s|\n"
  result << "s.name = %q{#{name}}\n"
  result << "s.version = %q{#{version}}\n"
  result << "s.platform = %q{#{platform}}\n" if @platform
  result << "s.has_rdoc = #{has_rdoc?}\n" if has_rdoc?
  result << "s.summary = %q{#{summary}}\n"
  • Gem::Specification#to_ruby method from RubyGems 0.2.0.

Of course, there is some user-interaction required, a user must be enticed into installing a new Gem. Once installed, the injected code is persistent since RubyGems will load all gemspecs during start-up. The injected code will also survive gem pristine, which re-generates all installed gemspecs.



The fix for this bug is rather simple, the ruby_code method should call String#dump or String#inspect instead of naively wrapping the Strings in %q{ }.

A more longeterm solution, would be for RubyGems to not store static-data as Ruby Code, but instead use a data format (YAML, XML, CSV, flat-file).