Fear Is the Mind Killer, SCCM Is the Tier Killer

Common SCCM Misconfigurations Leading to Privilege Escalation

  • Insight

Introduction

On a daily basis, the offensive security team at Truesec performs penetration tests and security assessments where Windows infrastructure and Active Directory (AD) is in scope. In some of the cases, we meet clients who have hardened their environment to some extent, including using LAPS, and implementing a tiered administration model. However, it’s not uncommon that we discover cracks in the model that can be exploited to pivot between tiers and ultimately achieve domain compromise.

We often find that in environments which has a tiered model, where SCCM is used, there are plenty of misconfigurations which can be exploited. System Center Configuration Manager (SCCM), now known as Microsoft Configuration Manager (ConfigMgr), is a systems management platform used for deploying software, managing updates, and enforcing configuration settings across large numbers of Windows devices.

For the rest of this blog post, I will keep on referring to it as SCCM as this is the most well known name, and is still consistently used within the offensive security realm.

As SCCM has been a target of interest by offensive security research during the past couple of years, tooling and techniques to compromise SCCM and use it for post exploitation purposes have been described in detail, see the reference list at the bottom of this post.

This post does not present any new concepts or tooling. The main goal of the post is to reiterate on known problems with SCCM and share some insights from our experience on this topic.

In a typical SCCM deployment, multiple AD accounts are configured to be used for different purposes. In this post we will hone in on two of those, the Network Access Account (NAA) and the Domain Join Account.

As a bonus, an additional piece at the end dives into some findings discovered while researching attack paths in SCCM environments where LAPS is deployed.

SCCM Network Access Account Misconfigurations

We often find that SCCM is configured to use Network Access Accounts (NAA) within the environment during the deployment process. This is a legacy setting which should, if possible, be removed in favor of using PKI certificates for client authentication. The purpose of the NAA is simply to access network resources (distribution points). The NAA does not perform any actions on the managed endpoint, hence, it should be configured according to the principle of least privilege.

The thing is, that the NAA credentials are stored locally on every device that is or have been managed by SCCM, and is encrypted with the Data Protection API (DPAPI).

These credentials can be extracted from the endpoint, for example by using tools such as SharpSCCM. The tool can be used to extract credentials from the endpoints local disk, which needs local administrator rights. SharpSCCM or sccmsecrets.py can also be used to attempt to retrieve the NAA credentials by “registering a new device” within SCCM, which only needs a valid domain computer account to succeed (or register a new device anonymously if non-default settings are misconfigured). A domain computer account is easy to construct within a default AD environment, as a normal user can register up to ten devices (machine account quota=10).

When SCCM is configured with automatic device approval, it allows anonymous device registration which can be abused by an attacker without authenticating.

For example, we can register a new device with sccmsecrets.py:

python3 SCCMSecrets.py policies --management-point http://sccm01.labbet.local --client-name patshehe

As we can see in the SCCM console, the device was registered successfully, and the output of the sccmsecrets.py command above gives us the NAA credentials.

Output of the sccmsecrets.py command, including the sccm-naa credentials:

NAA’s are often overprivileged, which can be abused to escalate privileges within the domain. For example, in a recent client engagement, we discovered that the NAA had permissions to add members to a group, where members of said group had the permission to change the password of tier 1 administrators.

In another engagement we discovered that the NAA could be used to log in to the SCCM primary site database server, with system administrative rights (sa) over the database. Do note that administrative rights over the database is not the same as administrative rights over SCCM. However, as SCCM permissions are based on table entries within the CM_<SITECODE> database, you can promote yourself to a full SCCM administrator if you have administrative rights over the database.

This allows the attacker to configure SCCM at will, for example deploying software to any managed endpoint, effectively allowing them to move laterally within the environment.

To do that, we first need to convert the SID of the user that we want to promote to SCCM admin to a hex representation. We can use sccmhunter.py or SharpSCCM for that. Below is an example of how to do it with sccmhunter.py, which will generate the full SQL command that we need to run with the administrative account in order to escalate our privileges of the lowpriv user.

python3 sccmhunter.py mssql -dc-ip 192.168.56.130 -d LABBET.LOCAL -u 'lowpriv' -p '<PASSWORD>' -tu lowpriv -sc lab -stacked

Running the sccmhunter.py command:

The SQL statement:

DECLARE @AdminID INT;

USE CM_lab;

INSERT INTO RBAC_Admins (AdminSID, LogonName, IsGroup, IsDeleted, CreatedBy, CreatedDate, ModifiedBy, ModifiedDate, SourceSite) SELECT 0x01050000000000051500000072BD282024B3B6E219A14E8D4F040000, 'LABBET\lowpriv', 0, 0, '', '', '', '', 'lab' WHERE NOT EXISTS ( SELECT 1 FROM RBAC_Admins WHERE LogonName = 'LABBET\lowpriv' );

SET @AdminID = (SELECT TOP 1 AdminID FROM RBAC_Admins WHERE LogonName = 'LABBET\lowpriv');

INSERT INTO RBAC_ExtendedPermissions (AdminID, RoleID, ScopeID, ScopeTypeID) SELECT @AdminID, RoleID, ScopeID, ScopeTypeID FROM (VALUES  ('SMS0001R', 'SMS00ALL', 29), ('SMS0001R', 'SMS00001', 1), ('SMS0001R', 'SMS00004', 1) ) AS V(RoleID, ScopeID, ScopeTypeID) WHERE NOT EXISTS ( SELECT 1 FROM RBAC_ExtendedPermissions  WHERE AdminID = @AdminID  AND RoleID = V.RoleID  AND ScopeID = V.ScopeID AND ScopeTypeID = V.ScopeTypeID );

Upgrading the user lowpriv to full SCCM administrator with sccmsecrets.py:

In both of these engagements, misconfigurations and overprivileged NAA accounts allowed us to escalate from a regular employee to a tier 1 admin, and from there on we abused other flaws within the tiered access model to escalate to tier 0.

Furthermore, it’s not uncommon that the NAA account is also used as the “domain join account”, which should normally be a separate account. The purpose of the “join account” is simply to join computers to the domain within the operating systems deployment phase, which has it’s own problems that we will look into below.

SCCM Domain Join Account Misconfigurations

In a third engagement, we discovered an interesting attack scenario based on the “join account” used within the environment. The typical finding that we discover is that the “join account”, which naturally becomes the owner of the computer object within the AD (this is by design), has joined devices within multiple different tiers to the domain. This means the account can be abused to escalate between tiers, if compromised.

With owner permissions over an object in AD, you’re given all extended rights. This means that the account has read and write permission to many attributes of that object, including the LAPS password. It should be noted that owner permissions can be abused in other ways, for example configuring Resource Based Constrained Delegation (RBCD), but in this post we are focusing on the LAPS scenario.

The flaw can be identified in multiple ways, but it ultimately comes down to reviewing the owner of computer objects in AD – which can be found in the mS-DS-CreatorSID attribute.

This is a known issue, which is even documented in the LAPS operations guide, from Microsoft. However, the command in the operations guide is slightly wrong. In the command there is a typo in the ms-ds-CreatorSid (CORRECT) attribute, as they have written msds-CreatorSid (INCORRECT) with a missing hyphen. If you copy and paste the command written in the guide – you will miss important findings.

We can use the (corrected) PowerShell command below to properly find the issue:

Get-ADComputer -LdapFilter '(ms-DS-CreatorSID=*)' -SearchScope SubTree -Properties * | Select-Object Name, mS-DS-CreatorSID

Do note that the command only works correctly if the full RSAT Active Directory PowerShell modules is installed, if you only import the DLL, you will not get the mS-DS-CreatorSID attribute. It’s unknown why – most likely it’s implemented in another DLL, that would need to be imported as well.

However, back when we were doing the engagement the issue was initially highlighted to us via rule triggered from PingCastleA-LAPS-Joined-Computers, but it was not clear exactly which account had the permission to retrieve the LAPS password. The finding in PingCastle was given a 5 point score and classified in the Level 3 section of the report, which is quite low. We looked at it briefly but got more interested in other findings with higher initial scoring and output from other reconnaissance tools.

When reviewing the results from our network share reconnaissance we identified credentials in cleartext which resided in the CustomSettings.ini file on a share called Packages$, exposed on a server in the environment. This is a file used with Microsoft Deployment Toolkit (MDT) and is normally present on a Deployment Share which in turn is centrally accessible to facilitate deployment. MDT can also be integrated with SCCM, which was the case in this particular environment.

Domain join account credentials can also be leaked or found in various other places, for example by extracting task sequence variables from PXE media files.

The discovered credentials gave us access to a domain account called SVC.JOINACC, which at first sight did not look that juicy at all. It had no special group memberships, in fact, it was only a member of the Domain Users group.

The name of the account still sounded interesting and reminded us of the finding from PingCastle, so we took a second look at it. PingCastle does not show the value of the mS-DS-CreatorSID attribute, but it does list the computer accounts which the finding is applicable to. So we manually reviewed the attribute on the computer accounts (the tier 1 servers were of special interest).

At first, we thought of just looking it up via the BloodHound dump we had already collected, but it turns out that the BloodHound collector does not retrieve this attribute properly. The data seems to not be decoded properly, and I don’t know why, haven’t had time to dig into the source code of BloodHound/SharpHound.

Trying do decode the value with CLI tools also failed:

So, then we checked manually with ADExplorer instead, and then resolved the SID by searching for the machine account objects in BloodHound. Resolution of the SID could of course also be done in ADExplorer or via other methods, such as PowerShell. The resolution showed that it was indeed the discovered SVC.JOINACC which was the owner of the servers’ computer object in AD.

First, we get the creator object from AD explorer:

Then, we resolve the SID in BloodHound:

So the next thing we did was logging in with the discovered SVC.JOINACC credentials in ADExplorer and see if we could retrieve the LAPS password, and it worked.

By abusing this misconfiguration we could escalate from Domain Users to Server Admin (tier 1), as the account had joined the majority of the server fleet to the domain as part of the deployment process, and hence was eligible to retrieve the LAPS password.

This included both Exchange servers and the Certificate Authority in the domain, both of which could be abused to escalate to Domain Admins (tier 0).

Additional Research

The finding in the assessment described above was associated with the Legacy LAPS solution, but the same situation applies to the new Windows LAPS as the abuse primitive is based on how the joining process in AD works out of the box.

I have simulated the same situation discovered during the assessment in my own lab. However, in my lab I’ve replaced Legacy LAPS with Windows LAPS, and will try to explain the flow of actions below where we see it’s applicable to Windows LAPS as well.

As seen below, the account SVC.JOINACC in my lab, which is the account that joined the computer CLIENT01 to the domain, has the permissions All extended rights.

This includes the special permissions to read the ms-LAPS-EncryptedPassword and ms-LAPS-Password attributes.

This PowerShell code can be used to find the issue:

# Replace with the DN of the computer object according to your needs
$computerDN = "CN=CLIENT01,OU=Clients,DC=labbet-newlaps,DC=local"

# Load the DirectoryEntry of that computer object
$entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$computerDN")

# Get the security descriptor (includes the ACL)
$acl = $entry.ObjectSecurity

# GUID for 'All Extended Rights', as we're checking for the ExtendedRight on the object type 
# '00000000-0000-0000-0000-000000000000' which is a wildcard, matching with "All"
$allExtendedRightsGuid = [Guid]"00000000-0000-0000-0000-000000000000"

# Get the access control rules
$accessRules = $acl.GetAccessRules($true, $true, [System.Security.Principal.NTAccount])

# Filter for Access Control Entries (ACEs) with ExtendedRight and ObjectType = All Extended Rights
$extendedRightsACEs = $accessRules | Where-Object {
    ($_.ActiveDirectoryRights -band [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight) -and
    ($_.ObjectType -eq $allExtendedRightsGuid -or $_.ObjectType -eq [Guid]::Empty)
}

# Display the identities
$extendedRightsACEs | Select-Object IdentityReference, ActiveDirectoryRights, ObjectType, InheritanceType

When researching about this, I followed the official Microsoft documents on how to deploy Windows LAPS within an environment.

One step of the guide is to look for this particular problem, accounts with extended rights on computers where LAPS is in use. This is done with the Find-LapsADExtendedRights PowerShell command from the LAPS PowerShell module.

Find-LapsADExtendedRights -Identity Clients -IncludeComputers

When running this command in my lab, we can see that the command only finds that NT AUTHORITY\SYSTEM and the Domains Admins within the domain have extended rights on the computers within the Clients OU. But this is clearly not right as we saw earlier in the figures above.

This is also seen in comparison with manually looking up the access controls on the account with the earlier provided PowerShell code.

This can be confirmed by attempting to read the LAPS password with the join account:

There is clearly a bug with the LAPS PowerShell module, specifically in the FindLapsADExtendedRights cmdlet. This gives a false sense of security in which you would think you’ve covered your back and validated that no unwanted identities have permissions to read the LAPS password.

Let’s figure out what is wrong here by analyzing the code of the LAPS PowerShell module. This is done by decompiling the C:\Windows\System32\WindowsPowerShell\v1.0\Modules\LAPS\lapspsh.dll with dnSpy. As seen in the figure below, there is a function called CheckACERights() within the FindLapsADExtendedRights class.

Now, I’m not a professional programmer, so I might be missing something here, but I will try to note down my understanding of the code below.

The CheckAceRights() function looks like this (comments added by me):

private bool CheckACERights(ActiveDirectoryAccessRule ace)
{
    bool result = false;

    // These GUIDs represent the LAPS-related attributes
	Guid schemaGuid = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "ms-LAPS-Password", SchemaObjectType.Attribute, true);
	Guid schemaGuid2 = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "ms-LAPS-EncryptedPassword", SchemaObjectType.Attribute, true);
	Guid schemaGuid3 = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "ms-LAPS-EncryptedPasswordHistory", SchemaObjectType.Attribute, true);
	
    // This GUID is for the "computer" class
    Guid schemaGuid4 = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "computer", SchemaObjectType.Class, true);

    // The if statement below checks four different access cases:

    // Case 1: Extended rights access to ms-LAPS-Password
    if (
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         ace.ObjectType == schemaGuid && // Targeting ms-LAPS-Password
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance
         
        ||

        // Case 2: Extended rights access to ms-LAPS-EncryptedPassword
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         ace.ObjectType == schemaGuid2 && // ms-LAPS-EncryptedPassword
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance

        ||

        // Case 3: Extended rights access to ms-LAPS-EncryptedPasswordHistory
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         ace.ObjectType == schemaGuid3 && // ms-LAPS-EncryptedPasswordHistory
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance

        ||

        // Case 4: ACE grants GenericAll (Full Control) over the object
        (ace.ActiveDirectoryRights == 983551 && // Full Control
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance

    )
    {
        result = true;
    }

    return result;
}

So it’s clear that they are checking for two things here:

  • Does the ACE of analysis have Extended Rights on the sensitive LAPS attributes on the computer object?
  • Does the ACE of analysis have Full Control on the computer object?

But they are missing to check if any identity has the permission all extended rights on the computer object. They should probably add another check within the if statement, something like to the code below (similar to the previous PowerShell code):

private bool CheckACERights(ActiveDirectoryAccessRule ace)
{
    bool result = false;

    // NEW: GUID used to represent ALL Properties (wildcard)
    Guid allExtendedRightsGuid = new Guid("00000000-0000-0000-0000-000000000000"); // or Guid.Empty; 

    // These GUIDs represent the LAPS-related attributes
	Guid schemaGuid = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "ms-LAPS-Password", SchemaObjectType.Attribute, true);
	Guid schemaGuid2 = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "ms-LAPS-EncryptedPassword", SchemaObjectType.Attribute, true);
	Guid schemaGuid3 = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "ms-LAPS-EncryptedPasswordHistory", SchemaObjectType.Attribute, true);
	
    // This GUID is for the "computer" class
    Guid schemaGuid4 = base.GetSchemaGuid(this._ldapConn, this._ldapConnectionInfo.Forest.SchemaNamingContext, "computer", SchemaObjectType.Class, true);

    // The if statement below checks four different access cases:

    // Case 1: Extended rights access to ms-LAPS-Password
    if (
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         ace.ObjectType == schemaGuid && // Targeting ms-LAPS-Password
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance
         
        ||

        // Case 2: Extended rights access to ms-LAPS-EncryptedPassword
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         ace.ObjectType == schemaGuid2 && // ms-LAPS-EncryptedPassword
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance

        ||

        // Case 3: Extended rights access to ms-LAPS-EncryptedPasswordHistory
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         ace.ObjectType == schemaGuid3 && // ms-LAPS-EncryptedPasswordHistory
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance

        ||

        // Case 4: ACE grants GenericAll (Full Control) over the computer object
        (ace.ActiveDirectoryRights == 983551 && // Full Control
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuid4)) // Applies to computer objects or "all objects" by inheritance

        ||

        // NEW: Check for All Extended Rights on the computer object
        ((ace.ActiveDirectoryRights & 256) == 256 && // Extended Rights
         (ace.ObjectType == allExtendedRightsGuid || ace.ObjectType == Guid.Empty) && // Object type is the wildcard "00000000-0000-0000-0000-000000000000" or empty - meaning ALL properties.
         (ace.InheritedObjectType == Guid.Empty || ace.InheritedObjectType == schemaGuidComputer)) // Applies to computer objects or "all objects" by inheritance

    )
    {
        result = true;
    }

    return result;
}

Learnings and Recommendations

  • It’s important to review even low prio findings from tools (i.e. the one from PingCastle) when pentesting, as many times, the tools used in and of itself doesn’t have the context and intuition that the operator has to identify flaws which can be chained together.
  • Trust but verify – as we saw from both the Legacy LAPS operations guide and the dive into the Windows LAPS rabbit hole, some tools and documentation have incorrect information. The more experience and knowledge you have, the more you are able to manually double check if tooling output is correct.
  • When you have a tiered access model in place, it’s important to review the setup to find any potential vectors that can break the access between any of the tiers.
  • When using SCCM in an AD environment, it’s crucial to follow the principle of least privilege for SCCM accounts, including domain join accounts and network access accounts. This includes both within the Active Directory domain and local privileges on the SCCM database level.

For example, the domain join account should only have permission to join to the domain and nothing else. Deny interactive logon, implement monitoring on their usage and identify usage outside of that scope. The following permissions are the only required ones for the domain join account, which shall be applied to descendant computer objects only:

  • Read All Properties
  • Write All Properties
  • Read Permissions
  • Modify Permissions
  • Change Password
  • Reset Password
  • Validate Write to DNS hostname
  • Validate Write to Service Principal Name

The Misconfiguration Manager repository from SpecterOps is a good resource for additional SCCM hardening.

  • Don’t use NAA, use PKI or eHTTP instead for secure communication. If you previously used NAA and move on to using PKI or eHTTP – make sure to still limit the privileges of the NAA account and disable it. The reason for this is that the account credentials can still be extracted from a managed device that was previously configured to use NAA during the deployment process.
  • If you are using domain join accounts in the deployment process, make sure to add a step in your automation to update the CREATOR OWNER of the joined computer objects to DOMAIN ADMINS to minimize attack surface.
  • Regardless if you are using Legacy LAPS or Windows LAPS, it’s important to continuously analyze who has extended rights over computers using LAPS. Read more about LAPS here.

References

  • https://techcommunity.microsoft.com/blog/coreinfrastructureandsecurityblog/you-might-want-to-audit-your-laps-permissions-/2280785
  • https://cybergladius.com/the-active-directory-access-control-list-explained/#10-objecttype
  • https://github.com/PowerShell/Community-Blog/issues/70
  • https://azurecloudai.blog/2019/10/01/laps-security-concern-computers-joiners-are-able-to-see-laps-password/
  • https://www.securityinsider-wavestone.com/2020/01/taking-over-windows-workstations-pxe-laps.html
  • https://dirteam.com/sander/2020/12/21/howto-check-your-laps-implementation-for-proper-security/
  • https://github.com/subat0mik/Misconfiguration-Manager/
  • https://posts.specterops.io/misconfiguration-manager-overlooked-and-overprivileged-70983b8f350d
  • https://posts.specterops.io/rooting-out-risky-sccm-configs-with-misconfiguration-manager-0beecaab1af3
  • https://www.mwrcybersec.com/research_items/identifying-and-retrieving-credentials-from-sccm-mecm-task-sequences
  • https://www.youtube.com/watch?v=W9PC9erm_pI
  • https://www.synacktiv.com/en/publications/sccmsecretspy-exploiting-sccm-policies-distribution-for-credentials-harvesting-initial
  • https://learn.microsoft.com/en-us/windows-server/identity/laps/laps-overview
  • https://www.microsoft.com/en-us/download/details.aspx?id=46899