Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions modules/auxiliary/admin/kerberos/forge_ticket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,7 @@ def forge_ccache(sname:, flags:, is_golden:)
is_golden: is_golden
)

Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ccache, framework_module: self)

if datastore['VERBOSE']
print_ccache_contents(ccache, key: enc_key)
end
store_forged_ticket(ccache, key: enc_key, ticket_type: is_golden ? 'TGT' : 'TGS')
end

def forge_silver
Expand Down Expand Up @@ -175,11 +171,7 @@ def forge_diamond
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError
fail_with(Msf::Exploit::Failure::BadConfig, 'Failed to modify ticket. krbtgt key is likely incorrect')
end
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
store_forged_ticket(ticket, key: enc_key, ticket_type: 'TGT', host: datastore['RHOST'])
end

def forge_sapphire
Expand Down Expand Up @@ -214,11 +206,7 @@ def forge_sapphire
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
store_forged_ticket(ticket, key: enc_key, ticket_type: 'TGT', host: datastore['RHOST'])
end

def validate_remote
Expand Down Expand Up @@ -274,6 +262,18 @@ def get_enc_key_and_type
[enc_key, enc_type]
end

def store_forged_ticket(ccache, key:, ticket_type:, host: nil)
stored_ccache = Msf::Exploit::Remote::Kerberos::Ticket::Storage.store_ccache(ccache, framework_module: self, host: host)

trace_mode = kerberos_offline_trace_mode
return stored_ccache unless trace_mode || datastore['VERBOSE']

key = nil if trace_mode && trace_mode != Rex::Proto::Kerberos::CredentialCache::Krb5CcachePresenter::TRACE_MODE_FULL
print_ccache_contents(ccache, key: key, source: "#{action.name} #{ticket_type}")

stored_ccache
end

def validate_spn!
unless datastore['SPN'] =~ %r{.*/.*}
fail_with(Msf::Exploit::Failure::BadConfig, 'Invalid SPN, must be in the format <service class>/<host><realm>:<port>/<service name>. Ex: cifs/host.realm.local')
Expand Down
204 changes: 204 additions & 0 deletions spec/modules/auxiliary/admin/kerberos/forge_ticket_trace_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# frozen_string_literal: true

require 'rspec'

RSpec.describe 'kerberos forge ticket trace' do
include_context 'Msf::UIDriver'
include_context 'Msf::Simple::Framework#modules loading'

let(:mod) do
load_and_create_module(
module_type: 'auxiliary',
reference_name: 'admin/kerberos/forge_ticket'
)
end

before(:each) do
mod.datastore['VERBOSE'] = false
allow(driver).to receive(:input).and_return(driver_input)
allow(driver).to receive(:output).and_return(driver_output)
mod.init_ui(driver_input, driver_output)
end

describe '#run' do
it 'does not print forged ticket trace output when disabled' do
configure_manual_ticket(action: 'FORGE_GOLDEN')
mod.datastore['KerberosTicketTrace'] = 'off'

mod.run

output = @output.join("\n")
expect(output).to include('TGT MIT Credential Cache ticket saved to')
expect(output).not_to include('Kerberos Credential: FORGE_GOLDEN TGT')
end

it 'prints golden ticket metadata without secret key material' do
configure_manual_ticket(action: 'FORGE_GOLDEN')
mod.datastore['KerberosTicketTrace'] = 'metadata'

mod.run

output = @output.join("\n")
expect(output).to include('# Kerberos Credential: FORGE_GOLDEN TGT')
expect(output).to include('Server: krbtgt/DEMO.LOCAL@DEMO.LOCAL')
expect(output).to include('Client: Administrator@DEMO.LOCAL')
expect(output).to include('Ticket etype: 23 (RC4_HMAC)')
expect(output).to include('Ticket Flags: 0x50e00000 (FORWARDABLE, PROXIABLE, RENEWABLE, INITIAL, PRE_AUTHENT)')
expect(output).not_to include('767400b2c71afa35a5dca216f2389cd9')
expect(output).not_to include('Cipher:')
end

it 'prints the trace once when verbose output is enabled' do
configure_manual_ticket(action: 'FORGE_GOLDEN')
mod.datastore['KerberosTicketTrace'] = 'metadata'
mod.datastore['VERBOSE'] = true

mod.run

expect(@output.join("\n").scan('# Kerberos Credential: FORGE_GOLDEN TGT').length).to eq(1)
end

it 'prints a silver ticket without decrypting it in ticket mode' do
configure_manual_ticket(action: 'FORGE_SILVER', spn: 'cifs/dc.demo.local')
mod.datastore['KerberosTicketTrace'] = 'ticket'

mod.run

output = @output.join("\n")
expect(output).to include('# Kerberos Credential: FORGE_SILVER TGS')
expect(output).to include('Server: cifs/dc.demo.local@DEMO.LOCAL')
expect(output).to include('Cipher:')
expect(output).not_to include('Decrypted (with key: 767400b2c71afa35a5dca216f2389cd9):')
end

it 'decrypts the golden ticket in full mode' do
configure_manual_ticket(action: 'FORGE_GOLDEN')
mod.datastore['KerberosTicketTrace'] = 'full'

mod.run

output = @output.join("\n")
expect(output).to include('# Kerberos Credential: FORGE_GOLDEN TGT')
expect(output).to include('Decrypted (with key: 767400b2c71afa35a5dca216f2389cd9):')
expect(output).to include('Logon Domain ID: S-1-5-21-1266190811-2419310613-1856291569')
end

it 'traces the final modified diamond ticket' do
configure_remote_ticket(action: 'FORGE_DIAMOND')
ccache = build_ccache
tgt_result = double(
'tgt_result',
krb_enc_key: { enctype: Rex::Proto::Kerberos::Crypto::Encryption::AES256 },
as_rep: double('as_rep', ticket: double('ticket')),
decrypted_part: double('decrypted_part')
)
allow(mod).to receive(:send_request_tgt).and_return(tgt_result)
expect(mod).to receive(:modify_ticket).and_return(ccache)
allow(Msf::Exploit::Remote::Kerberos::Ticket::Storage).to receive(:store_ccache)

mod.run

output = @output.join("\n")
expect(output).to include('# Kerberos Credential: FORGE_DIAMOND TGT')
expect(output).to include('Client: Administrator@DEMO.LOCAL')
end

it 'traces the final modified sapphire ticket' do
configure_remote_ticket(action: 'FORGE_SAPPHIRE')
ccache = build_ccache
credential = double(
'credential',
keyblock: double(
'keyblock',
enctype: double('enctype', value: Rex::Proto::Kerberos::Crypto::Encryption::AES256),
data: double('data', value: 'C' * 32)
)
)
authenticator = double('authenticator')
allow(authenticator).to receive(:authenticate_via_kdc).and_return({ credential: credential })
allow(authenticator).to receive(:u2uself).with(credential, impersonate: 'Administrator').and_return([double('tgs_ticket'), double('tgs_auth')])
allow(mod).to receive(:kerberos_authenticator).and_return(authenticator)
expect(mod).to receive(:modify_ticket).and_return(ccache)
allow(Msf::Exploit::Remote::Kerberos::Ticket::Storage).to receive(:store_ccache)

mod.run

output = @output.join("\n")
expect(output).to include('# Kerberos Credential: FORGE_SAPPHIRE TGT')
expect(output).to include('Client: Administrator@DEMO.LOCAL')
end
end

describe '#store_forged_ticket' do
let(:ccache) { build_ccache }
let(:stored_ccache) { { path: '/tmp/forged-ticket.ccache', loot: double('loot') } }

before(:each) do
configure_manual_ticket(action: 'FORGE_GOLDEN')
allow(Msf::Exploit::Remote::Kerberos::Ticket::Storage).to receive(:store_ccache).and_return(stored_ccache)
end

it 'returns the stored ccache when trace output is disabled' do
mod.datastore['KerberosTicketTrace'] = 'off'

result = mod.send(:store_forged_ticket, ccache, key: nil, ticket_type: 'TGT')

expect(result).to be(stored_ccache)
end

it 'returns the stored ccache after printing trace output' do
mod.datastore['KerberosTicketTrace'] = 'metadata'

result = mod.send(:store_forged_ticket, ccache, key: nil, ticket_type: 'TGT')

expect(result).to be(stored_ccache)
expect(@output.join("\n")).to include('# Kerberos Credential: FORGE_GOLDEN TGT')
end
end

def configure_manual_ticket(action:, spn: nil)
mod.datastore['ACTION'] = action
mod.datastore['DOMAIN'] = 'demo.local'
mod.datastore['DOMAIN_SID'] = 'S-1-5-21-1266190811-2419310613-1856291569'
mod.datastore['NTHASH'] = '767400b2c71afa35a5dca216f2389cd9'
mod.datastore['AES_KEY'] = nil
mod.datastore['USER'] = 'Administrator'
mod.datastore['USER_RID'] = 500
mod.datastore['RPORT'] = ''
mod.datastore['EXTRA_SIDS'] = 'S-1-18-1'
mod.datastore['SessionKey'] = 'A' * 16
mod.datastore['SPN'] = spn if spn
end

def configure_remote_ticket(action:)
mod.datastore['ACTION'] = action
mod.datastore['DOMAIN'] = 'demo.local'
mod.datastore['USER'] = 'Administrator'
mod.datastore['USER_RID'] = 500
mod.datastore['REQUEST_USER'] = 'requester'
mod.datastore['REQUEST_PASSWORD'] = 'Password1!'
mod.datastore['RHOSTS'] = '192.0.2.10'
mod.datastore['RPORT'] = 88
mod.datastore['NTHASH'] = nil
mod.datastore['AES_KEY'] = 'b' * 64
mod.datastore['EXTRA_SIDS'] = 'S-1-18-1'
mod.datastore['KerberosTicketTrace'] = 'metadata'
end

def build_ccache
mod.forge_ticket(
enc_key: ['b' * 64].pack('H*'),
enc_type: Rex::Proto::Kerberos::Crypto::Encryption::AES256,
start_time: Time.utc(2026, 1, 1, 0, 0, 0),
end_time: Time.utc(2026, 1, 2, 0, 0, 0),
sname: ['krbtgt', 'DEMO.LOCAL'],
flags: Rex::Proto::Kerberos::Model::TicketFlags.from_flags(mod.tgt_flags),
domain: 'demo.local',
username: 'Administrator',
user_id: 500,
domain_sid: 'S-1-5-21-1266190811-2419310613-1856291569',
extra_sids: ['S-1-18-1'],
session_key: 'B' * 32
)
end
end
Loading