MS12-020 Microsoft Remote Desktop Checker

2013-02-01T20:39:01
ID MSF:AUXILIARY/SCANNER/RDP/MS12_020_CHECK
Type metasploit
Reporter Rapid7
Modified 2017-07-24T13:26:21

Description

This module checks a range of hosts for the MS12-020 vulnerability. This does not cause a DoS on the target.

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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::Tcp
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'MS12-020 Microsoft Remote Desktop Checker',
      'Description'    => %q{
        This module checks a range of hosts for the MS12-020 vulnerability.
        This does not cause a DoS on the target.
      },
      'References'     =>
        [
          [ 'CVE', '2012-0002' ],
          [ 'MSB', 'MS12-020' ],
          [ 'URL', 'http://technet.microsoft.com/en-us/security/bulletin/ms12-020' ],
          [ 'EDB', '18606' ],
          [ 'URL', 'https://svn.nmap.org/nmap/scripts/rdp-vuln-ms12-020.nse' ]
        ],
      'Author'         =>
        [
          'Royce Davis "R3dy" <rdavis[at]accuvant.com>',
          'Brandon McCann "zeknox" <bmccann[at]accuvant.com>'
        ],
      'License'        => MSF_LICENSE
    ))

    register_options(
      [
        OptPort.new('RPORT', [ true, 'Remote port running RDP', 3389 ])
      ])
  end

  def check_rdp
    # code to check if RDP is open or not
    vprint_status("Verifying RDP protocol...")

    # send connection
    sock.put(connection_request)

    # read packet to see if its rdp
    res = sock.get_once(-1, 5)

    # return true if this matches our vulnerable response
    ( res and res.match("\x03\x00\x00\x0b\x06\xd0\x00\x00\x12\x34\x00") )
  end

  def report_goods
    report_vuln(
      :host         => rhost,
      :port         => rport,
      :proto        => 'tcp',
      :name         => self.name,
      :info         => 'Response indicates a missing patch',
      :refs         => self.references
    )
  end

  def connection_request
    "\x03\x00" +    # TPKT Header version 03, reserved 0
    "\x00\x0b" +    # Length
    "\x06" +        # X.224 Data TPDU length
    "\xe0" +        # X.224 Type (Connection request)
    "\x00\x00" +    # dst reference
    "\x00\x00" +    # src reference
    "\x00"          # class and options
  end

  def connect_initial
    "\x03\x00\x00\x65" + # TPKT Header
    "\x02\xf0\x80" +     # Data TPDU, EOT
    "\x7f\x65\x5b" +     # Connect-Initial
    "\x04\x01\x01" +     # callingDomainSelector
    "\x04\x01\x01" +     # callingDomainSelector
    "\x01\x01\xff" +     # upwardFlag
    "\x30\x19" +         # targetParams + size
    "\x02\x01\x22" +     # maxChannelIds
    "\x02\x01\x20" +     # maxUserIds
    "\x02\x01\x00" +     # maxTokenIds
    "\x02\x01\x01" +     # numPriorities
    "\x02\x01\x00" +     # minThroughput
    "\x02\x01\x01" +     # maxHeight
    "\x02\x02\xff\xff" + # maxMCSPDUSize
    "\x02\x01\x02" +     # protocolVersion
    "\x30\x18" +         # minParams + size
    "\x02\x01\x01" +     # maxChannelIds
    "\x02\x01\x01" +     # maxUserIds
    "\x02\x01\x01" +     # maxTokenIds
    "\x02\x01\x01" +     # numPriorities
    "\x02\x01\x00" +     # minThroughput
    "\x02\x01\x01" +     # maxHeight
    "\x02\x01\xff" +     # maxMCSPDUSize
    "\x02\x01\x02" +     # protocolVersion
    "\x30\x19" +         # maxParams + size
    "\x02\x01\xff" +     # maxChannelIds
    "\x02\x01\xff" +     # maxUserIds
    "\x02\x01\xff" +     # maxTokenIds
    "\x02\x01\x01" +     # numPriorities
    "\x02\x01\x00" +     # minThroughput
    "\x02\x01\x01" +     # maxHeight
    "\x02\x02\xff\xff" + # maxMCSPDUSize
    "\x02\x01\x02" +     # protocolVersion
    "\x04\x00"           # userData
  end

  def user_request
    "\x03\x00" +         # header
    "\x00\x08" +         # length
    "\x02\xf0\x80" +     # X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission)
    "\x28"               # PER encoded PDU contents
  end

  def channel_request
    "\x03\x00\x00\x0c" +
    "\x02\xf0\x80\x38"
  end


  def check_rdp_vuln
    # check if rdp is open
    unless check_rdp
      vprint_status "Could not connect to RDP."
      return Exploit::CheckCode::Unknown
    end

    # send connectInitial
    sock.put(connect_initial)

    # send userRequest
    sock.put(user_request)
    res = sock.get_once(-1, 5)
    return Exploit::CheckCode::Unknown unless res # nil due to a timeout
    user1 = res[9,2].unpack("n").first
    chan1 = user1 + 1001

    # send 2nd userRequest
    sock.put(user_request)
    res = sock.get_once(-1, 5)
    return Exploit::CheckCode::Unknown unless res # nil due to a timeout
    user2 = res[9,2].unpack("n").first
    chan2 = user2 + 1001

    # send channel request one
    sock.put(channel_request << [user1, chan2].pack("nn"))
    res = sock.get_once(-1, 5)
    return Exploit::CheckCode::Unknown unless res # nil due to a timeout
    if res[7,2] == "\x3e\x00"
      # send ChannelRequestTwo - prevent BSoD
      sock.put(channel_request << [user2, chan2].pack("nn"))

      report_goods
      return Exploit::CheckCode::Vulnerable
    else
      return Exploit::CheckCode::Safe
    end

    # Can't determine, but at least I know the service is running
    return Exploit::CheckCode::Detected
  end

  def check_host(ip)
    # The check command will call this method instead of run_host

    status = Exploit::CheckCode::Unknown

    begin
      connect
      status = check_rdp_vuln
    rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
      bt = e.backtrace.join("\n")
      vprint_error("Unexpected error: #{e.message}")
      vprint_line(bt)
      elog("#{e.message}\n#{bt}")
    ensure
      disconnect
    end

    status
  end

  def run_host(ip)
    # Allow the run command to call the check command
    status = check_host(ip)
    if status == Exploit::CheckCode::Vulnerable
      print_good("#{ip}:#{rport} - #{status[1]}")
    else
      print_status("#{ip}:#{rport} - #{status[1]}")
    end
  end
end