CVE-2018-1000079 (published 2018-03-13T15:29Z)

RubyGems version Ruby 2.2 series: 2.2.9 and earlier, Ruby 2.3 series: 2.3.6 and earlier, Ruby 2.4 series: 2.4.3 and earlier, Ruby 2.5 series: 2.5.0 and earlier, prior to trunk revision 62422 contains a Directory Traversal vulnerability in gem installation that can result in the gem could write to arbitrary filesystem locations during installation. This attack appear to be exploitable via the victim must install a malicious gem. This vulnerability appears to have been fixed in 2.7.6.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 5.5, MEDIUM (CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/rubygems/rubygems
commit_details: repo_url: https://github.com/rubygems/rubygems
author: Jonathan Claudius
author_date: 2018-02-01 00:00:48-05:00
msg: Add bug fix for #270068
repository: repo_url: https://github.com/rubygems/rubygems
repo_name: rubygems/rubygems
description: Library packaging and distribution for Ruby.
repo_language: Ruby
owner: rubygems
file_changes: filename: package.rb
change_type: ModificationType.MODIFY
diff: @@ -424,7 +424,7 @@ def install_location filename, destination_dir # :nodoc:
     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

programming_language: Ruby
repo_url: https://github.com/rubygems/rubygems
commit_details: repo_url: https://github.com/rubygems/rubygems
author: Samuel Giddins
author_date: 2018-02-11 21:44:58-08:00
msg: Implement a safe mkdir for package that verifies were inside the destination dir for all new directories ccreated
repository: repo_url: https://github.com/rubygems/rubygems
repo_name: rubygems/rubygems
description: Library packaging and distribution for Ruby.
repo_language: Ruby
owner: rubygems
file_changes: filename: package.rb
change_type: ModificationType.MODIFY
diff: @@ -378,7 +378,7 @@ def extract_tar_gz io, destination_dir, pattern = "*" # :nodoc:
             File.dirname destination
           end
 
-        FileUtils.mkdir_p mkdir, mkdir_options
+        mkdir_p_safe mkdir, mkdir_options, destination_dir, entry.full_name
 
         File.open destination, 'wb' do |out|
           out.write entry.read
@@ -416,13 +416,10 @@ def install_location filename, destination_dir # :nodoc:
     raise Gem::Package::PathError.new(filename, destination_dir) if
       filename.start_with? '/'
 
-    destination_dir = File.realpath destination_dir if
-      File.respond_to? :realpath
+    destination_dir = realpath destination_dir
     destination_dir = File.expand_path destination_dir
 
     destination = File.join destination_dir, filename
-    destination = File.realpath destination if
-      File.respond_to? :realpath
     destination = File.expand_path destination
 
     raise Gem::Package::PathError.new(destination, destination_dir) unless
@@ -432,6 +429,22 @@ def install_location filename, destination_dir # :nodoc:
     destination
   end
 
+  def mkdir_p_safe mkdir, mkdir_options, destination_dir, file_name
+    destination_dir = realpath File.expand_path(destination_dir)
+    parts = mkdir.split(File::SEPARATOR)
+    parts.reduce do |path, basename|
+      path = realpath path  unless path == ""
+      path = File.expand_path(path + File::SEPARATOR + basename)
+      lstat = File.lstat path rescue nil
+      if !lstat || !lstat.directory?
+        unless path.start_with? destination_dir and (FileUtils.mkdir path, mkdir_options rescue false)
+          raise Gem::Package::PathError.new(file_name, destination_dir)
+        end
+      end
+      path
+    end
+  end
+
   ##
   # Loads a Gem::Specification from the TarEntry +entry+
 
@@ -622,6 +635,16 @@ def verify_gz entry # :nodoc:
     raise Gem::Package::FormatError.new(e.message, entry.full_name)
   end
 
+  if File.respond_to? :realpath
+    def realpath file
+      File.realpath file
+    end
+  else
+    def realpath file
+      file
+    end
+  end
+
 end
 
 require 'rubygems/package/digest_io'

programming_language: Ruby
filename: test_gem_package.rb
change_type: ModificationType.MODIFY
diff: @@ -476,7 +476,7 @@ def test_extract_symlink_parent
      package.extract_tar_gz tgz_io, destination_subdir
    end
 
-   assert_equal("installing into parent path ../outside.txt of " +
+   assert_equal("installing into parent path lib/link/outside.txt of " +
                  "#{destination_subdir} is not allowed", e.message)
   end
 

programming_language: Ruby