City Council

Challenge Lab (Medium) - by 2ubZ3r0

The following post by 0xb0b is licensed under CC BY 4.0arrow-up-right


Scenario

Objective / Scope

A local municipality recently survived a devastating ransomware campaign. While their internal IT team believes the infection has been purged and the holes plugged, the Board of Supervisors isn't taking any chances. They’ve brought in Hack Smarter to provide a "second pair of eyes."

Your mission is to perform a comprehensive penetration test of the internal infrastructure. Reaching Domain Admin isn't the endgame; treat this like a real engagement. See how many vulnerabilities you're able to identify.

Initial Access

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

Summary

chevron-rightSummaryhashtag

In City Council we assess a municipality's internal Active Directory environment following a recent ransomware incident. With only VPN access and no credentials, we begin by enumerating the exposed services on the domain controller. The host runs a full AD stack (DNS, Kerberos, LDAP/LDAPS, SMB, WinRM, RDP, IIS), confirming we are operating directly against city.local.

Web enumeration of the IIS portal reveals staff names and email patterns, which we leverage to generate username candidates. Kerberos username enumeration does not yield results, so we pivot to the downloadable Linux client application referenced on the website. Traffic analysis of the binary during execution exposes plaintext LDAP bind credentials for the service account svc_services_portal. Using these credentials, we authenticate to the domain and enumerate Active Directory with BloodHound.

Kerberoasting reveals a crackable TGS for clerk.john, granting us READ/WRITE access to the Uploads share. An internal email hints that NTLM authentication is used without prompting for passwords. We weaponize this by planting NTLM coercion files (created via ntlm_theft) on the writable share and capture the NTLMv2 hash of jon.peters, which we crack offline. BloodHound shows jon.peters has GenericWrite over multiple users, enabling Targeted Kerberoasting. We obtain and crack service tickets for nina.soto and maria.clerk.

Access as nina.soto grants read access to the Backups share, where we discover WIM user profile backups. Extracting them reveals emails and DPAPI-protected Credential Manager blobs. Using Impacket's dpapi.py with clerk.john's credentials, we decrypt stored credentials and recover the password for emma.hayes from Helpdesk.

BloodHound analysis shows emma.hayes has WriteDacl and GenericWrite over key OUs and users. We grant ourselves GenericAll over the CityOps OU, reset and enable the sam.brooks account a Remote Management Users member, and gain a WinRM foothold. From there, we pivot to the quarantined web_admin account by moving it into the CityOps OU (leveraging GenericWrite over Quarantine and GenericAll over CityOps), reset its password, and execute a reverse shell via RunasCs.

As web_admin, we upload an ASPX web shell to IIS and gain execution as iis apppool\defaultapppool. The account holds SeImpersonatePrivilege, allowing us toNT AUTHORITY\SYSTEM a system shell by compiling and executing an EfsPotato exploit. This yields completing full domain controller compromise. We retrieve the final flag from the Administrator profile.

Recon

We use rustscan -b 500 -a 10.1.90.188 -- -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 machine is actually a domain controller with exposed services including DNS 53, Kerberos 88/464, an IIS /10.0 web server on port 80, multiple MSRPC endpoints 135, 593, 49664+, SMB 139/445, LDAP and LDAPS 389/636/3268/3269 tied to Active Directory, RDP 3389, WinRM on 5985, and .NET Remoting 9389. This indicates a fully integrated Windows AD environment where LDAP/LDAPS and Kerberos provide authentication, SMB and RPC enable remote management, and RDP/WinRM serve as remote access points.

WEB

For now, we'll continue and take a look at the website hosted via the IIS.

The site promotes a digital service portal to access various city government services such as permits, licenses, and service requests.

We browse through the website and discover a team of four people in the lower third. From this information, we can identify the first and last names as well as the email addresses of each individual team member.

We note down the names for possible later creation of a word list and username enumeration.

In the footer, we discover further links, including Documents & Forms.

In the footer, we discover further links, including Documents & Forms. Here we can download a Windows or Linux application to access various city government services.

Furthermore, we find instructions on how to use the applications, such as dependencies or entries that must be made in /etc/hosts. Here, we can already directly adopt the /etc/hosts entry. The entries required correspond to the domain we discovered from the Nmap scan.

SMB

Before we dive in with username enumeration we try to authenticate as guest and anonymously against SMB, but without success - the account is disabled. Nevertheless we could generate the hosts file entry like the following:

If not already done, we add the following line to our /etc/host entry:

LDAP

Since we have a list of first and last names, no valid usernames yet and no access, we create a username word list from this using username anarchy and use this resulting list to enumerate possible usernames using kerbrute.

We create a username wordlist using Username Anarchy:

Next we pass the list to Kerbrute, but we are not able to identify any valid users:

Binaries

We continue with the binaries. I decided to take a closer look at the Linux binary. For this part, I had to switch to an amd64 machine. It turns out that the reversing is not so straightforward. And we could find out more by executing the binary, if necessary.

If not already done, we add the following line to our /etc/host entry:

We download the binary and make it executable.

Next, we run the binary.

Access as svc_services_portal

We can fill out and submit different forms for inquiries. For testing purposes, we use the Building Permit Request form, as it is already neatly pre-filled. We submit an application and see the Application Status Log building up.

We see that a connection to the City Council Directory Services is being established. The svc_services_portal account is used and authenticated by means of an LDAP bind request. It is possible that credentials are stored in the binary and are transferred during the connection establishment.

After a short time, we see that our request has been processed.

We start Wireshark, select tun0 in our case, and capture the traffic. We send another request.

After a short time, we see the bind request, which contains the user svc_services_portal and the corresponding password in a packet.

If we follow the TCP request, it is easier to read.

We test the credentials using NetExec on SMB. We successfully authenticated. In addition to the standard shares, we discover the Uploads and Backups share. However, as this user, we do not have access to these; we cannot access them for reading or writing. As this user, we cannot access the system via RDP or WinRM.

BloodHound Enumeration I

With the credentials, we can now also enumerate the AD using BloodHound.

We first look at our compromised user and initially do not find any special permissions or groups. This confirms why we were unable to access the machine via RDP or WinRM. The user is not in any of the required groups.

We are able to identify Administrator as one of the Domain Admins.

However, we can detect Kerberos-enabled users. This is the user clerk.john. With the valid credentials we could have try Kerberoasting also blindly.

We also cannot find any other special groups or permissions from clerk.john. Our hope now is that we can extract a TGS blob from clerk.john using kerberoasting and then crack it offline. With access as clerk.john, we could maybe then gain access to one of the shares and thus extend our privileges if they contained sensitive loot.

Access as clerk.john

We use NetExec for kerberoasting and obtain the TGS blob from clerk.john.

When asking the KDC (Key Distribution Center) for a Service Ticket (ST), the requesting user needs to send a valid TGT (Ticket Granting Ticket) and the service name (sname) of the service wanted. If the TGT is valid, and if the service exists, the KDC sends the ST to the requesting user.

Multiple formats are accepted for the sname field: servicePrincipalName (SPN), sAMAccountName (SAN), userPrincipalName (UPN), etc. (see Kerberos ticketsarrow-up-right "cname formats").

The ST is encrypted with the requested service account's NT hash. If an attacker has a valid TGT and knows a service (by its SAN or SPN), he can request a ST for this service and crack it offline later in an attempt to retrieve that service account's password.

In most situations, services accounts are machine accounts, which have very complex, long, and random passwords. But if a service account, with a human-defined password, has a SPN set, attackers can request a ST for this service and attempt to crack it offline. This is Kerberoasting.

Next, we try to crack the hash using the wordlist rockyou.txt and are able to retrieve the password of john.clerk.

We test the credentials using NetExec on SMB. We successfully authenticated. And we do have actually READ and WRITE permission to the Uploads share.

Access as jon.peters

We connect to the share using smbclient.py. Here we find various documents, we download the entire content via mget *.

An email is among the files from Emma Hayes and Jon Peters. Here, the IT help desk provides instructions on how to use and integrate a share, the Uploads share, to which we also have READ and WRITE access. It is noted that NTLM is used for authentication and that a password entry is not necessary. This email seems to be a subtle hint that we, as attackers, should use the write permission to steal NTLM hashes. This is possible if we place a file on the share that references a remote resource under our control. When a user or service accesses this file, the system will automatically attempt to authenticate to the remote SMB share using NTLM, causing the NTLM challenge-response to be sent to our listener. By capturing this authentication attempt with a tool like Responder, we can obtain the NTLM hash.

The Greenwolf ntlm_theft tool may help us out here. With that we are able to create up to 21 files that can be used for NTLM hash theft.

Alternatively, the hashgrab tool is also very reliable for generating such files, which connect to our server when called up for rendering:

We generate the files...

... spin up responder...

... and connect to the share again using smbclient.py. We put the resulting .lnk file from ntlm_theft.py into the share and wait some time.

After a short duration we receive the NTLMv2-SSP of jon.peters.

We try to crack the hash and are successful.

We test the credentials using NetExec on SMB. We successfully authenticated. However, we only have access to the Uploads share, which we have already successfully enumerated and exploited. So we need to go back to the drawing board. Let's look at our BloodHound data to see what the user can do.

BloodHound Enumeration I

We see that the user jon.peters has GenericWrite permissions over three users. Those are paul.roberts, maria.clerk and nina.soto. his allows either a TargetedKerberoast or Shadow Credentials Atttack.

Access as nina.soto & maria.clark

We will first attempt a TargetedKerberoast attack.

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 Operatorarrow-up-right 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 Kerberoastingarrow-up-right. This technique is called Targeted Kerberoasting.

We receive the TGS blobs from all four users:

And can crack the blobs from nina.soto and maria.clerk.

We test the credentials again using NetExec via SMB. We are able to authenticate successfully and see at nina.soto that we now have read access to the share Backups.

Acces as emma.hayes

We connect to the share using smbclient.py and discover two subfolders: Documents Backup and UserProfileBackups. We download all contents of both files using mget *. The UserProfileBackups share contains two WIM files corresponding to the users clerk.john and sam.brooks.

Windws Imaging Format files are disk image files used by Windows to store a complete Windows installation or system deployment image. They allow multiple system images to be stored in one file.

circle-info

The WIM file from clerk.john is somewhat larger and therefore takes a little longer to download successfully. Both files are required.

To extract the contents, we use wimextract from the wimtools suite, which can be easily installed using apt install wimtools, for example.

We create a folder for the contents of the image...

... and extract them into this folder as follows.

We look at the folder structure using tree...

and spot a message_sam.eml email file in the users Desktop.

From the email, we can gather that the web_admin account was moved to the Quarantine OU due to security concerns related to system activity. This was done because the web server allows ASP.NET .aspx uploads, which could potentially be abused with that account to escalate privileges or perform unauthorized actions.

That's interesting, we'll keep that information in mind. We may need the web_admin account later to exploit this and place an ASPX web/reverse shell to extend our privileges.

We continue and extract the contents from clerk.john_ProfileBackup_0729.wim. We create a folder and continue in the same way as for sam_brooks.

Here, too, we look at the folder structure and its contents using tree.

And we also found an email here on the desktop

From the mail we can extract that Emma Hayes informs John that he can temporarily use her account while she is on vacation to handle urgent IT tasks, and the credentials will be shared via an approved channel. She instructs him to store them in Windows Credential Manager protected by DPAPI.

We identify the Credential Manager blob and the corresponding DPAPI masterkey in the user's profile directories of the extracted image.

Credential Manager blob:

Master Key file:

We extract the masterkey using dpapi.py masterkey with John's SID and password.

Next, we use that key with dpapi.py credential (a tool by impacket) to decrypt the stored credential file and extract the password of emma.hayes.

We test the credentials using NetExec on SMB. We successfully authenticated.

circle-info

We could also retrieve the password of emma.hayes from the command line powershell history of clerk.john from

AppData/Roaming/Microsoft/Windows/PowerShell/PSReadLineConsoleHost_history.txt

BloodHound Enumeration III

We see that emma.hayes is in the helpdesk group.

Furthermore, the user has GenericWrite permissions over the quarantine OU and WriteDacl permissions over the citypos OU. The user also has GenericWrite permissions over the web_admin user. (Remember, we may need this to compromise the user in the context of the running web application).

In addition to these, this user also has WriteDacl permissions for rita.cho, alex.king, and sam.brooks.

  • Quarantine OU (GenericWrite): Allows us to modify attributes of the OU, such as adding or modifying objects inside it (e.g., moving users or changing settings).

  • citypos OU (WriteDacl): Allows us to change the OU's permissions (ACL), potentially granting ourselves or others full control over objects within that OU.

  • web_admin user (GenericWrite): Allows us to modify the user’s attributes, such as changing the logon script, or adding SPNs for Kerberos abuse.

  • rita.cho, alex.king, sam.brooks (WriteDacl): Allows us to modify their ACLs, enabling us to grant ourselves rights like FullControl or ResetPassword over those accounts.

We see that rita.cho, alex.king, and sam.brooks are part of OU cityops.

This means that if we granted ourselves the GenericAll permission over the cityops OU via WriteDacl, we would also have access to the users inside the OU, granting us GenericAll over them. This would allow us to reset their passwords and compromise those accounts..

As in the previously discovered email from images, we can confirm that web_admin is part of quarantine.

We also discover that sam.brooks is part of the remote management users group. We can therefore establish our initial foothold through that account.

So, we will primarily link our potential attack vectors to obtain an interactive session through sam.brooks.

We will use WriteDacl to grant GenericAll permissions over the cityops OU and then change sam.brooks' password.

Shell as sam.brooks

We grant GenericAll rights over the CityOps OU, giving us full control over the object and its members, which allows actions such as resetting the passwords of users within that OU.

Next, we reset the password of the user sam.brooks, which is possible because our GenericAll rights over the CityOps OU give us control over its member accounts.

We test access using NetExec, but the account is disabled, something I have overlooked in the BloodHound data; however, since we have GenericAll, we can simply enable the account.

We then enable the sam.brooks account by removing the ACCOUNTDISABLE flag from the UserAccountControl attribute using bloodyAD.

We test again with NetExec and can now successfully authenticate with the enabled account.

Next we establish a remote PowerShell session using WinRM with the compromised and find the users flag in the Desktop folder of sam.brooks.

Shell as web_admin

In our session as sam.brooks, we try to write to the /inetpub/wwwroot directory but are unsuccessful. We lack the permissions; we cannot place an aspx shell here, for now. So, as expected, we will need the web_admin account.

The hashes obtained through targeted Kerberoasting were not crackable, and the Shadow Credentials attack was also unsuccessful.

Both ways are depicted here in a similar fashion:

Instead, we try to leverage our privileges by moving the web_admin account into the CityOps OU, where we previously granted ourselves GenericAll, allowing us to reset the web_admin password and take control of the account.

We prepare an LDIF file to move the web_admin user object from the Quarantine OU to the CityOps OU.

Next, we execute the LDAP modification to move the web_admin account from the Quarantine OU to the CityOps OU, placing it in a location where we have GenericAll control.

After moving the account to the CityOps OU, we leverage our GenericAll privileges over that OU to reset the password of the web_admin user, allowing us to take control of the account.

Next we verify the new credentials using NetExec, and we can successfully authenticate as web_admin.

Unfortunately, the user is not part of the Windows Remote Management Users group or the Remote Desktop Users group. Therefore, we cannot conveniently establish an interactive session.

What we can do, however, is start a reverse shell in the context of the user web_admin using RunasCs.exe.

For a more interactive shell, we use our go reverse shell, which has remained undetected so far.

We compile the reverse shell as follows on our Exegol instance:

Next, we run a listener to catch the reverse shell. For this purpose we use Penelope:

The reverse shell binary prepares and RunasCs.exe in the current working folder, we reconnect (if interrupted) using evil-winrm to the target as emma.hayes.

We will place the binaries in a global folder accessible to all users. In this case, C:\Temp.

We upload the binaries.

Next, we execute our reverse shell binary 0xb0b.exe in the context of the web_admin user:

We receive a connection...

... we are web_admin.

Shell as iis apppool\defaultapppool

For our reverse shell, we use the following ASPX file:

In this file, we adjust the port and IP address. For clarity's sake, we choose a different port than before, even though Penelope supports and can capture multiple reverse shell on the same port.

Despite using Penelope, the upload function fails. We start a Python web server to place the reverse shell.

Next, we bring the shell to the machine using cURL alias.

Then we create a new listener using Penelope. Here using port 4446.

Next, we call the reverse shell as follows:

After a little longer, we get a connection. We are iis apppool\defaultapppool.

Shell as NT AUTHORITY SYSTEM

We see that we, as this user, have the SeImpersonatePivilige permission active.

Since the SeImpersonatePrivilege is enabled we can make use of one of the infamous potato exploits. One of my favorite Potato exploits is the EfsPotato exploit. We can compile this on the machine if the C# compiler is available, and it should also go undetected.

We check for a compiler at C:\Windows\Microsoft.Net\Framework\ and see a v4.0 version is persent. Nice.

The csc.exe to compile the exploit is present.

Next, we download the source file from our attacker machine, compile with the compiler found at C:\Windows\Microsoft.Net\Framework\v4... and execute it with the whoami command.

We download the file:

Compile the potato exploit:

Run whoami with the exploit. We are NT authority\system.

Using the exploit we run our reverse shell binary again, Penelope can handle multiple connections.

We receive another session on our Penelope instance running on port 4445.

We can deattach our current session in this case with CTRL + D.

To interact with the new session we issue session 2.

We are NT AUTHORITY SYSTEM and find the final flag at C:\Users\Administrator\root.txt.

Recommendation

Don't miss out on the full-fledged pentest report that DKob has created for the scenario!

Last updated

Was this helpful?