~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / forest.md

forest

Windows · Easy · released 2019-10-12 · retired 2020-04-25

Summary

This writeup is reconstructed from public walkthroughs (see Source attribution below). I have not personally rooted this box. Forest is one of the most-cited “Easy” Windows machines on Hack The Box and is widely used as a teaching example for Active Directory attack chains: it stitches together unauthenticated RPC enumeration, AS-REP roasting, BloodHound graph analysis, and an Exchange-introduced ACL misconfiguration that ends in a DCSync against the domain controller. Despite its difficulty rating, the box is conceptually rich; a learner who genuinely understands Forest has the basic mental model needed for far harder AD targets.

The high-level path is short: anonymous SMB/RPC binds against the DC give us a list of domain user accounts. One of those accounts, svc-alfresco, has Kerberos pre-authentication disabled (the UF_DONT_REQUIRE_PREAUTH flag on its userAccountControl). That allows us to request an AS-REP for the account from the KDC without proving knowledge of its password, take the encrypted blob offline, and crack it with hashcat mode 18200. The recovered password gets us a WinRM shell and the user flag.

From inside the domain we run BloodHound. The graph reveals a chain of nested group memberships that lands svc-alfresco in Account Operators, which in turn has rights over the Exchange Windows Permissions group; that group, by Exchange’s installer-time default, has WriteDACL on the root of the domain naming context. Walking the chain, svc-alfresco is effectively able to rewrite the DACL on the domain object itself, which is one of the most powerful primitives in AD.

The final step turns WriteDACL on the domain into DCSync replication rights (DS-Replication-Get-Changes and DS-Replication-Get-Changes-All), at which point a single secretsdump.py call exfiltrates the krbtgt and Administrator NT hashes. A pass-the-hash to Administrator gives the root flag. Each link in the chain is a recurring real-world weakness; Forest is the canonical lab where they all line up cleanly enough to be solved in one sitting.

Source attribution

Primary source for this writeup:

Cross-referenced sources:

I wrote this guide for myself, not as an original solve. I have not verified each command against a live Forest instance; commands and group names are reproduced from the sources above and may differ slightly between BloodHound versions, Impacket releases, and PowerView forks.

Recon and AD enumeration

The first signal Forest gives is its port surface. An nmap scan against the host shows the unmistakable footprint of a domain controller: 53 (DNS), 88 (Kerberos), 135 (RPC endpoint mapper), 139 and 445 (SMB), 389 and 636 (LDAP and LDAPS), 464 (kpasswd), 593 (RPC over HTTP), 3268 and 3269 (Global Catalog LDAP and LDAPS), and 5985 (WinRM). Plus a handful of high RPC ports and 9389 (AD Web Services). The presence of WinRM on 5985 is worth flagging for later — it tells us that if we ever obtain a credential, we likely have a remote shell waiting. The script output for SMB and LDAP also leaks the domain htb.local and the hostname FOREST, so we already know we are looking at the DC for that domain. Notably, Forest does not present a web server; this is a pure AD box. A canonical opening scan looks like:

nmap -p- --min-rate 5000 -oA forest-allports <TARGET>
nmap -sC -sV -p 53,88,135,139,389,445,464,593,636,3268,3269,5985,9389 -oA forest-svcs <TARGET>

The two-phase approach (discover all open ports, then run scripts only against the ones that are open) is the standard practice on AD targets because the default 1000-port nmap is missing several AD services. Verifying the same with nmap --script smb-os-discovery,smb2-security-mode -p 445 <TARGET> reveals Windows Server 2016 Standard, which gives us a baseline for which mitigations are likely already on (SMB signing, default Kerberos AES, etc.) and which legacy issues might still apply (anonymous SAMR, default Exchange ACLs).

A second AD-specific enumeration habit is to time-sync to the DC before doing anything Kerberos-related: sudo ntpdate <TARGET> or sudo rdate -n <TARGET>. Kerberos rejects authentication when client and server clocks drift more than five minutes; chasing that error is a frustrating early failure mode, so we eliminate it up front.

The first meaningful question on any AD target is: what can we enumerate without credentials? The answer on Forest is “a lot,” because the domain still permits anonymous bindings to RPC. We point rpcclient at the host with empty credentials:

rpcclient -U "" -N <TARGET>

The flags -U "" and -N request an anonymous (null) session — empty username, no password prompt. Once at the rpcclient $> prompt we can issue MS-SAMR queries that should normally require an authenticated user. The two most useful are enumdomusers (lists user objects) and querydispinfo (gives the same data with descriptions). On Forest, enumdomusers returns roughly thirty accounts, including Administrator, Guest, krbtgt, half a dozen Exchange-related service accounts (SM_* and HealthMailbox*), and the user that turns out to be the foothold: svc-alfresco. We also note Exchange artifacts (SystemMailbox{...}, SM_2c8eef0a09b545acb, etc.), which tell us the box is running on-premises Exchange — an important hint for the privilege escalation chain.

It is easy to gloss over how broken this is. By default a modern AD domain does not permit anonymous SAMR enumeration. Forest is configured with the legacy “Pre-Windows 2000 Compatible Access” group, which maps the Anonymous Logon SID into a privileged read context against directory data. Microsoft has documented this for years and many real environments still carry the setting forward — usually because some long-retired NT4 application required it and no one ever cleared the legacy ACE. Production AD audits frequently catch it. Tools like enum4linux-ng -A <TARGET> and crackmapexec smb <TARGET> --users -u '' -p '' will surface the same data as rpcclient and are worth running in parallel; if any of them gives you the user list, the chain proceeds.

While we are doing recon, we also try anonymous LDAP. ldapsearch -x -H ldap://<TARGET> -b 'DC=htb,DC=local' either returns the directory or an operationsError. On Forest, anonymous LDAP returns a partial directory tree — including user objects — which is yet another path to the same enumeration. We additionally pull DNS records (dig axfr @<TARGET> htb.local is worth a try), and check SMB shares (smbclient -L //<TARGET> -N), even though Forest does not gift us anything from those vectors.

Foothold — RPC null-bind user enumeration

The role of the RPC null bind in our chain is not just “fun trivia” — it is a mandatory precondition for the next step. AS-REP roasting requires that we know an account name. Without an authenticated foothold, we cannot just grep the directory for accounts whose userAccountControl includes DONT_REQ_PREAUTH; the LDAP filter that surfaces them needs at least a basic user. So the workflow is:

  1. Pull the user list anonymously via RPC.
  2. Save it to a file (users.txt) with one sAMAccountName per line.
  3. Hand that list to a tool that asks the KDC for an AS-REP for each name and silently accepts the ones that come back.

Operationally that looks like:

rpcclient -U "" -N <TARGET> -c "enumdomusers" \
  | awk -F'[][]' '{print $2}' | tee users.txt

We also try lsaenumsid and lookupsids to walk RIDs if we hit a stripped-down domain, but on Forest enumdomusers is enough. Other tools that achieve the same enumeration include enum4linux-ng, crackmapexec smb <TARGET> --users -u "" -p "", and ldapsearch -x -H ldap://<TARGET> (anonymous LDAP also works on this box). Cross-checking between two tools is healthy because each occasionally misses accounts, particularly Exchange mailbox proxies.

There is no exploitation yet — we are just listing names. The point is that the box has decided to trust unauthenticated callers with the population of every user object in the directory. Everything that follows depends on that decision.

Worth noting: the Exchange-related SIDs (HealthMailbox*, SM_*) are an immediate hint that on-premises Exchange is installed. That hint matters two ways. First, Exchange installs many service mailboxes whose names are present whether or not the install is currently functional, so the recon list is trustworthy. Second, and more importantly, Exchange installs a set of privileged AD groups — Exchange Trusted Subsystem, Exchange Windows Permissions, Organization Management, etc. — and historically grants them broad rights on the directory. Spotting Exchange in the recon stage tells us to look hard at those groups when we get inside.

User flag — AS-REP roast svc-alfresco

Kerberos pre-authentication is the design feature that prevents trivial offline cracking of an AD password. When a client requests a Ticket Granting Ticket (AS-REQ), it normally sends a timestamp encrypted with the user’s long-term key derived from the password. The KDC decrypts that timestamp, verifies it is recent, and only then returns an AS-REP. Because the KDC needs the user’s password (or its derivation) to validate the request, and because the encrypted timestamp does not leak the password material, an external attacker cannot get a crackable artifact by just naming a target user.

That guarantee evaporates if the account has the UF_DONT_REQUIRE_PREAUTH flag set on userAccountControl. With pre-auth disabled, the KDC will happily return an AS-REP to anyone who asks. The interesting part of that response — the encrypted Ticket Granting Service session key blob — is encrypted with the user’s password-derived key. We can take it offline and brute force the password without ever talking to the DC again. This is the AS-REP Roasting attack, formalized by Tim Medin and popularized in tools like Rubeus and Impacket.

Impacket ships GetNPUsers.py for exactly this. From our attacker box:

impacket-GetNPUsers htb.local/ -no-pass -usersfile users.txt -dc-ip <DC> -format hashcat -outputfile asrep.hashes

The -no-pass tells the tool to send an AS-REQ without supplying credentials, -usersfile is the list we built from RPC, and -format hashcat writes hashes in the form $krb5asrep$23$user@REALM:... that hashcat parses directly. On Forest, only one user comes back: svc-alfresco. Other domain accounts return KDC_ERR_PREAUTH_REQUIRED, which is the expected, secure response.

Cracking the hash is straightforward:

hashcat -m 18200 asrep.hashes /usr/share/wordlists/rockyou.txt

Mode 18200 is the AS-REP type (Kerberos 5 AS-REP etype 23, RC4-HMAC). With rockyou the password falls in seconds — public writeups all confirm the recovered value is <password> (a short, English-looking word that any wordlist contains), driving home that AS-REP roasting is dangerous specifically when paired with weak service account passwords. John the Ripper has equivalent support via the krb5asrep format if you prefer that toolchain. The salient point is that the cracking is done entirely offline — the DC has no idea we ever asked, and there is no rate limit, no lockout policy, and no log of failed authentications because there were no authentications. That is the whole reason pre-authentication exists in the first place.

Practitioners often wrap the AS-REP roast workflow into a single one-liner that combines RPC enumeration, file generation, and the GetNPUsers run; the value of doing it in three explicit steps as a learner is that you can inspect each artifact (the user list, the AS-REP hashes file, the cracked password) and understand which step produced which output. Once the mental model is solid, automation is trivial.

With credentials in hand we look at how to log in. Port 5985 is open, and svc-alfresco is, per BloodHound (run later) and net group membership inspection, transitively a member of Remote Management Users. That makes WinRM a natural entry. Using evil-winrm:

evil-winrm -i <TARGET> -u svc-alfresco -p '<password>'

The shell drops in the user’s profile, where Desktop\user.txt holds the user flag. The exact flag value is omitted by policy.

Before moving on, it is good hygiene to do a quick “what am I” pass: whoami /all (groups, SIDs, privileges), net user svc-alfresco /domain (cross-checks against the directory’s view), and Get-ADUser svc-alfresco -Properties * if RSAT modules are present. We will see that svc-alfresco is not in Domain Admins, not in Enterprise Admins, but is in Account Operators (transitively via Service AccountsPrivileged IT Accounts) and in Remote Management Users. Those two memberships are the ones the chain depends on.

BloodHound — finding the path

Forest is an excellent tutorial for BloodHound because the privilege escalation chain is non-obvious if you only look at one group at a time, and obvious if you ask for a graph. The collection step is the now-standard SharpHound run. Either upload the .exe/.ps1 and run from the WinRM shell, or use the Python collector against the DC with svc-alfresco credentials:

bloodhound-python -u svc-alfresco -p '<password>' -d htb.local -ns <DC> -c All

We import the resulting .json files into BloodHound (legacy or Community Edition) and mark [email protected] as Owned. Then run the prebuilt query “Shortest Paths from Owned Principals” or, equivalently, “Find Shortest Paths to Domain Admins” with svc-alfresco as the source. The graph that comes back is the Forest narrative in pictures.

Walking the path edge by edge:

Two of those edges deserve a moment of attention. Account Operators is a built-in group documented as “less than full Domain Admin”; in practice it can manage user, computer, and group objects across the domain — a much larger blast radius than the name suggests. And Exchange Windows Permissions exists because the Exchange installer needs to manage mail-enabled object attributes; historically the installer also gave the group WriteDACL on the domain root, which is the primitive Forest weaponizes. Microsoft has shipped guidance to remove this ACE (the “Exchange split permissions model”), but legacy installs and many production environments still carry it. CVE-2018-8581 is the most famous public exploitation of the same surface.

Right-clicking the WriteDACL edge in BloodHound surfaces the canonical abuse text and pre-writes a PowerView one-liner that uses Add-DomainObjectAcl -Rights DCSync. That is exactly what we run next.

It is worth pausing to articulate what BloodHound has actually told us, because the visualization can feel like magic. Each edge is the result of an LDAP query SharpHound performed during collection. MemberOf edges come from the member attribute on group objects. GenericAll, WriteDACL, and the various extended-rights edges come from parsing the security descriptor (nTSecurityDescriptor) on each object’s ACL and translating each ACE into the corresponding edge type. So when BloodHound says “Exchange Windows Permissions has WriteDACL on the domain object,” what it means concretely is “there is an ACE on DC=htb,DC=local’s nTSecurityDescriptor whose Trustee is Exchange Windows Permissions and whose access mask includes WRITE_DAC.” Knowing this makes the abuse step less of a leap of faith and more an obvious consequence of the underlying directory ACL data.

Privilege escalation — Exchange Windows Permissions WriteDACL → DCSync

The privesc has two atomic steps even though we frequently chain them in a single PowerShell line.

First, we put svc-alfresco directly into Exchange Windows Permissions. Because svc-alfresco is in Account Operators (transitively) and Account Operators has GenericAll over the Exchange Windows Permissions group, we can add ourselves to the group:

Add-DomainGroupMember -Identity 'Exchange Windows Permissions' -Members svc-alfresco

Group membership in AD is realized through the member attribute on the group object; GenericAll on the object includes the right to write member. After this command, svc-alfresco inherits the group’s WriteDACL on the domain object.

Second, we use that WriteDACL to add an explicit ACE on the domain that grants svc-alfresco the two extended rights that constitute DCSync:

PowerView’s Add-DomainObjectAcl with -Rights DCSync writes both ACEs in one call. Even simpler from the attacker host, Impacket’s dacledit.py does the same thing without uploading PowerView to the victim:

impacket-dacledit -action write -rights DCSync \
    -principal svc-alfresco -target-dn 'DC=htb,DC=local' \
    'htb.local/svc-alfresco:<password>' -dc-ip <DC>

The first run will fail with INSUFF_ACCESS_RIGHTS until the Exchange Windows Permissions membership has actually been added (the net group command above commits to AD, but Kerberos tickets cached by dacledit happen via a fresh bind so this is normally fine — just verify the Get-ADGroupMember listing actually shows svc-alfresco before retrying). On success it prints [*] DACL modified successfully! and writes a backup of the prior DACL to dacledit-<timestamp>.bak in the cwd.

PowerView path (alternative, runs from the WinRM session):

$user = 'htb\svc-alfresco'
$pwd  = ConvertTo-SecureString '<password>' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($user,$pwd)

Add-DomainObjectAcl -Credential $cred `
  -PrincipalIdentity svc-alfresco `
  -TargetIdentity 'HTB.LOCAL\Domain Admins' `
  -Rights DCSync

Public writeups frequently target the domain head ('DC=htb,DC=local') instead of Domain Admins; both work because the rights propagate from the naming context root. The credential bundling step is necessary because group membership changes affect the Kerberos ticket cache, and we want PowerView to use a freshly minted token that reflects our new group.

With the ACE in place we drop back to the attacker host and run Impacket’s secretsdump.py, which speaks the MS-DRSR replication protocol to a DC and dumps Kerberos and SAM secrets:

impacket-secretsdump -just-dc htb.local/svc-alfresco:'<password>'@<DC>

The output includes NT hashes for every domain account, the krbtgt hash (gold ticket material), and the local SAM. We pluck the Administrator entry — typically Administrator:500:aad3b435b51404eeaad3b435b51404ee:<NT-hash>::: — and pass-the-hash to a privileged service:

impacket-psexec -hashes :<NT-hash> htb.local/Administrator@<TARGET>

evil-winrm -i <TARGET> -u Administrator -H <NT-hash> is an equally valid path. From the resulting SYSTEM-equivalent shell, \Users\Administrator\Desktop\root.txt holds the root flag.

A final operational note that catches first-timers: a PowerShell scheduled task on Forest periodically restores the original group memberships and ACLs (revert.ps1-style cleanup). If you take a long break between adding the ACE and running secretsdump, you will sometimes find your DCSync rights gone. Do the chain in one go. If the box is sluggish or someone else’s session resets state, a clean Get-NetGroupMember 'Exchange Windows Permissions' confirms whether svc-alfresco is still a member; if not, just rerun the Add-DomainGroupMember plus Add-DomainObjectAcl pair and proceed straight to secretsdump.

It is also worth thinking about why DCSync is so much more powerful than “just” reading the Administrator hash. Because the same secretsdump call exfiltrates krbtgt, an attacker can mint Golden Tickets for any user in the domain, with arbitrary group memberships, valid for years. That means recovery from a DCSync compromise is not just resetting Administrator’s password; it is rotating krbtgt (twice, with a delay between, to flush both KDC keys) and assuming any service in the forest could have been impersonated. Forest is a teaching example of why DCSync is treated as a domain-compromise event in incident response.

Why each step worked

Anonymous SAMR enumeration worked because the DC was configured to honor the legacy “Pre-Windows 2000 Compatible Access” mapping for anonymous callers, an option Microsoft explicitly warns against in modern hardening guides. The fix is to remove Anonymous Logon from that group and to enforce the RestrictAnonymous and RestrictAnonymousSAM policies; we never get a user list, we never have an account name to AS-REP roast, and the chain dies at step one. The same control closes anonymous LDAP and several null-session SMB primitives, so it is high-leverage hardening.

AS-REP roasting worked because svc-alfresco had DONT_REQ_PREAUTH set. Pre-authentication is the only Kerberos protection against an offline-crackable AS-REP, and disabling it is almost always a workaround for legacy clients that no longer exist. Combined with a weak password drawn from a common wordlist, the offline cracking step took seconds. Either fix — re-enabling pre-auth, or rotating the account to a strong random password — would have killed this step.

The privilege escalation worked because of an Exchange installer artifact that has been the subject of major research since at least 2017. Exchange registers the Exchange Windows Permissions group and historically grants it WriteDACL on the domain root so the Exchange Trusted Subsystem can manage mail-enabled object attributes. With that ACE in place, anyone who lands inside Exchange Windows Permissions — or anyone with GenericAll over the group, like Account Operators here — can write a new ACE on the domain that grants the two replication rights. The two rights together constitute DCSync, and DCSync is functionally equivalent to compromising the domain because it pulls every credential in the directory, including krbtgt.

DCSync itself worked because Microsoft’s own replication protocol (MS-DRSR) is the legitimate means by which DCs synchronize their copies of the directory. Once we hold both replication rights, our caller is indistinguishable from a peer DC and the source DC streams us the secrets. The protocol is by design; the misconfiguration was that a non-DC principal was granted the rights. The two extended-rights GUIDs are well known and appear in nTSecurityDescriptor ACEs in raw GUID form; tools like Get-ObjectAcl (PowerView) and dsacls translate them back to friendly names. Auditing for non-DC principals that hold either right on the domain naming context is the exact query you want for catching this misconfiguration during a routine review.

It is also useful to recognize that the privesc step did not exploit any code-execution bug. There is no CVE on Forest in the conventional sense; everything we did used legitimate, documented Windows administration interfaces. That is the most important characterization of modern AD attacks: the issue is not memory corruption in lsass.exe, it is the cumulative effect of years of permission grants no one ever audited.

Counterfactuals

Several small, plausible defensive choices would each have shut down Forest:

Any one of these would have broken the chain. The lesson is not that Forest had a single critical bug, but that AD security is a system property and the attacker’s job is to find one weak link in the system.

Key Takeaways

Forest is the canonical AS-REP roast plus BloodHound plus DCSync chain. If you can explain why each step works, you have the basic vocabulary to read most other AD writeups. Sister boxes like Sauna, Active, Resolute, and Mantis revisit subsets of the same primitives with different twists; once Forest clicks, those become exercises in pattern recognition rather than fresh research.

A good drill is to explain the chain to a non-Windows person in plain English. The reduced version: “the server lets us guess at user names; one of those users has a setting that lets us steal a crackable password hash; once we are inside, the way Exchange was installed gives us a shortcut to the root account; the shortcut is built out of three ordinary administration features stacked together.” Most real-world AD compromises follow that same shape — a series of mundane features composed into something none of them was meant to do alone.

Anonymous enumeration is the cheapest reconnaissance there is, and the cheapest defense is to disable it. The asymmetry — one PowerShell line on the defensive side, hours of attacker work avoided — is one of the most lopsided trades in AD security.

Pre-auth flags are a recon-day-1 check on every internal engagement. The query is cheap, the impact when you find a hit is huge, and many environments still carry legacy accounts with the flag set. Likewise, anonymous RPC and LDAP are first-call tests that are too often skipped. Adding GetNPUsers.py and GetUserSPNs.py to your standard kickoff checklist costs nothing and pays out frequently, especially in mature environments where every other low-hanging fruit has long been picked.

A good way to internalize the BloodHound lesson is to build the Forest chain by hand once, in your own lab. Nest a low-privilege user inside Account Operators, attach the Exchange installer’s default ACEs to Exchange Windows Permissions, and watch BloodHound discover the path on its own. The “I built this trap and now I’m walking through it” exercise burns the edge types into memory in a way that reading a writeup never quite does.

Exchange’s default ACL footprint deserves a category of its own. The Exchange Windows Permissions group with WriteDACL on the domain root is the textbook example of an installer that grants more than it needs. The dirkjanm and SpecterOps research linked above is required reading; Forest is a fingertip-easy lab demonstration of a misconfiguration that has shipped to production countless times. The same pattern shows up across many enterprise products that perform installer-time AD modifications: SCCM, certificate authorities, third-party identity tools. Whenever an enterprise application asks for “schema admin to install,” assume there are residual ACLs to audit afterwards.

BloodHound transforms tedious manual ACL spelunking into a single graph query. Learn the canonical edges (MemberOf, GenericAll, WriteDACL, WriteOwner, ForceChangePassword, AddMember, the various AllExtendedRights, and DCSync). On Forest the path is six edges long; learning to recognize each one in isolation is what generalizes. Make a habit of reading the “Help” tab on each edge in the BloodHound UI — the abuse text is short, accurate, and contains the exact PowerView and BloodyAD command snippets to weaponize the edge. Treat that as documentation worth memorizing rather than a crutch you only consult under pressure.

Finally, treat DCSync as a “win condition” indicator on the defensive side. The pattern of a non-DC principal exercising DS-Replication-Get-Changes is high-fidelity malicious activity and is one of the most useful Tier 0 detections you can build.

A meta-takeaway worth internalizing: each step in Forest is independently small. RPC anonymous bind is a one-line tool invocation. AS-REP roasting is a two-line tool chain. Adding a group member and an ACE is a two-line PowerShell snippet. DCSync is one Impacket call. The reason the chain is dangerous in a real environment is that small individual misconfigurations compose into total compromise. Defensive review must therefore be holistic: it is not enough to ask “is this account hardened?” — the question is “given this account, what graph paths does it open?” That is the discipline BloodHound was built to support, and Forest is a living illustration of why graph-based AD review is now standard practice for both red and blue teams.

References

← all htb machines hackthebox.com ↗