SCCM Hierarchy Takeover via Entra Integration…Because of the Implication

Nov 19 2025
Share
By: Garrett Foster • 17 min read

TL;DR SCCM sites (prior to KB35360093) integrated with Entra ID can be abused to compromise the entire hierarchy.

Introduction

Despite several attempts to convince myself that “I’m done with SCCM”, here we are again. Last time, I wrote about abusing the Management Point’s role in the SCCM site database to recover and decrypt credentials from operating system deployment (OSD) policies. That discovery was made in part by the bizarre allergic reaction I have to writing documentation. But, this time, I was the victim of “nerd-sniping”. If you’re unfamiliar, here’s an example of what that looks like in action. It’s subtle, but effective.

Originally, I thought these attacks were passive, but now I know it’s 100% intentional because, the more of these blogs I write, the more I find myself writing a sentence somewhere that includes the  phrase “…Duane Michael and I were working on…”.

Anyways, while Duane Michael and I were working on Misconfiguration Manager updates for TROOPERS25, he wrote up the documentation for CRED-7, which involved the Administration Service REST API. To double-check our understanding, I began reversing the service binaries when I landed on this bit of code that points to an additional authentication endpoint for the API.

A Google search doesn’t return many results from Microsoft when searching for the AdminService_TokenAuth API endpoint, but there are several results from third-party blogs that point to it being related to Cloud Management Gateways or Co-management.

Entra Integration

A Cloud Management Gateway (CMG) is a site system role that integrates with Microsoft Entra ID to enable management of SCCM clients that aren’t connected to the local network physically or by VPN. You can think of it as an internet-facing Management Point proxy that clients can connect to from anywhere. Co-management operates differently as a solution that combines SCCM with Microsoft Intune to enable management of devices from both services. From my point of view, each is a way to handle remote device management; it’s just a matter of preference or need. 

While the use case of each setup may be different, both share the requirement to integrate with Entra ID. This is accomplished by setting up “Cloud Management” through the Azure Services wizard from the Configuration Manager console.

The setup process is straightforward. An administrator:

  1. Signs-in with an Entra global or cloud-application administrator account
  2. Configures a server app and client app
  3. Configures some discovery and synchronization settings

Something that stood out to me during this process was that the “App Properties” menu has an option to disable Entra ID authentication if there aren’t any associated users or devices. Naturally, this means Entra ID authentication for SCCM is enabled by default.

In the end, the process is pretty anti-climactic. Following completion, both applications can be seen in the console’s applications pane of registered Microsoft Entra ID tenants. What isn’t shown from this perspective is how the applications interact with each other or what permissions they’re granted in Entra.

Reviewing their API permissions in Entra shows the applications are pretty boring, and neither is configured with interesting permissions.

Instead, the two are simply part of a standard OAuth2 flow where a user signs in through the “clientapp” to access an API the “webapp” exposes. What isn’t clear (yet) is what the backend is that’s associated with the “webapp” application’s exposed API. 

Administration Service

The AdminService is an OData REST API that supports typical CRUD operations to enable administrative management of SCCM via HTTPs. During use, the API acts as an intermediary between the user and the SMS Provider. The SMS Provider is a site system role that hosts a WMI provider for management of the site through the site database. Spend enough time with SCCM and you’ll come to realize it’s all just a MSSQL wrapper.

The code snippet I shared at the beginning that showed the AdminService_TokenAuth authentication endpoint is found in the WmiMiddleware class in the AdminService.Host.dll library. This class is responsible for transforming OData requests to WMI queries that the SMS Provider processes on behalf of the authenticating user.

The ProcessRequest method handles determining how to generate the WindowsIdentity of the requesting user by first checking the Boolean value of this.aadEnabled. I showed earlier that this is set to “True” by default and allows Entra ID authentication for clients unless explicitly disabled. 

If it’s disabled, the method checks if the API request originates from a Service Connection Point (SCP) to set the WindowsIdentity. If it doesn’t, it processes the request using Windows integrated authentication. The SCP is a site system role that’s installed in a site to enable software updates and is also part of the Entra Tenant Attach cloud service. It’s not relevant here, but it is another method of integrating SCCM with Entra. 

If Entra ID authentication is left enabled, the service validates the client’s token through the ParserBearerToken method and returns a 401 error code if validation fails.


To begin the validation process, the ParseBearerToken method instantiates the TokenValidator class and passes the token to ValidateToken.

ValidateToken  wraps the ValidateTokenEx method and this is where things start moving a bit. First, there’s a caching check to see if the token has already been validated. If it hasn’t, it’s passed off to ValidateJsonToken to return a ClaimsPrincipal variable.

The below screenshot shows this method kicks off a few tasks:

  1. Parses out the issuer, audience, and tenantId  from the bearer token’s claims
  2. Creates a validatorKey out of the issuer and audience
  3. Checks to see if a current validatorKey exists and refreshes it from the database if it doesn’t

The GenerateValidatorKey method simply returns the concatenated upper-case string of the issuer and audience strings. 

The RefreshJsonTokenWithValidatorWithDB method uses the value of the tenantId variable to request authentication data from the site database. Before executing that query, though, the routine checks to see what service type has been implemented. This was defined previously when the TokenValidator class was constructed and is set to the “AdministrativeManagement” integer value of 0. I do not have this service set up, so the serviceType will fall back to ServiceType.CM (3).

For reference, there isn’t much documentation on what the “Administration Service Management ” service is or what it does at a low level, but Microsoft does have a snippet about it that’s interesting:

“When configuring Azure Services, for enhanced security you can select Administration Service Management option. Selecting this option allows administrators to segment their admin privileges between cloud management and administration service.”

Finally, a session is created (or reused) to query the site database for the issuer, audience, stsMetaData, and stsSigningCert values of the co-management service associated with the token’s tenantID

The GetAuthenticationInfo method shows how the database query is constructed. The routine declares the expected return values, then executes the spGetTokenValidationInfo stored procedure with the  “@TokenAuthority” and “@ServiceType” variables set to 0 (Entra) and 3 (ServiceType.CM), respectively.

Running this procedure manually in the database shows what would be returned during a client authentication attempt. This is the first connection made between the applications deployed in Entra and how Entra authentication works for the SCCM site.

The API exposed by the “webapp” application is returned as the audience value from the database.

Now, with a refreshed validatorKey from the site database, the two are compared to ensure the client’s token is being validated against the correct tenant configuration from the site. If that check passes, the token is passed off to ValidateJsonToken.

The ValidateJsonToken method first checks to see if the token should be validated using Microsoft Identity Service Engine (MISE) APIs. This lab isn’t using any type of federated authentication, so the routine passes the token to the ValidateJsonTokenLegacy method.

With the correct validation endpoint determined, this method instantiates a JwtSecurityTokenHandler class, then passes both it and the token to the ValidateJsonTokenCore method… which then passes the same values to ValidateToken.

From here, the length of the token is parsed by counting the segments in the token to determine if JSON Web Encryption (JWE) is in use and if it should be decrypted first before it’s validated. If unnecessary, the else statement is hit, and the token is passed to ValidateToken.

In this version of the ValidateToken method, the site configuration data is loaded then it and the token (which is using JWS) are passed to the ValidateJWS method.

Finally, ValidateJWS actually validates the token. The GetJwtSecurityTokenFromToken method does not parse out a security token; it simply deserializes and returns the token as jwtToken. The deserialized token then has its signature and payload validated based on that particular site’s configuration. If both pass, SCCM concedes and the validated token is returned.

Back in ValidateTokenEx, the token must pass one more check and is passed to the EndUserAuthenticationAdditionalChecks method. 

Here, the token’s claims are parsed to ensure that a version claim exists and has a value of either “1.0” or “2.0”. From there, each respective version is checked to ensure specific claim types are present and with the correct values.

If no exception is thrown from this last check, the token passes all validation checks and is returned to the ProcessRequest method in the WMIMiddleware class.

The ProcessRequest routine’s logic continues by parsing the token for the value of its UserPrincipalName (UPN) claim, then creates a new identity object by constructing a WindowsIdentity from that value.

When the WindowsIdentity class is constructed with the value for the userPrincipalName, it performs a Kerberos service-for-user (s4u) logon to impersonate the AD identity associated with the UPN.

Following impersonation, the API request is processed by the WMI provider and the results are returned to the user. It makes a lot of sense why there was that caching mechanism in the beginning.

To recap the flow:

  1. User authenticates to the Entra app and gets a token
  2. User sends API request to SCCM AdminService
  3. AdminService validates the token and extracts the Entra user’s UPN
  4. AdminService uses UPN to impersonate the AD account and run WMI commands

At no point during the token validation were there any authorization checks for the UPN in the Entra user’s claim. The service trusts the claims from a validated Entra token and will impersonate any Active Directory identity that can be mapped from the token’s UPN. All an attacker would need to elevate privileges is to control an Entra account with a UPN of a known privileged identity in SCCM because the WMI provider solely handles authorization checks

UPNs and Hybrid Environments

UPNs in AD are the “internet-style” logon name of an account formatted like an email (i.e., garrett@unsigned-sh0rt.net). The highlighted portion in the screenshot below shows the UPN populated during a new user account set up in AD.

In a typical hybrid deployment, if the domain is verified in Entra, the UPN of an Entra account is synchronized from its corresponding AD account’s UPN attribute. If the domain from the suffix of the UPN synced to Entra is not verified, or if the UPN value is empty, the cloud account is created with the default suffix of “onmicrosoft.com”. 

There’s some extra nuance to this attribute. The value of an AD or Entra identity’s UPN must be unique throughout an AD forest or Entra tenant and any attempt to reuse a UPN will result in a constraint violation.

There’s also Microsoft’s concept of UPN mapping. When a user signs into AD with a UPN as their logonname, the UPN is stored in the cname field of the AS-REQ sent to the KDC during the authentication service exchange. The KDC uses the cname to locate an account with a matching UPN value. If a match is found, an encryption key is derived from the located account’s stored credentials that is used by the KDC to decrypt the pre-authentication info stored in the padata field of the AS-REQ. 

This lookup is referred to as explicit UPN mapping: the provided logonname (cname) directly matches the UPN attribute value of an account in AD. But, it’s possible an account may not have an explicitly defined UPN for the KDC to include in the AS-REP. To handle this scenario, Microsoft defined how the KDC should construct a UPN for an account by, “…concatenating the user name, the “@” symbol, and the DNS name of the domain.” This is referred to as implicit UPN mapping:

“A UPN can be implicitly or explicitly defined. An implicit UPN is of the form UserName@DNSDomainName.com. An implicit UPN is always associated with the user’s account, even if an explicit UPN is not defined. An explicit UPN is of the form Name@Suffix, where both the name and suffix strings are explicitly defined by the administrator.”

When an explicit lookup fails but the provided UPN’s suffix matches the domain’s DNS name, the KDC tries really hard to find a match for the cname.

The first step is what has already been covered with the explicit UPN mapping. In the second, the prefix of the UPN is used to locate an account with a matching sAMAccountName attribute. If this search fails, a “$” is appended to the prefix to see if there’s a computer account that matches. If a match is still not found, steps 4 and 5 expand the lookup across the forest and trusted domains before finally giving up. 


All of these fallbacks offer a bit of wiggle room around the uniqueness constraints. I highlighted previously from Microsoft’s definition that an account will always have an implicit UPN mapped. This is true even if an explicit mapping exists. In fact, as Jorge demonstrates in his blog, the implicit UPN is the primary context an account is authenticated under and is present in the cached Kerberos tickets for the logon session. This means that an account may technically have two UPNs.

POC || GTFO

Honestly, there are too many hypothetical scenarios to detail from here that would demonstrate how to dance around the search order logic to synchronize a suitable UPN into Entra. Instead, I’ll address the scenario Microsoft used to describe abuse of this issue.

Despite documentation that explicitly states it’s best practice to not synchronize privileged AD accounts (like SCCM admins) to Entra, the easiest account to impersonate isn’t a user at all; it’s the primary site server’s machine account. The common condition, since it’s the default behavior, is that machine accounts in AD do not have a UPN value automatically generated when they’re created. 

Assumptions

For simplicity and the sake of demonstration, there are a few assumptions being made:

  1. The beachhead host is a managed SCCM client
  2. The domain’s DNS name is validated in Entra
  3. Existing control of an account with permissions to create a user account
  4. The created user account is a member of a synchronized container

The needed ClientID, Scope, and Entra tenant information can be pulled from the registry of an SCCM-managed client, as shown below.

With that out of the way, first, create a user account with an explicit UPN that matches the primary site server’s implicit UPN.

On the next AD synchronization cycle, the corresponding account is created in Entra with the site server’s implicit UPN value of “sccm-sitesrv$@unsigned-sh0rt.net”.

Once synced, use the “clientapp” to authenticate as the attacker user to Entra to get an access token scoped to the “webapp” API with the site server’s implicit UPN in the token claims.

From here, immediately trying to use the acquired access token against the AdminService API will fail. Since explicit UPN mapping is the first step in the client lookup sequence, the admin service will impersonate the created attacker account.

To get around this, the UPN for the attacker user is modified to its original value.

Now, when using the access token to authenticate to the API, the AdminService will resolve the token’s UPN to the implicit UPN of the site server’s machine account.

The AdminService.log shows the attempt succeeded and that the request was run on behalf of the site server’s machine account to add the low-privilege domainuser account as an SCCM admin.

Caveats

Of course, there are some caveats to abusing this attack path. The most obvious is an attacker must have the ability to manipulate UPNs since standard AD user accounts do not have the right to modify their own. There are a few permissions that could be misconfigured and abused to meet this requirement. Here’s a few examples:

  1. GenericWrite or WriteProperty for a target user
    1. You’ll still need the target account’s password to authenticate
  2. GenericAll, CreateChild, or CreateUser for a target Organizational Unit (OU)
  3. Write permissions for the Public Information property set
    1. The UPN attribute is bundled into the set and is currently an open PR for the BloodHound repo

Another caveat, and arguably the biggest blocker, is that the AD domain’s DNS name must be verified in Entra. Traversing this attack path in environments that use non-routable domains, such as corp.local, becomes significantly more difficult. I mentioned before that the behavior for an unverified or empty UPN synced to Entra will be automatically assigned the “onmicrosoft.com” prefix. This isn’t ideal for admins either and Microsoft provides some options on how to set up hybrid environments when in this situation.

The first option involves changing the AD domain which is a pretty rough undertaking and it’s arguably easier to just migrate everything to a new domain with the preferred DNS name. The second option is to add a UPN suffix to AD and then update user’s UPNs to allow synchronization. The documentation even simplifies updating the AD users by providing some PowerShell that will update every user account’s UPN including the privileged accounts that shouldn’t be synchronized.

If an attacker gains the ability to create users in Entra, and the above recommendation is followed, they could create a user with a UPN for an unsynchronized, privileged AD account and achieve the same result. I mentioned there were several hypotheticals right?

Conclusion

Ultimately, the whole issue boils down to creative UPN manipulation. We reported this issue to Microsoft and they fixed it in this hotfix. Admins that have hybrid environments should deploy the most recent patch to reduce their risk. I reviewed the patch and an additional check was added that parses the onprem_sid claim from the token. Now the service requires the Entra token to match an SCCM discovered user account in AD before processing requests at the SMS Provider which closes the door on this attack path. There’s some other service types that looked interesting, though.

Disclosure Timeline

  • 2025-06-03 – Initial disclosure to Microsoft; 2025-08-31 disclosure date
  • 2025-06-11 – Microsoft confirmed receipt
  • 2025-06-17 – Microsoft confirmed the behavior 
  • 2025-06-20 – Microsoft requested and I agreed to an extension to with tentative disclosure in late September
  • 2025-08-13 – Requested an update for patch release
  • 2025-08-18 – Microsoft confirmed an expected release date of late September
  • 2025-09-26 – Microsoft informed the patch would be delayed to late November
  • 2025-09-26 – Requested more information and pushed back on the late notice update 2025-09-29 – Microsoft informed me that the service team may be able to release earlier
  • 2025-10-13 – Microsoft assigned the case CVE-2025-59501
  • 2025-10-27Patch released