# WriteAccountRestrictions

## Summary

|                            |                                           |
| -------------------------- | ----------------------------------------- |
| **FSProtect ACL Alias**    | WriteAccountRestrictions                  |
| **AD Alias**               | Write Account Restrictions                |
| **Affected Object Types**  | Users, Computer, Managed Service Accounts |
| **Exploitation Certainty** | Certain                                   |
| **AD Right**               | WriteProperty                             |
| **Permission Guid**        | 4c164200-20c0-11d0-a768-00aa006e0529      |

## Description

The `WriteAccountRestrictions` permission in Active Directory refers to the `User-Account-Restrictions` property set. This permission allows an account to modify restriction attributes on user accounts, such as *logon hours*, *permitted workstations*, and *account expiration dates*. Critically, it includes the ability to modify the `msDS-AllowedToActOnBehalfOfOtherIdentity` attribute, which is essential for configuring Kerberos `Resource-Based Constrained Delegation` (RBCD).

If misconfigured or exploited, an attacker with the `WriteAccountRestrictions` permission can alter the `msDS-AllowedToActOnBehalfOfOtherIdentity` attribute on target objects. This enables them to perform a Kerberos RBCD attack by allowing an account they control to act on behalf of other identities. As a result, the attacker can impersonate any user—including domain administrators—to access services on the target computer. This can lead to unauthorized access, privilege escalation, and compromise of the entire network.

## Identification

#### Active Directory Module

Using the ActiveDirectory PowerShell module, you can enumerate `WriteAccountRestrictions` entries.

### PowerShell

**1.** Find-WriteAccountRestrictions function

```powershell
function Find-WriteAccountRestrictions {
    [CmdletBinding()]
    param ([string]$SearchBase = $null,[string]$OutputPath = "WriteAccountRestrictions.csv")
    Import-Module ActiveDirectory -ErrorAction stop
    Write-Host "Gathering Active Directory objects and inspecting ACLs for explicit 'Write Account Restrictions' permissions..."
    $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow;
    $ActiveDirectoryRights = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty;
    # Guid of Write Account Restrictions permission
    # https://learn.microsoft.com/en-us/windows/win32/adschema/r-user-account-restrictions
    $WriteAccountRestrictionsPermissionGuid = "4c164200-20c0-11d0-a768-00aa006e0529";
    $ExcludedSIDs = @()
    try { $ExcludedSIDs += (Get-ADGroup -Identity "Domain Admins").SID
    }catch {Write-Warning "Could not resolve SID for 'Domain Admins'. It might not be filtered from results." }
    $adObjectParams = @{
        Filter    = "*"
        Properties = "nTSecurityDescriptor"
        ErrorAction = "Stop"
    }
    if ($SearchBase) {
        $adObjectParams.Add("SearchBase", $SearchBase)
        Write-Host "Searching for objects within '$SearchBase'."
    } else { Write-Host "Searching for all objects in the domain." }
    $foundAcls = @()
    try {
        $objectsToScan = Get-ADObject @adObjectParams
        if (-not $objectsToScan) {
            Write-Output "No Active Directory objects found matching the criteria."
            return
        }
        foreach ($obj in $objectsToScan) {
            $ObjectDistinguishedName = $obj.DistinguishedName;
            try {
                $acl = Get-Acl -Path "AD:$ObjectDistinguishedName"
                foreach ($ace in $acl.Access) {
                    if ($ace.AccessControlType -eq $AccessControlType -and
                        ($ace.ActiveDirectoryRights -band $ActiveDirectoryRights) -and # Use -band for bitwise comparison
                        ($ace.ObjectType -eq $WriteAccountRestrictionsPermissionGuid) -and
                        -not $ace.IsInherited -and
                        ($ExcludedSIDs -notcontains $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]))) {
                        $foundAcls += [PSCustomObject]@{
                            'Vulnerable Object' = $ObjectDistinguishedName
                            'Internal Threat'   = $ace.IdentityReference.Value # Get the string representation
                        }
                    }
                }
            }
            catch {
                Write-Warning "Could not retrieve ACL for '$ObjectDistinguishedName': $($_.Exception.Message)"
            }
        }
    }
    catch {
        Write-Error "Failed to retrieve Active Directory objects: $($_.Exception.Message)"
        return
    }
    if ($foundAcls.Count -gt 0) {
        Write-Host "Found $($foundAcls.Count) Active Directory object(s) with explicit 'Write Account Restrictions' permissions (excluding 'Domain Admins')."
        try {
            $foundAcls | Sort-Object -Unique 'Vulnerable Object', 'Internal Threat' | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
            Write-Output "Results exported successfully to '$OutputPath'"
        }
        catch { Write-Error "Failed to export results to CSV file '$OutputPath': $($_.Exception.Message)"}
    } else {  Write-Output "No Active Directory objects found with explicit 'Write Account Restrictions' permissions (excluding 'Domain Admins')."
    }
}
```

**2.** Scan all objects in the domain

```powershell
Find-WriteAccountRestrictions
```

**3.** Using SearchBase to limit the searching Scope

```powershell
Find-WriteAccountRestrictions -SearchBase "CN=Computers,DC=Forestall,DC=Labs"
```

#### .NET Directory Services

By leveraging PowerShell’s built-in .NET DirectoryServices namespace, you can enumerate `WriteAccountRestrictions` entries without relying on any external modules or dependencies.

**1.** Find-WriteAccountRestrictionsSimple function

```powershell
function Find-WriteAccountRestrictionsSimple {
    [CmdletBinding()]
    param([string]$Target,[string]$OutputPath = "WriteAccountRestrictions.csv")
    $guid      = [guid]"4c164200-20c0-11d0-a768-00aa006e0529"
    $allow     = [System.Security.AccessControl.AccessControlType]::Allow
    $writeProp = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
    $excludedSids = @()
    try {
        $root      = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
        $baseDN    = $root.Properties["defaultNamingContext"].Value
        $searchRoot= New-Object System.DirectoryServices.DirectoryEntry("LDAP://$baseDN")
        $ds        = [System.DirectoryServices.DirectorySearcher]::new($searchRoot)
        $ds.Filter = "(&(objectCategory=group)(sAMAccountName=Domain Admins))"
        $ds.PageSize = 1000
        [void]$ds.PropertiesToLoad.Add("objectSid")
        $res = $ds.FindOne()
        if ($res -and $res.Properties["objectsid"] -and $res.Properties["objectsid"][0]) {
            $excludedSids += New-Object System.Security.Principal.SecurityIdentifier($res.Properties["objectsid"][0],0)
        }
    } catch {}
    if ($Target) {
        try {
            $entries = @( New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target") )
        } catch { Write-Error "Failed to bind to '$Target': $_"; return }
    } else {
        try {
            $root      = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
            $baseDN    = $root.Properties["defaultNamingContext"].Value
            $searchRoot= New-Object System.DirectoryServices.DirectoryEntry("LDAP://$baseDN")
            $ds        = [System.DirectoryServices.DirectorySearcher]::new($searchRoot)
            $ds.Filter = "(objectClass=*)"
            $ds.PageSize = 1000
            [void]$ds.PropertiesToLoad.Add("distinguishedName")
            $hits = $ds.FindAll()
            $entries = foreach ($hit in $hits) {
                try { $hit.GetDirectoryEntry() } catch { $null }
            }
        } catch { Write-Error "LDAP enumeration failed: $_"; return }
    }
    $found = foreach ($entry in $entries) {
        if (-not $entry) { continue }
        $dn = $entry.distinguishedName
        try {
            $acl  = $entry.ObjectSecurity
            $aces = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])
        } catch { continue }
        foreach ($ace in $aces) {
            if ($ace.AccessControlType -ne $allow) { continue }
            if (($ace.ActiveDirectoryRights -band $writeProp) -eq 0) { continue }
            if ($ace.ObjectType -ne $guid) { continue }
            if ($ace.IsInherited) { continue }
            $sid = $ace.IdentityReference
            $skip = $false
            foreach ($ex in $excludedSids) { if ($sid -eq $ex) { $skip = $true; break } }
            if ($skip) { continue }
            $who = try { $sid.Translate([System.Security.Principal.NTAccount]).Value } catch { $sid.Value }
            [PSCustomObject]@{
                'Vulnerable Object' = $dn
                'Internal Threat'   = $who
            }
        }
    }
    if ($found) {
        $out = $found | Sort-Object -Unique 'Vulnerable Object','Internal Threat'
        try { $out | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 } catch { Write-Warning "CSV export failed: $_" }
        $out
    } else {  Write-Host "No explicit 'Write Account Restrictions' ACEs found (excluding 'Domain Admins')."}
}

```

**2.** Scan all objects in the domain

```powershell
Find-WriteAccountRestrictionsSimple
```

### Active Directory Users and Computers

**1.** Open `Active Directory Users and Computers` on your Windows server.

**2.** Right-click on the Object name.

**3.** Select Properties from the context menu.

**4.** In the Properties window, navigate to the Security tab.

**5.** Click on the Advanced button to open the Advanced Security Settings dialog.

**6.** In the Advanced Security Settings window, locate and select the relevant Access Control Entry (ACE) for the user or group you wish to configure.

**7.** Click Edit to modify the selected ACE.

**8.** In the permissions list, locate and check the option `Write Account Restrictions`.

**9.** Click OK to save your changes and close the dialogs.

![ADUC](/files/Qxp2QekhBwQA1beMPHez)

## Exploitation

For exploitation information go to [AddAllowedToAct](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/AddAllowedToAct/README.md)

## Mitigation

Access Control Entries identified as dangerous objects should be removed by following the steps below.

**1.** Open `Active Directory Users and Computers`, and activate `Advanced Features` option.

**2.** Double click the affected Object and open `Security` tab.

**3.** In this tab, click `Advanced` button and open the dangerous objects Access Control Entry.

**4.** Remove the `Write Account Restrictions` right.

**5.** Click OK and Apply buttons for saving changes.

![ADUC](/files/Qxp2QekhBwQA1beMPHez)

## Detection

Adding new Access Control Entries on the Active Directory objects changes the `ntSecurityDescriptor` attribute of the objects themselves. These changes can be detected with the 5136 and 4662 Event IDs to identify dangerous objects modifications.

| Event ID | Description                                           | Fields/Attributes               | References                                                                                                                     |
| -------- | ----------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| 5136     | A directory service object was modified.              | ntSecurityDescriptor            | <https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136>                                     |
| 4662     | An operation was performed on an object.              | AccessList, AccessMask          | <https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662>                                     |
| 4724     | An attempt was made to reset an account's password.   | TargetUserName, SubjectUserName | <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4724> |
| 4722     | A user account was enabled.                           | TargetUserName, SubjectUserName | <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4722> |
| 4768     | A Kerberos authentication ticket (TGT) was requested. | TargetUserName                  | <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4768> |
| 4769     | A Kerberos service ticket was requested.              | TargetUserName, ServiceName     | <https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4769> |

## References

* [RBCD (Resource-Based Constrained Delegation) - thehacker.recipes](https://www.thehacker.recipes/ad/movement/kerberos/delegations/rbcd)
* [Wagging the Dog: RBCD (Shenanigans Labs Blog)](https://shenaniganslabs.io/2019/01/28/Wagging-the-Dog.html)
* [RBCD Abuse (Redfox Security Blog)](https://redfoxsec.com/blog/rbcd-resource-based-constrained-delegation-abuse/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestall.io/fsprotect/edges/ad/writeaccountrestrictions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
