Windows AD CTF Writeup

Got back into ctf’s as of late and had alot of fun with a fairly new windows box. This windows machine is heavily focused on enumeration and active directory exploitation. The most interesting part of the challenge is you basically root the machine without ever gaining a shell on the machine itself. All of the enumeration and attacks are performed remotely. This seems like it would be similar to gaining vpn access to a network and performing your attacks through the vpn. Rather than having an implant call back to your C2. The machine is exploited using a combination of end user negligence, IT errors and common AD constrained delegation misconfiguration.



Scanning the machine reveals port 80 is hosting an IIS server, navigating to the server doesnt show much. Its a basic static site but there are two links that point to a document directory being accesible and hosting pdf files.



The two documents dont contain anything useful. But running exiftool reveals the names of two users that can be used for password spraying down the line.

BruteForcing Pdf’s

With no other directories found using gobuster to brute force other directories, i wrote a script to brute force available pdfs files by looping over the date format. If the status code wasnt a 400 i would download the pdf. Extract the author metadata (to add to our user list) and extract the text from the pdf to check if the document contained anything useful.

The script source is below

import requests
import os
from PyPDF3 import PdfFileReader

class BruteForceDirs():
    def __init__(self,ip,port):
        self.ip = ip
        self.port = port = f"http://{self.ip}:{self.port}/documents/"
        self.good_urls = []

    def spam(self,year):
        months = ["01","02","03","04","05","06","07","08","09","10","11","12"]
        days = ["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"]
        target_url = + year + "-" + "$$" + "-" + "!!" + "-upload.pdf"
        for month in months:
            month_url = target_url.replace("$$",month)
            for day in days:
                final_url = month_url.replace("!!",day)
                response = requests.head(final_url)
                if response.status_code != 404:

    def parse_pdf(self):
        print("Attempting to download and parse!")
        for url in self.good_urls:
            file_name = url.split("/documents/")[1]
            with requests.get(url,stream=True) as s:
                with open(file_name,"wb") as f:
                    for chunk in s.iter_content(chunk_size=512 * 1024):
            print(f"PARSING PDF -> {file_name}")
            with open(file_name,"rb") as f:
                reader = PdfFileReader(f)
                contents = reader.getPage(0).extractText().split("\n")
                [print(x) for x in contents]
                print(f"DONE! Deleting File! -> {file_name}")
            except Exception as e:
                print(f"Failed to delete file -> {file_name}")
    def print(self):
        for url in self.good_urls:

if __name__ == "__main__":
    bfd = BruteForceDirs("","80")
    years = ["2020","2021"]
    for year in years:

After piping the output using tee and skimming over it, i found two interesting documents. One contains the default password given to new users joining the company -> NewIntelligenceCorpUser9876 <- . And one seems to be an internal document stating that a script is running to check if web servers go down and that service accounts are being locked down. (Probably due to a recent breach)


Along with this new information i also dumped all the authors from each pdf (of which there were around 30). Into a user list that i can use to password spray with the default password.



Running crackmapexec and password spraying the network shows a user has not changed the default password. And we can access some smb shares on the target host.

cme smb -u ../users.txt -p NewIntelligenceCorpUser9876 -d intelligence.htb --continue-on-success --shares


I enumerated the shares with smbclient and it revealed read access to several folders. One folder called ‘IT’ seemed to be interesting and it contained a script called downdetector.ps1


The scripts contents is below

# Check web server status. Scheduled to run every 5min
Import-Module ActiveDirectory 
foreach($record in Get-ChildItem "AD:DC=intelligence.htb,CN=MicrosoftDNS,DC=DomainDnsZones,DC=intelligence,DC=htb" | Where-Object Name -like "web*")  {
try {
$request = Invoke-WebRequest -Uri "http://$($record.Name)" -UseDefaultCredentials
if(.StatusCode -ne 200) {
Send-MailMessage -From 'Ted Graves <Ted.Graves@intelligence.htb>' -To 'Ted Graves <Ted.Graves@intelligence.htb>' -Subject "Host: $($record.Name) is down"
} catch {}

This script was the one referenced in the pdf document. The script performs a get request with the default credentials flag. This indicates some authentication is being performed. And it checks AD for objects with the name that start with ‘web’ and performs the request on that host.


I proceeded to add a machine account to active directory. By default all users in AD have access to add machine accounts to the domain. Not being able to use powermad for this task i found a port of powermad to python which i ported to python3 then used to add a machine account with the name webAttack to the domain.


After i used the dntool created by dirkjan to add a dns entry to that machine account i just added to the domain. And pointed it to my machines ip address.


After starting responder and waiting for a couple minutes i recieved the ntlmv2 hash from the ted.graves user. As he was the account the script was running as. Using the rockyou wordlist and john i was able to crack the hash quickly.



At this point i have another user (ted.graves) password (Mr.Teddy) that seems to be in an IT/Dev role. I decided to run bloodhound and enumerate the domain further.


The findings were substantial and point out a clear path to domain admin. The Ted.graves user that i control is in the IT Support AD Group. This group has the ability to retreive the password for the Group Managed Service Account SVC_INT. This service account has constrained delegation permissions on the target domain controller.


A quick google search pointed me to this python script that can be used to extract the ntlm hash from the service account. After running the script as ted.graves we get the expected hash for the service account.


Constrained Delegation

Constrained Delegation allows us to request a kerberos ticket for the WWW service on the domain controller as any user. But unfortunately a security researcher Alberto Solino found that the service name portion of the ticket (sname) is not actually a protected part of the ticket. So we can change it to any service we want (like cifs).

I decided to leverage impackets script and request a ticket as the Administrator account on the domain. And then use impackets script convert it to a CIFS ticket.


This allowed me to perform a hash dump on the domain controller using


Using the same ticket, i used to gain a reverse shell and read the flag.


Key Takeaways

  • Sometimes a shell isnt needed on network to compromise accounts.
  • Misconfigured active directory environments lead to easy compromise. Specifically Improper Use of GMSA and Misconfigured Constrained Delegation.
  • Responder is extremely powerful
  • Active Directory has a huge attack surface. And a basic user accounts have more power than most people think.

Further Reading