CVE-2018-1000074: Deserialization of Untrusted Data (CWE-502)
value: 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 Deserialization of Untrusted Data vulnerability in owner command that can result in code execution. This attack appear to be exploitable via victim must run the `gem owner` command on a gem with a specially crafted YAML file. This vulnerability appears to have been fixed in 2.7.6.
CWE description: The product deserializes untrusted data without sufficiently verifying that the resulting data will be valid.
CVSS v3 base score: 7.8
Published: 2018-03-13T15:29Z
Repository: https://github.com/rubygems/rubygems
Language: Ruby
Files changed: lib/rubygems/commands/owner_command.rb, test/rubygems/test_gem_commands_owner_command.rb
Commit date: 2018-02-15 22:19:04 -0800
Commit message: Fix possible Unsafe Object Deserialization Vulnerability in gem owner

Diff: diff --git a/lib/rubygems/commands/owner_command.rb b/lib/rubygems/commands/owner_command.rb
index 8e2271657a..637b5bdc4d 100644
--- a/lib/rubygems/commands/owner_command.rb
+++ b/lib/rubygems/commands/owner_command.rb
@@ -62,11 +62,11 @@ def show_owners name
     response = rubygems_api_request :get, "api/v1/gems/#{name}/owners.yaml" do |request|
       request.add_field "Authorization", api_key
     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|
         say "- #{owner['email'] || owner['handle'] || owner['id']}"
       end
diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb
index 44652c1093..05230772d9 100644
--- a/test/rubygems/test_gem_commands_owner_command.rb
+++ b/test/rubygems/test_gem_commands_owner_command.rb
@@ -1,9 +1,12 @@
 # frozen_string_literal: true
 require 'rubygems/test_case'
 require 'rubygems/commands/owner_command'
 
+class Foo
+end
+
 class TestGemCommandsOwnerCommand < Gem::TestCase
 
   def setup
     super
 
@@ -41,10 +44,35 @@ def test_show_owners
     assert_match %r{- user2@example.com}, @ui.output
     assert_match %r{- user3}, @ui.output
     assert_match %r{- 4}, @ui.output
   end
 
+  def test_show_owners_dont_load_objects
+    skip "testing a psych-only API" unless defined?(::Psych::DisallowedClass)
+
+    response = <<EOF
+---
+- email: !ruby/object:Foo {}
+  id: 1
+  handle: user1
+- email: user2@example.com
+- id: 3
+  handle: user3
+- id: 4
+EOF
+
+    @fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 200, 'OK']
+
+    assert_raises Psych::DisallowedClass do
+      use_ui @ui do
+        @cmd.show_owners("freewill")
+      end
+    end
+
+  end
+
+
   def test_show_owners_setting_up_host_through_env_var
     response = "- email: user1@example.com\n"
     host = "http://rubygems.example"
     ENV["RUBYGEMS_HOST"] = host
 

Vulnerable code:       owners = YAML.load resp.body
Fixed code:       owners = Gem::SafeYAML.load resp.body
class Foo
end

  def test_show_owners_dont_load_objects
    skip "testing a psych-only API" unless defined?(::Psych::DisallowedClass)

    response = <<EOF
---
- email: !ruby/object:Foo {}
  id: 1
  handle: user1
- email: user2@example.com
- id: 3
  handle: user3
- id: 4
EOF

    @fetcher.data["#{Gem.host}/api/v1/gems/freewill/owners.yaml"] = [response, 200, 'OK']

    assert_raises Psych::DisallowedClass do
      use_ui @ui do
        @cmd.show_owners("freewill")
      end
    end

  end