CVE-2017-0903 (published 2017-10-11T18:29Z)

RubyGems versions between 2.0.0 and 2.6.13 are vulnerable to a possible remote code execution vulnerability. YAML deserialization of gem specifications can bypass class white lists. Specially crafted serialized objects can possibly be used to escalate to remote code execution.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-502
cwe_name: Deserialization of Untrusted Data
description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
extended_description: 
            It is often convenient to serialize objects for communication or to save them for later use. However, deserialized data or code can often be modified without using the provided accessor functions if it does not use cryptography to protect itself. Furthermore, any cryptography would still be client-side security -- which is a dangerous security assumption.
            Data that is untrusted can not be trusted to be well-formed.
            When developers place no restrictions on "gadget chains," or series of instances and method invocations that can self-execute during the deserialization process (i.e., before the object is returned to the caller), it is sometimes possible for attackers to leverage them to perform unauthorized actions, like generating a shell.
         
         

Fix information:
repo_url: https://github.com/rubygems/rubygems
commit_details: repo_url: https://github.com/rubygems/rubygems
author: Aaron Patterson
author_date: 2017-10-06 11:11:40-07:00
msg: Whitelist classes and symbols that are in Gem spec YAML

This patch adds a method for loading YAML specs from a gem and
whitelists classes and symbols that are allowed in the spec.  Then it
changes calls to YAML.load to call the whitelisted "safe" loader
instead.

[CVE-2017-0903]
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: Manifest.txt
change_type: ModificationType.MODIFY
diff: @@ -387,6 +387,7 @@ lib/rubygems/resolver/specification.rb
 lib/rubygems/resolver/stats.rb
 lib/rubygems/resolver/vendor_set.rb
 lib/rubygems/resolver/vendor_specification.rb
+lib/rubygems/safe_yaml.rb
 lib/rubygems/security.rb
 lib/rubygems/security/policies.rb
 lib/rubygems/security/policy.rb

programming_language: Shell
filename: rubygems.rb
change_type: ModificationType.MODIFY
diff: @@ -675,7 +675,7 @@ def self.load_yaml
 
     unless test_syck
       begin
-        gem 'psych', '>= 1.2.1'
+        gem 'psych', '>= 2.0.0'
       rescue Gem::LoadError
         # It's OK if the user does not have the psych gem installed.  We will
         # attempt to require the stdlib version
@@ -699,6 +699,7 @@ def self.load_yaml
     end
 
     require 'yaml'
+    require 'rubygems/safe_yaml'
 
     # If we're supposed to be using syck, then we may have to force
     # activate it via the YAML::ENGINE API.

programming_language: Ruby
filename: config_file.rb
change_type: ModificationType.MODIFY
diff: @@ -345,7 +345,7 @@ def load_file(filename)
     return {} unless filename and File.exist? filename
 
     begin
-      content = YAML.load(File.read(filename))
+      content = Gem::SafeYAML.load(File.read(filename))
       unless content.kind_of? Hash
         warn "Failed to load #{filename} because it doesn't contain valid YAML hash"
         return {}

programming_language: Ruby
filename: package.rb
change_type: ModificationType.MODIFY
diff: @@ -468,7 +468,7 @@ def read_checksums gem
 
     @checksums = gem.seek 'checksums.yaml.gz' do |entry|
       Zlib::GzipReader.wrap entry do |gz_io|
-        YAML.load gz_io.read
+        Gem::SafeYAML.safe_load gz_io.read
       end
     end
   end

programming_language: Ruby
filename: old.rb
change_type: ModificationType.MODIFY
diff: @@ -101,7 +101,7 @@ def file_list io # :nodoc:
       header << line
     end
 
-    YAML.load header
+    Gem::SafeYAML.safe_load header
   end
 
   ##

programming_language: Ruby
filename: safe_yaml.rb
change_type: ModificationType.ADD
diff: @@ -0,0 +1,48 @@
+module Gem
+
+  ###
+  # This module is used for safely loading YAML specs from a gem.  The
+  # `safe_load` method defined on this module is specifically designed for
+  # loading Gem specifications.  For loading other YAML safely, please see
+  # Psych.safe_load
+
+  module SafeYAML
+    WHITELISTED_CLASSES = %w(
+      Symbol
+      Time
+      Date
+      Gem::Dependency
+      Gem::Platform
+      Gem::Requirement
+      Gem::Specification
+      Gem::Version
+      Gem::Version::Requirement
+      YAML::Syck::DefaultKey
+      Syck::DefaultKey
+    )
+
+    WHITELISTED_SYMBOLS = %w(
+      development
+      runtime
+    )
+
+    if ::YAML.respond_to? :safe_load
+      def self.safe_load input
+        ::YAML.safe_load(input, WHITELISTED_CLASSES, WHITELISTED_SYMBOLS, true)
+      end
+
+      def self.load input
+        ::YAML.safe_load(input, [::Symbol])
+      end
+    else
+      warn "YAML safe loading is not available. Please upgrade psych to a version that supports safe loading (>= 2.0)."
+      def self.safe_load input, *args
+        ::YAML.load input
+      end
+
+      def self.load input
+        ::YAML.load input
+      end
+    end
+  end
+end

programming_language: Ruby
filename: specification.rb
change_type: ModificationType.MODIFY
diff: @@ -1101,7 +1101,7 @@ def self.from_yaml(input)
     Gem.load_yaml
 
     input = normalize_yaml_input input
-    spec = YAML.load input
+    spec = Gem::SafeYAML.safe_load input
 
     if spec && spec.class == FalseClass then
       raise Gem::EndOfYAMLException

programming_language: Ruby