Skip to main content
CTOS Corporation delivers cutting-edge managed services, cloud solutions, and cybersecurity expertise to clients of all sizes. You have been hired to perform their annual penetration test against 3 high-value targets in the Active Directory environment. Your task is to identify all vulnerabilties and (if possible) elevate your privileges to Domain Admin. You have been provided VPN access to their internal network, but no other information. Image

Enumeration

Normally we would start off with an nmap scan, but there is something else we need to address and organize before starting. On the HSM launch page, we have three machines Image To keep track of our information, we will be using this table:
IPHostnamePorts
10.1.22.247DC01
10.1.149.204IT-WS01
10.1.13.133WEB-01
Let’s start off my obtaining open ports for each host, ensuring that at the end we update our table to have a clean attack surface going into the attack phase of our engagement.

DC01 Nmap

Nothing out of the ordinary here, as these are the expected open domain controller ports alongside RDP and WinRM.

IT-WS01 Nmap

Looks like a normal domain joined windows workstation, featuring SMB, RDP, and WinRM.

WEB-01 Nmap

Web is as expected, just running HTTP and SSH on a linux machine. So our updated table looks like this:
IPHostnamePorts
10.1.22.247DC01Normal AD + RDP and WinRM
10.1.149.204IT-WS01SMB, RDP, WinRM
10.1.13.133WEB-01SSH + HTTP (80)
Let’s continue enumeration systematically, starting with DC01 checking for any guest / null authentication tricks, and then moving to the web server to hopefully find a way in.

DC01 Enumeration Continued

To probe for non-authenticated resources, we can use netexec. First, checking for null authentication, which is not providing a username or password and attempting to list shares:
Image We see a green checkmark, but clearly access to any shares is denied. Moving on to guest, we see that the account is disabled entirely. This limits our options quite severely, as there aren’t a TON of things we can do without proper authentication to the domain.
Image One last thing to check before moving on is null authentication via LDAP. If we can perform LDAP queries we may be able to garner additional information from object properties which can give us a foothold. Unfortunately, it doesn’t look like this is the case, as we don’t successfully bind:
Image

Web-01 Enumeration Continued

Without any handle on the DC01, we can move on to enumerate the linux based web server. First, running gobuster to ensure we don’t miss any directories or subdomains:
gobuster reveals many endpoints that we should explore! Moving on to the web page itself, we find that it is some kind of MSP or enterprise solution manager: Image Let’s explore each endpoint that gobuster found:
  • /about
    • On the about page there is a list of the leadership team. This could be important, as if we get a name schema (i.e. first.last, firstInitial.last, etc.) we can create a valid user list for our target. Image
  • /careers
    • Doesn’t reveal a ton, but there are some small tidbits about what potential technology CTOS CORP may use on the backend that we will eventually be targeting. Image
  • /contact
    • Nothing too interesting on contact other than the two email addresses and the ability for user input. I don’t think this is the way forward, so I will leave a stored XSS payload and see if it gets us anywhere Image
  • /news
    • Simply a news / blog page for the company. Similarly to careers, we can garner a small amount of information such as “streamlined onboarding system” and “expansion of cloud management services”. Both of these could be potential attack vectors. Image
  • /services
    • The CTOS services, which provide some generic consulting and MSP services. Image
  • /login (/portal)
    • This isn’t a page advertised on the main site, as we found it through fuzzing, but it looks like we can login to the dev portal so long as we have “CTOS developer credentials”.
Image Submitting a test request and intercepting with Caido, we find a couple of things. First, it appears as if the CTOS website sets it’s own cookie “ctos_session” alongside a normal JWT. Image This cookie doesn’t decode super neatly in b64, but we can get a glimpse of what it is doing: Image We can extract that we have a visitor_id, what pages we have visited, first login time, username, and if we’re authenticated or not. The normal JWT decodes to just a warning message telling us to log in to access the dev portal. Image Going back to our ctos_session, I want to touch on WHY the base64 decodes all wonky and weird. This is because it is a deserialized python pickle object. We know this because the prefix ‘gASV’ decodes to \x80\x04, which identifies Python’s pickle protocol 4. This object is interesting because if a server unpickles attacker-controlled data without appropriate protections, that can lead to arbitrary code execution. We can learn more about our cookie by decoding it properly with this script:
This gives us a lot cleaner of an idea of what the cookie is carrying:
Now, the big question here is if we can manage to manipulate any of the values in this object? Particularly, the ones that are interesting is naturally going to be username and authenticated. This is similar to a mass assignment attack we would traditionally see. We can start by testing whether the cookie is integrity-protected and whether changes to the fields survive a round trip. We can do this with this script:
Image Unfortunately, while our payload was preserved, it doesn’t look like changing our cookie did anything: Image

SQLi on the Dev Login Page

Running sqlmap against the login page, we discover that there is a boolean-based SQL injection on the username parameter!
Image We can confirm this by testing a payload manually:
Image And we’re in as the admin user! Image

Enumerating the Dev Portal

The first thing that sticks out to me is the red text asking to be clicked, the ‘Site Archive’. Clicking on this we get a backup.zip of the site! Image Analyzing it in the CLI, we get the full app.py! Image Unfortunately, the app is just a reflection of what we already see on the main page, the only difference is that it is being hosted locally on 5000 instead of on 80: Image While it doesn’t reveal any creds, the app DOES disclose more about that CTOS_Session we saw earlier. Looking at the code closer, we might be able to trigger RCE via insecure pickle deserialization. There are a few main points that we need to understand first though:
  1. We control the cookie
  2. In app.py, we see that our cookie is simply base64.b64decode(cookie) decoded followed by pickle.loads(data).
  3. Pickle can invoke callable objects during deserialization. It is not merely restoring fields on a session object.
  4. The after_request hook calls get_session() and then serializes the resulting object back into a cookie.
Looking up pickle deserialization, we find that using the __reduce__ function is our best route to get RCE. Image

Initial Foothold: Shell as phil on web01

To perform this RCE, we can write our cookie with a script like this using the above mentioned function:
Image
Note: you may need to try a few different python payloads until you get one that clicks. I would also stay away from Penelope, as my shells kept dropping because of the lack of PATH.
Inputting our cookie and refreshing the page… Image Success! We get a shell on web01 as phil. Image

Lateral Movement: phil → john

Running a quick ls as phil, we can find that we have access to the CTOS corp db. We can run a quick python server and get this back onto our kali machine for examination. Image
Note: To get rid of the annoying PATH errors, run the following commands:
Image Opening the DB up, we find that there is only one user, the admin user and their password. Image This isn’t entirely unhelpful, as we can use this password to spray to other users as needed. Speaking of other users, we have another user on the WEB01 box names john. Image Backup up a couple of directories, we find that in /opt/backup there is a root owned backup script that stages files from our home directory. The script is particularly looking for backup config and then is running /usr/bin/zip against it.
Image Looking at the backup log file, we find that we can actually read this file. The script is catting out our config file and appending it to the log file. Note that we can also WRITE into the source dir, which root is running zip against. Image There are a lot of juicy things we could go after, but to start I’m going to attempt to get an SSH key in the logs. To do this, we can create the expected backup_config file in phil’s directory to be a symlink to the target file. In this case, we want the target file to be root’s SSH key.
Image Strangely though, when we look into the logs, we see that there is no backup file to be found. Image While I’m not entirely sure why this is, we can assume that it may be a permission error, so shat if instead of going for root, we go for a different user? What about john’s SSH key? Following the same steps as before:
Image Nice! This one comes through as we originally anticipated after a few minutes! Image

Privilege Escalation: John → Root

We are now john on web-01! Image Immediately, we see that we are a part of the disk group. Image This is a big find because it means that read restricted files, such as /etc/shadow or /root/.ssh/id_rsa . First, we need to find the root partition with df -h Image From there, we can use debugfs to simply read out the root ssh key!
Image

Root on WEB01

Hooray! We’re root! But now what? Image Well we still have two other workstations to compromise, a windows joined workstation as well as a domain controller, so we’re not close to done quite yet. Something we didn’t know before based on our enumeration (but makes sense) is that this linux machine is actually domain joined as well. Listing out the kerberos realms we find that we have some configuration here. Image This config continues in the kerberos configuration file (/etc/krb5.conf) , where we find an internal host and KDC server at 172.16.11.140 Image We also have other kerberos files, as keytab and a conf directory: Image In the directory, there is another configuration which points to /var/lib/sss/pubconf/krb5.include.d , though I can’t say there is anything too interesting here. Image In the kerberos keytab, it is just giberish: Image This is because a keberos keytab file is a secure file that stores pairs of Kerberos principal names and their encrypted secret keys (derived from a password). We can kind of make out a username from our keytab file here, that being svc_web , but how can we use this file? We can use klist to look at the file closer: Image Seeing a deprecated encryption here is a good sign for us! This means we can actually take the keytab file from the linux machine and derive an NT hash from it! To do this we can either use impacket on our kali machine OR use the already installed ktutil on the compromised linux box. I will choose to simply use the already installed ktutil:
The output we see for the arcfour-hmac entry is the NT hash, because the RC4 key in Kerberos is identical to the MD4/NTLM hash of the password!

Lateral Movement: svc_web → IT-WS01

Testing this hash, we can authenticate to the domain controller! Image We also authenticate to IT-WS01. Listing out the shares we find that we have read access on IT_Onboarding Image Using netexecs spider_plus module we can pull down all the files from this share
Image We find a file named SEC-POL-2026.pdf. Opening it up, we find that we have a password policy for the domain! Image Remember, now that we are authenticated to the domain we can pull down information such as usernames!
Establishing a users list:
Image Now with this password convention, we can come up with a matching list for each of the users. We COULD create a script to do this, but to be honest, I have an inkling based on our previous enumeration that the target here is going to be Lisa Conrad. Image It makes sense that the Junior IT Support makes the mistake? Always “blame the intern” or something like that. Anyways, going with my theory we can create a list of potential passwords for Lisa and spray them! Image Success! We are able to authenticate as lisa onto the domain. Spraying our creds we find that we have winrm access as Lisa onto it-ws01! Image We can use evil-winrm to login: Image

Privilege Escalation: Administrator on IT-WS01

Now that we have a user-level shell on IT-WS01, we can’t do much outside of basic user functions. We need to attempt to escalate our privs, and to do this we can use an automated tool like privesccheck to check for windows vulns and misconfigurations. Transferring the file to the IT-WS01 machine through evil-winrm
Running the checks:
And we found something! Image There is a service names CTOSInventorySvc , which is running AS system for a file that we can over write (AllAccess)! To take advantage of this, we need to do a few things:
  1. Creating a malicious file on kali that will be ran on the target machine:
  1. Compiling the file on kali into an executable:
  1. Upload the file via evil-winrm and rename it to the filepath the service is expecting. In our case, it is CTOSInventorySvc.exe .
    1. Note: make sure to make a backup of the original exe!
Image And after a minute of waiting we see our backdoor user was added! Image We can confirm this with nxc! Image

Pivoting from IT-WS01 to DC01

With our newfound admin account, we can RDP in and start poking around as an administrator on the machine. First things first, I’m going to disable AV and UAC to make it a lot easier for us to dump secrets on the machine. The hope is that by dumping secrets we may find some domain joined accounts that left traces of their hash or plaintext creds we could use to pivot to the domain controller. Image Disabling UAC which will allow us to run secretsdump remotely:
Unfortunately, there is interesting here, meaning we have to dig deeper! Back to the filesystem, we find a keepass DB in the administrator’s documents! Image Copying it over to our local machine and trying to open it, we find it’s locked: Image Thankfully, we can crack the password on the DB by using John: Image This password opens the DB, and through it we can find the creds for the svc_infra_mgr! Image

Lateral Movement: svc_infra_mgr → it_ops_lead

Using these creds we can pivot to bloodhound and see this interaction:
Note: to pull down bloodhound info we can use these newfound creds OR l_conrad’s creds using nxc:
Image This means we can perform a targeted kerberoast OR a shadow creds attack. I will elect to use bloodyAD to perform a shadow creds attack, but it was throwing an error:
Image And today I learned that you could also perform this attack with certipy!
Image

Final Privilege Escalation: Compromising the Domain Policy

Now that we have the it_ops_lead account compromised, we can go back to bloodhound and see that they have the ability to add members to the policy automation group. Image From here, the policy automation grtoup has the ability to write policies over the default domain controllers policy. This means that we can grant ourselves any permissions over the default domain controllers policy object, and furthermore use this permission to create policies across the domain, including scripts / automations! Image So how do we do it?

Traditional Method — Manual Insertion of Startup Script

  1. Add ourselves to the policy automation group. We can do this with bloodyad
Image
  1. Give our user GenericAll over the GPO
Image
  1. Find out where the GPO is being stored:
Image
  1. Create a scheduled task script to be ran, in our case it will be startup.bat
  2. Establish a connection with the GPO via smbclient and put our startup script:
Image
  1. The DC doesn’t know that we made a change to the GPO, so we have to modify the versionNumber attribute of the GPO so the DC pulls the update. (Note: If we had a shell on the system we could also run gpupdate /force)
    Image
Once the GPO version is updated, the Domain Controllers will automatically process the policy and execute your script during the next Group Policy background refresh cycle (typically every 5 minutes for Domain Controllers)

Simplified Method — pygpoabuse

If you don’t want to go through all that hassle (sorry if you just went through all that hassle :D), we can use a tool that does it automatically for us! Bloodhound suggests using pygpoabuse.py Image The syntax is like so:
Image Our task gets successfully added, and after about 5 minutes, we can test to see if our nth user was added:
Image Success! Now with domain admin permissions on the domain controller we can dump secrets to obtain the krbtgt hash:
Image And that’s the lab! This one was quite the doozy, and I learned a ton, especially with the pickle deserialization foothold and the final privilege escalation through the domain controller policy, as I had never done either technique before!