Skip to content
03 · DelegationPart 4 of 4Foundation

Set ms-DS-MachineAccountQuota to 0 and delegate joins

Stop any domain user from creating computer accounts: set ms-DS-MachineAccountQuota to 0, find who relies on it, and delegate domain join to an OU.

Florian Amette7 min read

By default, every authenticated user in an Active Directory domain can create up to ten computer accounts. The limit is stored in the ms-DS-MachineAccountQuota attribute on the domain head, and the permission comes from the "Add workstations to domain" user right, which the Default Domain Controllers Policy grants to Authenticated Users. It was a convenience for the era when users joined their own PCs. Today it mostly hands attackers a free domain principal with a known password and a controllable SPN, which is exactly what RBCD abuse, relay-to-LDAP chains and several AD CS template attacks need.

This is one of the simplest hardening changes in AD: one attribute and one user right. The work is in finding who silently depends on the default and giving them a proper, scoped delegation instead.

Why a free computer account matters

A user account alone is a weak foothold for several AD attacks. Many of them need a principal that has a service principal name, a password the attacker knows, and the right to request Kerberos tickets as a service. Computer accounts have all three, and the quota lets any authenticated user create one from any domain-joined or even non-joined machine with network access to a DC, using nothing more than standard LDAP or SAMR calls. Offensive toolkits such as Impacket and Powermad include a one-line command for it.

What that account enables depends on the rest of your configuration, which is why the quota shows up in so many attack chains:

  • Resource-based constrained delegation. If the attacker can write msDS-AllowedToActOnBehalfOfOtherIdentity on a target, directly or through an NTLM relay to LDAP, they point it at their new computer account and impersonate users to the target.
  • AD CS templates enrollable by Domain Computers. The new account is automatically a member of Domain Computers, so it can enroll in the default Machine template and any custom template with the same permissions. Combined with other template misconfigurations, that becomes a path to authentication as someone else.
  • Kerberos implementation bugs. The 2021 sAMAccountName spoofing issues (CVE-2021-42278 and CVE-2021-42287) required a computer account the attacker could rename. Patching fixed those specific bugs, but the next one will likely have the same prerequisite.
  • Persistence. A computer account created during an intrusion is rarely noticed, rarely expires and keeps a valid password for as long as the attacker wants.

None of these require the quota if the attacker already has OU-level create rights, which is why the audit below also looks at who holds those.

Measure: current quota and who has used it

Read the current value and check who holds the user right on domain controllers.

PowerShell
Import-Module ActiveDirectory

$domainDN = (Get-ADDomain).DistinguishedName
Get-ADObject -Identity $domainDN -Properties 'ms-DS-MachineAccountQuota' |
    Select-Object DistinguishedName, 'ms-DS-MachineAccountQuota'

For the user right, open the GPO linked to the Domain Controllers OU (normally Default Domain Controllers Policy) and look at:

Text
Computer Configuration > Policies > Windows Settings > Security Settings >
  Local Policies > User Rights Assignment > Add workstations to domain

The default value is Authenticated Users. On a DC you can confirm the effective setting with gpresult /h or by exporting the local security policy with secedit /export /cfg, where the right is SeMachineAccountPrivilege.

Then find computer objects created through the quota. When a non-privileged user creates a computer account using SeMachineAccountPrivilege, the DC stamps the creator's SID in mS-DS-CreatorSID. Accounts created by admins or through OU delegation do not carry it.

PowerShell
Get-ADComputer -LDAPFilter '(mS-DS-CreatorSID=*)' `
    -Properties mS-DS-CreatorSID, whenCreated, operatingSystem, lastLogonTimestamp, enabled |
    ForEach-Object {
        $sid = $_.'mS-DS-CreatorSID'
        $creator = try { (New-Object Security.Principal.SecurityIdentifier($sid, 0)).Translate(
                        [Security.Principal.NTAccount]).Value } catch { "$sid (unresolved)" }
        [PSCustomObject]@{
            Computer   = $_.Name
            Creator    = $creator
            Created    = $_.whenCreated
            OS         = $_.operatingSystem
            Enabled    = $_.Enabled
            LastLogon  = if ($_.lastLogonTimestamp) { [datetime]::FromFileTime($_.lastLogonTimestamp) }
            DN         = $_.DistinguishedName
        }
    } | Sort-Object Created -Descending | Export-Csv .\quota-created-computers.csv -NoTypeInformation

Pay attention to computers with no operating system value and no logon: that pattern often means an account created by a script or tool, not by a real join. Recent entries created by standard users are either a helpdesk process nobody documented or something to hand to incident response.

Audit: who legitimately joins machines

Look at event 4741 (a computer account was created) on the DCs over a few weeks. The Subject fields show which account created each computer object. Group by creator to see which people and service accounts join machines and how often.

PowerShell
Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4741 } -MaxEvents 5000 |
    ForEach-Object {
        $x = [xml]$_.ToXml()
        $d = @{}; $x.Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
        [PSCustomObject]@{ Creator = "$($d.SubjectDomainName)\$($d.SubjectUserName)"; Computer = $d.TargetUserName }
    } | Group-Object Creator | Sort-Object Count -Descending | Select-Object Count, Name

Run this against every DC, or better, against your central collector (see AD auditing and detection). Typical legitimate creators are: the deployment service account used by SCCM/MDT task sequences, the Intune Connector for Active Directory used for hybrid Autopilot joins, a few server administrators and helpdesk technicians. Anything outside those groups goes on the list to talk to.

Enforce: set the quota to 0 and delegate properly

Create the delegated join rights first

Create a group, for example GG-Workstation-Join, and give it join rights on the target OU only. The minimal set on the OU is:

  • Create Computer objects and Delete Computer objects on the OU (this object and descendants).
  • On descendant computer objects: Reset password, Read and write Account Restrictions, Validated write to DNS host name and Validated write to service principal name.

You can grant this with the Delegation of Control Wizard (custom task, "Only the following objects in the folder: Computer objects"), or scripted with the ActiveDirectory provider:

PowerShell
$ou      = 'OU=Workstations,DC=corp,DC=example,DC=com'
$group   = Get-ADGroup 'GG-Workstation-Join'
$sid     = [Security.Principal.SecurityIdentifier]$group.SID
$computer = [guid]'bf967a86-0de6-11d0-a285-00aa003049e2'   # computer class
$resetPw  = [guid]'00299570-246d-11d0-a768-00aa006e0529'   # Reset Password
$acctRes  = [guid]'4c164200-20c0-11d0-a768-00aa006e0529'   # Account Restrictions
$dnsHost  = [guid]'72e39547-7b18-11d1-adef-00c04fd8d5cd'   # Validated write to DNS host name
$spn      = [guid]'f3a64788-5306-11d1-a9c5-0000f8036d4d'   # Validated write to SPN

$R = [System.DirectoryServices.ActiveDirectoryRights]
$A = [System.Security.AccessControl.AccessControlType]::Allow
$I = [System.DirectoryServices.ActiveDirectorySecurityInheritance]

$acl = Get-Acl "AD:\$ou"
$acl.AddAccessRule((New-Object DirectoryServices.ActiveDirectoryAccessRule($sid, ($R::CreateChild -bor $R::DeleteChild), $A, $computer, $I::All)))
$acl.AddAccessRule((New-Object DirectoryServices.ActiveDirectoryAccessRule($sid, $R::ExtendedRight, $A, $resetPw, $I::Descendents, $computer)))
$acl.AddAccessRule((New-Object DirectoryServices.ActiveDirectoryAccessRule($sid, ($R::ReadProperty -bor $R::WriteProperty), $A, $acctRes, $I::Descendents, $computer)))
$acl.AddAccessRule((New-Object DirectoryServices.ActiveDirectoryAccessRule($sid, $R::Self, $A, $dnsHost, $I::Descendents, $computer)))
$acl.AddAccessRule((New-Object DirectoryServices.ActiveDirectoryAccessRule($sid, $R::Self, $A, $spn, $I::Descendents, $computer)))
Set-Acl -Path "AD:\$ou" -AclObject $acl

Scope this to workstation and server staging OUs only, never to the Domain Controllers OU or any OU holding Tier 0 assets. Put the deployment service accounts, the Intune connector account and the helpdesk join group in it, and give each its own group if they target different OUs.

For machines that should be joined by a specific person without broader rights, pre-stage the computer object and use "The following user or group can join this computer to a domain" in Active Directory Users and Computers, or use offline domain join with djoin.exe /provision run by an administrator.

Set the quota to 0

PowerShell
Set-ADDomain -Identity (Get-ADDomain) -Replace @{ 'ms-DS-MachineAccountQuota' = 0 }

Changing the attribute requires write access to the domain head, normally Domain Admins. It takes effect as soon as it replicates; there is nothing to reboot.

Remove Authenticated Users from the user right

With the quota at 0 the user right is harmless for standard users, but removing it is defence in depth and makes intent explicit. In the Domain Controllers GPO, set Add workstations to domain to your join group only, or leave it empty if all joins go through OU delegation (OU permissions do not depend on this right).

Clean up what the quota created

Work through the CSV from the measure step. For each legitimate device, move it to the correct OU, confirm the owner is Domain Admins or your provisioning group, and remove the explicit ACEs granted to the original creator: the user who created the object keeps write rights on it (including the Account Restrictions property set) long after the device changes hands. Disable accounts with no matching device, wait a cycle, then delete.

Note the domain join hardening introduced with the October 2022 updates (KB5020276): reusing an existing computer account during join is now blocked unless the joining account created it, is a privileged account, or is allowed by the "Domain controller: Allow computer account re-use during domain join" policy. Fixing ownership during cleanup avoids surprises there when devices are reimaged.

Verify

PowerShell
# Quota is 0
(Get-ADObject (Get-ADDomain).DistinguishedName -Properties 'ms-DS-MachineAccountQuota').'ms-DS-MachineAccountQuota'

# No new quota-created computers since the change
$changeDate = Get-Date '2026-01-15'   # date the quota was set to 0
Get-ADComputer -LDAPFilter '(mS-DS-CreatorSID=*)' -Properties whenCreated |
    Where-Object { $_.whenCreated -gt $changeDate } |
    Select-Object Name, whenCreated

Test a join with a standard user account: it should fail with an error stating the user has exceeded the maximum number of computer accounts. Test a join with a member of the delegated group into the staging OU: it should succeed. Keep alerting on 4741 where the creator is not a known provisioning account. Most assessment tools flag a non-zero quota, so your next PingCastle assessment should clear that rule.

What it breaks

  • Users joining their own machines with their normal domain account, including developers building lab VMs joined to production AD. They now need the delegated group or a pre-staged object.
  • Deployment processes that used a plain user account without OU delegation, often an old MDT JoinDomain credential in CustomSettings.ini or a task sequence network join account. They fail at the join step until the account is added to the delegated group.
  • Third-party tools that create computer objects for Linux hosts (realmd/SSSD, Samba), NAS appliances or VDI brokers when configured with a standard account. Give each a dedicated service account with delegation on its own OU.
  • Hybrid Autopilot if the Intune connector's computer account or service account was not delegated on the target OU; the documentation already requires this, but tenants that "just worked" were often relying on the quota.
  • Reimaging with reuse of existing names may hit the 2022 join hardening if ownership was not normalized.

Related reading: the Delegation topic and pillar guide on Active Directory delegation, the machine account quota glossary entry, and AD CS template auditing, where machine templates enrollable by Domain Computers turn any attacker-created computer into a certificate.

Frequently asked questions

Does setting ms-DS-MachineAccountQuota to 0 stop admins or deployment tools from joining computers?

No. The quota only limits accounts that create computer objects through the 'Add workstations to domain' user right. Domain Admins and any account with 'Create Computer objects' permission on an OU are not counted against it. If your imaging, SCCM, MDT or Intune hybrid join connector uses a service account delegated on an OU, it keeps working. Only users who were joining machines with nothing but their own account are affected.

Why do attackers care about creating a computer account?

A computer account is a domain principal with a password the attacker chooses and an SPN they control. That is the missing ingredient for several attacks: resource-based constrained delegation abuse, relaying to LDAP to configure RBCD, some AD CS certificate template abuses and certain Kerberos exploits like the 2021 sAMAccountName spoofing chain. With the default quota of 10, any phished user account provides one.

What should I do with computer accounts that users already created?

List them using the mS-DS-CreatorSID attribute, which records the user who created each one through the quota. Match each to a real device. Legitimate machines should be moved to the correct OU and have their ownership changed to Domain Admins or your provisioning group. Accounts with no matching device, or created by accounts that are now disabled, should be disabled and investigated before deletion.

Set ms-DS-MachineAccountQuota to 0 and delegate joins

Related guides

Group Policy & SYSVOL

Auditing GPO permissions and gPLink rights

Find who can edit, create and link GPOs in Active Directory: GPO ACLs, gPLink rights on OUs and sites, Group Policy Creator Owners and WMI filters.

Intermediate