Remote Command Injection Vulnerabilities in Ruby Gems

Larry W. Cashdollar, @_larry0

3/16/2015

Summary 

This paper is an attempt to document my exploration of some simple but critical coding mistakes I discovered commonly found in Ruby Gems.  These vulnerabilities are very easy to find by examining various code repositories like rubygems.org.

Audience

The reader should have some knowledge of computer programming and command line shell use under Windows/Linux, you don’t need to be a Ruby Developer (I’m not) to understand what it is I’m babbling about.

Methodology

The vulnerabilities I easily discovered pass unsanitized user supplied input directly to the shell for execution.  This user data is being trusted to a function where a user can manipulate it into changing the programs proper course of execution.  These types of remote execution vulnerabilities are context dependent. The user input must be supplied through a remote Rails application, or if the client program is trusting data from a remote source. Typically if the application is CLI utility this type of vulnerability isn’t unless that command line utility crosses a trust boundary e.g. sudo for example.

Why

After finding a few vulnerabilities by randomly downloading a gem and examining the code I wondered if I could automate the process.  I wanted to run a script and wait for it to finish after a few hours then check a report for any interesting hits.  I knew there had to be dozens of vulnerable gems but didn’t want to download and look at each one manually.

Execution Methods

There are various ways to execute code from ruby.  I've found several documents that already exist that describe this in detail.  I won't reproduce that documentation here, you can check out the links below.  

http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html

http://tech.natemurray.com/2007/03/ruby-shell-commands.html

http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html

Code Examples 

We are looking for user controlled data being passed to a variable or parameter that is then passed to a shell execution function like %x{} or system.  I’ll list some simple examples below to demonstrate a subset of what we are looking for.

In this example a filename supplied by the user is passed to a media file API written in Ruby.

`afplay #{filename}` 

The `back ticks` tell Ruby pass that line directly to the shell and execute it.  If the input is unsanitized for shell meta characters a malicious user can pass commands directly to the shell by creating a specially crafted filename.

Say #{url} is supplied from recursively scraping a website and passed to the curl command like so:

command = ‘curl  -sk‘ + #{url}

`#{command}`

If a url is embedded in the website like: http://example.org/load.txt;halt; the halt command will be passed to the command shell and executed.  The system will halt if that Ruby Gem is being run by the root user.

Examining Real Vulnerabilities *

Command Wrap Gem (CVE 2013-1875)

7 def self.capture (url, target)

8        command = CommandWrap::Config::Xvfb.command(File.dirname(__FILE__) + "/../bin/CutyCapt --min-width=1024 --min-height=768 --url={url} --out={target}")
command_wrap.rb:

9        `#{command}`

Ftpd (CVE 2013-2512)


From: ftpd-0.2.1/lib/ftpd/disk_file_system.rb



204     # Ls interface used by List and NameList
205
206     module Ls
207
208       def ls(ftp_path, option)
209         path = expand_ftp_path(ftp_path)
210         dirname = File.dirname(path)
211         filename = File.basename(path)
212         command = [
213           'ls',
214           option,
215           filename,
216           '2>&1',
217         ].compact.join(' ')
218         if File.exists?(dirname)
219           list = Dir.chdir(dirname) do
220            
 `#{command}`

The ls command doesn’t sanitize input, so typing ls filename;id; will list the filename and return the user ID of the running process.

Fileutils (CVE 2013-2516)

From: ./lib/file_utils.rb

 7     def capture (url, target)
8    command = FileUtils::Config::Xvfb.command(File.dirname(__FILE__) + "/../bin/CutyCapt --min-width=1024 --min-height=768 --url=
{url} --out={target}")
9         `#{command}`
10     end

The function capture doesn't sanitize input on URLs passed to CutyCapt for execution. If a URL contains shell meta characters like ';' a remote attacker execute a command on the clients system if they are enticed to click a specially crafted link.

CLI Utilities

Command Injection can also be valid for command line utilities depending on if they cross any trust boundaries.  In some environments sudo access may be used limit access to certain utlities.  What if a heavy ruby shop utilizes sudo to control access to ruby gems made available to developers in checking system logs or editing specific files? Suppose you can inject commands into a those ruby gems?
A specific example of this would be this sudo gem https://github.com/gderosa/rubysu and the ruby gem fileutils (CVE-2013-2516). The fileutils gem is specifically mentioned in the rubysu documentation, and fileutils v0.7 is vulnerable to command injection. So any commands injected into the fileutils utility would circumvent the restrictions imposed by sudo.

Finding Vulnerabilites

You can quickly look for command injection and other vulnerabilities just using grep.  Two acquaintances of mine[1] coined the phrase “grep and gripe”.

A quick and basic check would be:

$ grep -nC5 “command” *.rb

$ grep -nC5 “cmd” *.rb

$ grep -nC5 “run” *.rb

$ grep -nC5 “system“ *.rb

$ grep -nC5 “%x{“ *.rb

$ grep -nC5 “passwd” *.rb

$ grep -nC5 “/tmp” *.rb

This would give you line numbers (-n) and five lines of context (C5).

Better yet you can combine these with egrep and | or.

$ egrep -nC5 “api|key|secret” *.rb

Scripting It 

rubygems.org has an API that can pull down the Gem code that we intend to analyze.  You can pull down Gem details in YAML or json format.  A perl script could be written that indexes all the gems, pulls down details for each one and downloads each gem.  Then the gems can be untarred where the source .rb files can be analyzed.

I developed a shell script to do exactly this, but poorly. I wrote it before noticing the nice API supplied by the rubygems.org folks.  I was also interested in mining for Ruby Gem vulnerabilities rather than writing all the code to properly digest JSON and YAML formats so it would have been a poorly written script either way.

https://github.com/lcashdol/rubygem_miner

Exploiting Them 

Ken Thompson added a demonstration of command injection upon my request for the latest release of his excellent RailsGoat application (http://railsgoat.cktricky.com). You can try exploiting remote command injection yourself.

Breaking out of escaped strings

On occasion developers attempt to escape user supplied input by using quotations marks, but we can still break out of that.  Suppose our file name we enter is \"id#; /usr/bin/id>/tmp/p;\" the \” will close off the other quotes allowing our malicious commands to be passed to shell.

irb(main):095:0> @file = "\"id#; /usr/bin/id>/tmp/p;\""

=> "\"id#; /usr/bin/id>/tmp/p;\""

irb(main):096:0> puts @file

"id#; /usr/bin/id>/tmp/p;"

=> nil

irb(main):097:0>  system %{/bin/echo "#{@file}" }

id#

sh: 1: : Permission denied

=> false

irb(main):098:0>

root@underfl0w:/tmp# cat /tmp/p

uid=0(root) gid=0(root) groups=0(root)

root@underfl0w:/tmp#

What the shell sees is:

larry@vapid:~$ echo ""id; /usr/bin/id>/tmp/p;""

Avoiding RCI Vulnerabilities

There are some good guides on avoiding command injection, these cover input sanitization and using shell execution functions properly.

http://code.google.com/p/ruby-security/wiki/Guide#Good_ol%27_shell_injection

http://guides.rubyonrails.org/security.html#command-line-injection


[1] Brian a.k.a Jericho http://jerichoattrition.wordpress.com/about/ and Steve Christie a.ka. SushiDude coined the term in their presentation Buying Into the Bias: Why Vulnerability Statistics Suck.