ShadowGate2
Challenge Lab (Medium) - by 2ubZ3r0
The following post by 0xb0b is licensed under CC BY 4.0
Scenario
Objective
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
You have been provided with VPN access to their network, but no other information.
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.
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.
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.

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.

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:

We add the following entry to our /etc/hosts file.
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.

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.

We add the following to our /etc/hosts file:
Next, we visit the dev page and find a file upload portal that requires credentials.

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
Access as admin
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.

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

Access as mitch.r
We prepare the file using ntlm_theft.py.

We spin up Responder to catch the hash.

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

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

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

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.

But it does not contain any useful files.

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

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.

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.
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.

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

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.

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

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.

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

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

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

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.

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

We enumerate the database...

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

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.

We connect to our share...

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

We try to crack the hash and are successful.

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

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

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.

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.

Shell as oscar.m
We change the password of oscar.m.

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

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.
The GenericAll permission lets us clear it outright with bloodyAD's set object module:

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

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

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.

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.

We restore the account with the follwing command.

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

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.

Access as sam.h
We change the password of sam.h.

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

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.


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



Shell as Administrator via ESC3
If not already done, we'll add the Certificate Authority to our /etc/hosts file.
Next we follow the depicted ESC3 route from the certipy wiki:
Step 1: Obtain an Enrollment Agent certificate

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

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

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.

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:
Step 1: (If needed, as facilitated by Manage CA) Ensure capability to approve requests

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

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


Step 4: Approve the pending request.

Step 5: Retrieve the issued certificate

Step 6: Authenticate using the certificate

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.

Shortcut and Alternative Path
Shortcut
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:
Alternative Path (Shortcut)
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.
We craft the silver ticket locally. The FQDN part after the / has to match.
With the resulting ticket we can authenticate to MSSQL as Administrator.
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.
Last updated