RubyGems · Report #270068
Installer can modify other gems if gem name is specially crafted
How it was found
Installer can modify other gems if gem name is specially crafted
The install_location function allows writing to certain files outside the installation directory.
The install_location function in lib/rubygems/package.rb attempts to ensure that files are not installed outside destination_dir. However the test it employs, a string comparison using start_with?, fails to prevent the case when destination_dir is a prefix of the path being written.
Example that should be prevented but is allowed:
install_location '../install-whatever-foobar/hello.txt', '/tmp/install'
# outputs '/tmp/install-whatever-foobar/hello.txt'
gem install always constructs destination_dir as '#{name}-#{version}', so the vulnerability cannot overwrite arbitrary files. However, a malicious gem with name='rails' and an empty version number (version=''), for example, could overwrite the files of any other gem whose name begins with rails-, like rails-i18n or rails-letsencrypt.
Proof of concept
The attached ra.gem demonstrates the vulnerability. It assumes that some other gems have already been installed.
gem install --install-dir=/tmp/install rails-i18n rails-letsencrypt rails-html-sanitizer
gem install --install-dir=/tmp/install ra.gem
The malicious gem will do three things, each of which could potentially lead to code execution:
- delete an existing rails-letsencrypt-0.5.3 gem
- overwrite a code file in the rails-i18n-5.0.4 gem
- symlink rails-html-sanitizer-1.0.3 to a world-writable directory
The structure of the gem file reveals how the attack works:
$ tar -xvf ra.gem
metadata.gz
data.tar.gz
$ gzip -dc metadata.gz | head -n 4
--- !ruby/object:Gem::Specification
name: rails
version: !ruby/object:Gem::Version
version: ''
$ tar -tvf data.tar.gz
-rw-r--r-- 0/0 12 1969-12-31 16:00 README
drwxr-xr-x 0/0 0 1969-12-31 16:00 ../rails-letsencrypt-0.5.3/
-rw-r--r-- 0/0 12 1969-12-31 16:00 ../rails-i18n-5.0.4/lib/rails_i18n.rb
lrw-r--r-- 0/0 0 1969-12-31 16:00 ../rails-html-sanitizer-1.0.3 -> /tmp/attacker-controlled
Remediation
A sufficient fix is to append a directory separator to destination_dir before doing the start_with? check.
diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb
index c36e71d8..f73f9d30 100644
--- a/lib/rubygems/package.rb
+++ b/lib/rubygems/package.rb
@@ -424,7 +424,7 @@ EOM
destination = File.expand_path destination
raise Gem::Package::PathError.new(destination, destination_dir) unless
- destination.start_with? destination_dir
+ destination.start_with? destination_dir + '/'
destination.untaint
destination
Attached files
ra.gem, an example of a vulnerable gemmake-ra-gem.py, sample code that generates the proof of concept (to run:./make-ra-gem.py > ra.gem)0001-Add-test_install_location_suffix.patch, test code that checks for this vulnerability. Run withruby -I"lib:test" test/rubygems/test_gem_package.rb.