CVE-2018-1000073
Description
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 install_location function of package.rb that can result in path traversal when writing to a symlinked basedir outside of the root. This vulnerability appears to have been fixed in 2.7.6.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Directory traversal in RubyGems' install_location function allows writing to arbitrary filesystem paths via a symlinked basedir outside root.
Vulnerability
CVE-2018-1000073 is a directory traversal vulnerability in the RubyGems component of Ruby. The issue resides in the install_location function of package.rb and affects Ruby 2.2.9 and earlier, 2.3.6 and earlier, 2.4.3 and earlier, and 2.5.0 and earlier, prior to trunk revision 62422 [1][2][3][4]. An attacker can craft a gem that, when installed, causes files to be written to a path outside the intended directory by leveraging a symlink in the base installation directory.
Exploitation
To exploit this vulnerability, an attacker needs to convince a user or automated system to install a malicious gem. The attack requires a symlink to be present in the gem's base installation directory (basedir) pointing to an arbitrary filesystem location. During gem installation, the install_location function follows the symlink and writes gem files to the target location specified by the symlink, bypassing path restrictions.
Impact
Successful exploitation allows an attacker to write files to arbitrary locations on the filesystem with the privileges of the Ruby process performing the installation. This can lead to overwriting critical system files, configuration files, or user data, potentially enabling privilege escalation, denial of service, or remote code execution depending on the written content and permissions.
Mitigation
This vulnerability is fixed in Ruby 2.7.6 and later versions. Red Hat has released updated packages for various Ruby versions in RHSA-2018:3731, RHSA-2018:3730, RHSA-2018:3729, and RHSA-2020:0663 [1][2][3][4]. Users should upgrade RubyGems or Ruby to a patched version and ensure they only install gems from trusted sources.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
rubygems-updateRubyGems | < 2.7.6 | 2.7.6 |
org.jruby:jruby-stdlibMaven | < 9.1.16.0 | 9.1.16.0 |
Affected products
29- ghsa-coords29 versionspkg:gem/rubygems-updatepkg:maven/org.jruby/jruby-stdlibpkg:rpm/opensuse/ruby2.5&distro=openSUSE%20Leap%2015.0pkg:rpm/opensuse/ruby2.5&distro=openSUSE%20Leap%2015.1pkg:rpm/opensuse/ruby-bundled-gems-rpmhelper&distro=openSUSE%20Leap%2015.0pkg:rpm/opensuse/ruby-bundled-gems-rpmhelper&distro=openSUSE%20Leap%2015.1pkg:rpm/suse/ruby2.1&distro=HPE%20Helion%20OpenStack%208pkg:rpm/suse/ruby2.1&distro=SUSE%20Enterprise%20Storage%205pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP2-BCLpkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP2-LTSSpkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP3-BCLpkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP3-LTSSpkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP4pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP5pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP2pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP3pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP4pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP5pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Software%20Development%20Kit%2012%20SP4pkg:rpm/suse/ruby2.1&distro=SUSE%20Linux%20Enterprise%20Software%20Development%20Kit%2012%20SP5pkg:rpm/suse/ruby2.1&distro=SUSE%20OpenStack%20Cloud%207pkg:rpm/suse/ruby2.1&distro=SUSE%20OpenStack%20Cloud%208pkg:rpm/suse/ruby2.1&distro=SUSE%20OpenStack%20Cloud%20Crowbar%208pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP1pkg:rpm/suse/yast2-ruby-bindings&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP2-BCLpkg:rpm/suse/yast2-ruby-bindings&distro=SUSE%20Linux%20Enterprise%20Server%2012%20SP2-LTSSpkg:rpm/suse/yast2-ruby-bindings&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2012%20SP2pkg:rpm/suse/yast2-ruby-bindings&distro=SUSE%20OpenStack%20Cloud%207
< 2.7.6+ 28 more
- (no CPE)range: < 2.7.6
- (no CPE)range: < 9.1.16.0
- (no CPE)range: < 2.5.5-lp151.4.3.1
- (no CPE)range: < 2.5.5-lp151.4.3.1
- (no CPE)range: < 0.0.2-lp151.2.1
- (no CPE)range: < 0.0.2-lp151.2.1
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.1.9-19.3.2
- (no CPE)range: < 2.5.5-4.3.1
- (no CPE)range: < 2.5.5-4.3.1
- (no CPE)range: < 3.1.53-9.8.1
- (no CPE)range: < 3.1.53-9.8.1
- (no CPE)range: < 3.1.53-9.8.1
- (no CPE)range: < 3.1.53-9.8.1
Patches
20b06b48ab443Apply backported RubyGems 2.7.6 patch.
7 files changed · +75 −20
lib/ruby/stdlib/rubygems/commands/owner_command.rb+1 −1 modified@@ -62,7 +62,7 @@ def show_owners name end with_response response do |resp| - owners = YAML.load resp.body + owners = Gem::SafeYAML.load resp.body say "Owners for gem: #{name}" owners.each do |owner|
lib/ruby/stdlib/rubygems/package.rb+33 −4 modified@@ -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 open destination, 'wb' do |out| out.write entry.read @@ -416,20 +416,35 @@ 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.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 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+ @@ -603,6 +618,10 @@ def verify_files gem raise Gem::Package::FormatError.new \ 'package content (data.tar.gz) is missing', @gem end + + if duplicates = @files.group_by {|f| f }.select {|k,v| v.size > 1 }.map(&:first) and duplicates.any? + raise Gem::Security::Exception, "duplicate files in the package: (#{duplicates.map(&:inspect).join(', ')})" + end end ## @@ -616,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'
lib/ruby/stdlib/rubygems/package/tar_header.rb+14 −9 modified@@ -104,25 +104,30 @@ def self.from(stream) fields = header.unpack UNPACK_FORMAT new :name => fields.shift, - :mode => fields.shift.oct, - :uid => fields.shift.oct, - :gid => fields.shift.oct, - :size => fields.shift.oct, - :mtime => fields.shift.oct, - :checksum => fields.shift.oct, + :mode => strict_oct(fields.shift), + :uid => strict_oct(fields.shift), + :gid => strict_oct(fields.shift), + :size => strict_oct(fields.shift), + :mtime => strict_oct(fields.shift), + :checksum => strict_oct(fields.shift), :typeflag => fields.shift, :linkname => fields.shift, :magic => fields.shift, - :version => fields.shift.oct, + :version => strict_oct(fields.shift), :uname => fields.shift, :gname => fields.shift, - :devmajor => fields.shift.oct, - :devminor => fields.shift.oct, + :devmajor => strict_oct(fields.shift), + :devminor => strict_oct(fields.shift), :prefix => fields.shift, :empty => empty end + def self.strict_oct(str) + return str.oct if str =~ /\A[0-7]*\z/ + raise ArgumentError, "#{str.inspect} is not an octal string" + end + ## # Creates a new TarHeader using +vals+
lib/ruby/stdlib/rubygems/package/tar_writer.rb+2 −0 modified@@ -196,6 +196,8 @@ def add_file_signed name, mode, signer digest_name == signer.digest_name end + raise "no #{signer.digest_name} in #{digests.values.compact}" unless signature_digest + if signer.key then signature = signer.sign signature_digest.digest
lib/ruby/stdlib/rubygems.rb+1 −1 modified@@ -10,7 +10,7 @@ require 'thread' module Gem - VERSION = "2.6.14" + VERSION = "2.6.14.1" end # Must be first since it unloads the prelude from 1.9.2
lib/ruby/stdlib/rubygems/server.rb+13 −1 modified@@ -631,6 +631,18 @@ def root(req, res) executables = nil if executables.empty? executables.last["is_last"] = true if executables + # Pre-process spec homepage for safety reasons + begin + homepage_uri = URI.parse(spec.homepage) + if [URI::HTTP, URI::HTTPS].member? homepage_uri.class + homepage_uri = spec.homepage + else + homepage_uri = "." + end + rescue URI::InvalidURIError + homepage_uri = "." + end + specs << { "authors" => spec.authors.sort.join(", "), "date" => spec.date.to_s, @@ -640,7 +652,7 @@ def root(req, res) "only_one_executable" => (executables && executables.size == 1), "full_name" => spec.full_name, "has_deps" => !deps.empty?, - "homepage" => spec.homepage, + "homepage" => homepage_uri, "name" => spec.name, "rdoc_installed" => Gem::RDoc.new(spec).rdoc_installed?, "ri_installed" => Gem::RDoc.new(spec).ri_installed?,
lib/ruby/stdlib/rubygems/specification.rb+11 −4 modified@@ -15,6 +15,7 @@ require 'rubygems/stub_specification' require 'rubygems/util/list' require 'stringio' +require 'uri' ## # The Specification class contains the information for a Gem. Typically @@ -2810,10 +2811,16 @@ def validate packaging = true raise Gem::InvalidSpecificationException, "#{lazy} is not a summary" end - if homepage and not homepage.empty? and - homepage !~ /\A[a-z][a-z\d+.-]*:/i then - raise Gem::InvalidSpecificationException, - "\"#{homepage}\" is not a URI" + # Make sure a homepage is valid HTTP/HTTPS URI + if homepage and not homepage.empty? + begin + homepage_uri = URI.parse(homepage) + unless [URI::HTTP, URI::HTTPS].member? homepage_uri.class + raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" + end + rescue URI::InvalidURIError + raise Gem::InvalidSpecificationException, "\"#{homepage}\" is not a valid HTTP URI" + end end # Warnings
1b931fc03b81Non-working patch for deducing symlinked base-dirs
1 file changed · +2 −0
lib/rubygems/package.rb+2 −0 modified@@ -421,6 +421,8 @@ def install_location filename, destination_dir # :nodoc: 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
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
20- lists.opensuse.org/opensuse-security-announce/2019-07/msg00036.htmlghsavendor-advisoryx_refsource_SUSEWEB
- access.redhat.com/errata/RHSA-2018:3729ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2018:3730ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2018:3731ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2019:2028ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2020:0542ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2020:0591ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2020:0663ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-gx69-6cp4-hxrjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-1000073ghsaADVISORY
- usn.ubuntu.com/3621-1/mitrevendor-advisoryx_refsource_UBUNTU
- www.debian.org/security/2018/dsa-4219ghsavendor-advisoryx_refsource_DEBIANWEB
- www.debian.org/security/2018/dsa-4259ghsavendor-advisoryx_refsource_DEBIANWEB
- blog.rubygems.org/2018/02/15/2.7.6-released.htmlghsax_refsource_MISCWEB
- bugs.debian.org/cgi-bin/bugreport.cgighsaWEB
- github.com/jruby/jruby/commit/0b06b48ab4432237ce5fc1bef47f2c6bcf7843f7ghsaWEB
- github.com/rubygems/rubygems/commit/1b931fc03b819b9a0214be3eaca844ef534175e2ghsax_refsource_MISCWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/rubygems-update/CVE-2018-1000073.ymlghsaWEB
- lists.debian.org/debian-lts-announce/2018/08/msg00028.htmlghsamailing-listx_refsource_MLISTWEB
- usn.ubuntu.com/3621-1ghsaWEB
News mentions
0No linked articles in our index yet.