# GenericWrite

## Summary

|                            |                                      |
| -------------------------- | ------------------------------------ |
| **FSProtect ACL Alias**    | GenericWrite                         |
| **AD Alias**               | Write                                |
| **Affected Object Types**  | Objects                              |
| **Exploitation Certainty** | Certain                              |
| **AD Right**               | GenericWrite                         |
| **AD Permission Guid**     | 00000000-0000-0000-0000-000000000000 |

## Description

The `GenericWrite` permission in Active Directory allows a user to modify certain attributes of an object, such as user accounts, groups, or computer objects. This permission is versatile and facilitates various administrative tasks, enabling administrators to update object properties without granting full control over the objects. Proper configuration of `GenericWrite` ensures that administrators can efficiently manage directory objects while maintaining security.

However, if misconfigured, the `GenericWrite` permission may allow an attacker to alter critical attributes, such as modifying group memberships, resetting passwords, or modifying security settings. For example, an attacker could add themselves to privileged groups like Domain Admins and weaken security configurations, enabling further exploitation. Proper assignment, monitoring, and auditing of the `GenericWrite` permission are essential to maintain directory security.

## Identification

### PowerShell

#### Active Directory Module

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

**1.** Find-GenericWrite function

```powershell
function Find-GenericWrite {
    [CmdletBinding()]
    param ([string]$Target = $null,[string]$SearchBase = $null,[string]$OutputPath = "GenericWrite.csv",[switch]$ExcludeAdmins = $false)
    Import-Module ActiveDirectory
    Write-Host "Gathering Active Directory objects and inspecting ACLs for explicit GenericWrite or broad WriteProperty permissions..."
    $ExcludedSIDs = @()
    if ($ExcludeAdmins) {
        Write-Host "Excluding default administrative groups and built-in accounts."
        $ExcludedSIDs += (New-Object System.Security.Principal.NTAccount "NT AUTHORITY\SYSTEM").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "NT AUTHORITY\SELF").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "BUILTIN\Account Operators").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "BUILTIN\Administrators").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "BUILTIN\Terminal Server License Servers").Translate([System.Security.Principal.SecurityIdentifier]),(New-Object System.Security.Principal.NTAccount "NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS").Translate([System.Security.Principal.SecurityIdentifier]) 
        $ExcludedSIDs += [System.Security.Principal.SecurityIdentifier]::new("S-1-3-0");
        try {$ExcludedSIDs += (Get-ADGroup -Identity "Domain Admins").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Enterprise Admins").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Schema Admins").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Cert Publishers").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Group Policy Creator Owners").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Domain Controllers").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Key Admins").SID;$ExcludedSIDs += (Get-ADGroup -Identity "Enterprise Key Admins").SID;$ExcludedSIDs += (Get-ADGroup -Identity "DnsAdmins").SID;$ExcludedSIDs += (Get-ADGroup -Identity "RAS and IAS Servers").SID}
        catch {Write-Warning "Could not resolve one or more default domain admin groups. They might not be filtered from results."}}
    $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow;$GenericWriteRight = [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite;$WritePropertyRight = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
    $foundAcls = @();$objectsToScan = @()
    try {
        if ($Target) {
            Write-Host "Searching for permissions on specific object: '$Target'."
            $specificObject = Get-ADObject -Identity $Target -Properties nTSecurityDescriptor -ErrorAction Stop
            if ($specificObject) {$objectsToScan += $specificObject} else {Write-Output "Object '$Target' not found.";return}
        } else {
            $adObjectParams = @{Filter    = "*"
                Properties = "nTSecurityDescriptor"
                ErrorAction = "Stop"}
            if ($SearchBase) {
                $adObjectParams.Add("SearchBase", $SearchBase)
                Write-Host "Searching for all objects within '$SearchBase'."
            } else {$adObjectParams.Add("SearchBase", (Get-ADRootDSE).DefaultNamingContext);Write-Host "Searching for all objects in the domain."}
            $objectsToScan = Get-ADObject @adObjectParams
        }
        if (-not $objectsToScan) {Write-Output "No 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) {
                $isExcluded = $false
                    if ($ExcludeAdmins) {
                        try {if ($ExcludedSIDs -contains $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])) {$isExcluded = $true}}
                        catch {Write-Warning "Could not translate SID for exclusion check: $($ace.IdentityReference.Value). Error: $($_.Exception.Message)"}}
                    if ($ace.AccessControlType -eq $AccessControlType -and-not $ace.IsInherited -and  -not $isExcluded) {
                        if (($ace.ActiveDirectoryRights -eq $GenericWriteRight) -or
                            (($ace.ActiveDirectoryRights -band $WritePropertyRight) -and ([Guid]::Empty -eq $ace.ObjectType))) { # $null -eq $ace.ObjectType checks if no specific property GUID is set
                            $foundAcls += [PSCustomObject]@{
                                'Vulnerable Object' = $ObjectDistinguishedName
                                'Permission Type'   = if ($ace.ActiveDirectoryRights -eq $GenericWriteRight) { 'GenericWrite' } else { 'Broad WriteProperty' }
                                'Permission Holder' = $ace.IdentityReference.Value # Get the string representation of IdentityReference
}}}}}
            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) {
        $exclusionMessage = if ($ExcludeAdmins) { " (excluding default admin groups and built-in accounts)" } else { "" }
        Write-Host "Found $($foundAcls.Count) object(s) with explicit GenericWrite or broad WriteProperty permissions$exclusionMessage."
        try {
            $foundAcls | Sort-Object -Unique 'Vulnerable Object', 'Permission Holder' | 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 {
        $exclusionMessage = if ($ExcludeAdmins) { " (excluding default admin groups and built-in accounts)" } else { "" }
        Write-Output "No Active Directory objects found with explicit GenericWrite or broad WriteProperty permissions$exclusionMessage." }}
```

**2.** Scan all domain objects

```powershell
Find-GenericWrite
```

**3.** Scan a specific object

```powershell
Find-GenericWrite -Target "CN=Users,DC=forestall,DC=labs"
```

**4.** To exclude default admin ACLs to improve visibility

```powershell
Find-GenericWrite -ExcludeAdmins
```

**5.** Using SearchBase to limit the scope

```powershell
Find-GenericWrite -SearchBase 'CN=Users,Dc=Forestall,DC=Labs' 
```

#### .NET Directory Services

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

**1.** Find-GenericWriteSimple function

```powershell
function Find-GenericWriteSimple {
    [CmdletBinding()]
    param ([string]$Target = $null,[string]$SearchBase   = $null,[string]$OutputPath   = "GenericWrite.csv",[switch]$ExcludeAdmins)
    $GenericWriteRight = [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite
    $WritePropertyRight = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
    $Allow = [System.Security.AccessControl.AccessControlType]::Allow
    try {$baseDN = if ($SearchBase) { $SearchBase } else { ([ADSI]"LDAP://RootDSE").defaultNamingContext }} catch { Write-Error "Failed to read RootDSE: $_";return}
    $ExcludedSidStrings = @()
    if ($ExcludeAdmins) {
        $wellKnown = @("NT AUTHORITY\SYSTEM","NT AUTHORITY\SELF","BUILTIN\Account Operators","BUILTIN\Administrators","NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS","CREATOR OWNER","BUILTIN\Terminal Server License Servers")
        foreach ($acct in $wellKnown) { try {$sid = (New-Object System.Security.Principal.NTAccount($acct)).Translate([System.Security.Principal.SecurityIdentifier]);$ExcludedSidStrings += $sid.Value} catch {}}
        foreach ($name in @(
            "Domain Admins","Enterprise Admins","Schema Admins","Cert Publishers","Domain Controllers","Key Admins","Enterprise Key Admins","DnsAdmins", "RAS and IAS Servers","Group Policy Creator Owners"
        )) {
            try {$grp = [ADSI]"LDAP://CN=$name,CN=Users,$baseDN";$bytes = $grp.Properties["objectSid"][0];if ($bytes) {$sid = New-Object System.Security.Principal.SecurityIdentifier($bytes,0);$ExcludedSidStrings += $sid.Value}} catch {}
        } $ExcludedSidStrings = $ExcludedSidStrings | Sort-Object -Unique }
    function _Get-DN {
        param($searchResult, $entry)
        $dn = $null
        if ($searchResult -and $searchResult.Properties.Contains("distinguishedname")) {$dn = $searchResult.Properties["distinguishedname"] | Select-Object -First 1 }
        if (-not $dn -and $entry) {try { $dn = $entry.Properties["distinguishedName"] | Select-Object -First 1 } catch {}}
        return $dn}
    $rows = New-Object System.Collections.Generic.List[object]
    if ($Target) {
        try {$entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target");$null = $entry.RefreshCache(@("ntSecurityDescriptor","distinguishedName"))} catch {Write-Error "Failed to bind to target '$Target' : $($_.Exception.Message)";return}
        $dn = $entry.Properties["distinguishedName"] | Select-Object -First 1
        if (-not $dn) { $dn = $Target }
        try {$aces = $entry.ObjectSecurity.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])} catch {Write-Warning "Failed to read ACL for $dn : $($_.Exception.Message)";return}
        foreach ($ace in $aces) {
            if ($ace.AccessControlType -ne $Allow -or $ace.IsInherited) { continue }
            $hasGenericWrite   = ($ace.ActiveDirectoryRights -band $GenericWriteRight)
            $hasWriteProperty  = ($ace.ActiveDirectoryRights -band $WritePropertyRight) -and ([Guid]::Empty -eq $ace.ObjectType) # Broad WriteProperty only
            if (-not ($hasGenericWrite -or $hasWriteProperty)) { continue }
            $sidValue = $ace.IdentityReference.Value
            if ($ExcludeAdmins -and $ExcludedSidStrings -contains $sidValue) { continue }
            $who = $sidValue
            try { $who = $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value } catch {}
            $rows.Add([PSCustomObject]@{
                'Vulnerable Object' = $dn
                'Permission Type'   = if ($hasGenericWrite) { 'GenericWrite' } else { 'WriteProperty' }
                'Permission Holder' = $who})}}else {
        try {
            $root = [ADSI]"LDAP://$baseDN";$srch = New-Object System.DirectoryServices.DirectorySearcher($root)
            $srch.Filter = "(objectClass=*)";$srch.PageSize = 1000
            $srch.SearchScope = [System.DirectoryServices.SearchScope]::Subtree;$srch.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
            foreach ($p in @("distinguishedName","ntSecurityDescriptor")) { [void]$srch.PropertiesToLoad.Add($p) }
        } catch {Write-Error "LDAP search setup failed: $($_.Exception.Message)";return}
        try {$results = $srch.FindAll()} catch {Write-Error "LDAP search failed: $($_.Exception.Message)";return}
        foreach ($res in $results) {
            $entry = $null
            try { $entry = $res.GetDirectoryEntry() } catch { continue }
            $dn = _Get-DN -searchResult $res -entry $entry
            if (-not $dn) { continue }
            try {$aces = $entry.ObjectSecurity.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])} catch {continue}
            foreach ($ace in $aces) {
                if ($ace.AccessControlType -ne $Allow -or $ace.IsInherited) { continue }
                $hasGenericWrite   = ($ace.ActiveDirectoryRights -eq $GenericWriteRight)
                $hasWriteProperty  = ($ace.ActiveDirectoryRights -band $WritePropertyRight) -and ([Guid]::Empty -eq $ace.ObjectType) # Broad WriteProperty only
                if (-not ($hasGenericWrite -or $hasWriteProperty)) { continue }
                $sidValue = $ace.IdentityReference.Value
                if ($ExcludeAdmins -and $ExcludedSidStrings -contains $sidValue) { continue }
                $who = $sidValue
                try { $who = $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value } catch {}
                $rows.Add([PSCustomObject]@{
                    'Vulnerable Object' = $dn
                    'Permission Type'   = if ($hasGenericWrite) { 'GenericWrite' } else { 'WriteProperty' }
                    'Permission Holder' = $who})}}}
if ($rows.Count -gt 0) {$rows = $rows | Sort-Object 'Vulnerable Object','Permission Type','Permission Holder' -Unique;$rows | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8;Write-Host "Exported $($rows.Count) entr$(if ($rows.Count -eq 1){'y'}else{'ies'}) to $OutputPath"} else {Write-Host "No objects found with explicit GenericWrite or WriteProperty permissions."}}
```

**2.** Scan all domain objects

```powershell
Find-GenericWriteSimple
```

**3.** Scan a specific object

```powershell
Find-GenericWriteSimple -Target "CN=Users,DC=forestall,DC=labs"
```

**4.** To exclude default admin ACLs to improve visibility

```powershell
Find-GenericWriteSimple -ExcludeAdmin
```

### Active Directory Users and Computers

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

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

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

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

**5.** Locate and select the relevant Access Control Entry (ACE) for the user or group you wish to configure.

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

**7.** In the permissions list, locate and check the option `Write`.

**8.** Click Apply to save your changes and close the dialogs.

![ADUC](/files/fgJTKqaN9BxtP8Q0JrlH)

## Exploitation

### Users

When an attacker possesses GenericWrite permissions over a user object in Active Directory, they can exploit a vulnerability by writing to the `msds-KeyCredentialLink` attribute.

This action allows the attacker to create "Shadow Credentials" for that user object. Shadow Credentials enable an attacker to authenticate as the compromised principal using Kerberos Public Key Cryptography for Initial Authentication (PKINIT).

For more detailed information on this attack vector, refer to the [AddKeyCredentialLink](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/AddKeyCredentialLink/README.md) edge documentation in Active Directory security assessments.

Alternatively, with appropriate write permissions, you can modify the `servicePrincipalNames (SPN)` attribute of a user object. This modification can then facilitate a targeted Kerberoasting attack against the account. For more details on the abuse scenarios related to this, see the[WriteSPN](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/writeSPN/README.md) edge documentation.

### Groups

If you have GenericWrite permissions on a group, you can add yourself or any other controlled principal to that group. Further details on this form of abuse can be found under the [AddMember](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/AddMember/README.md) edge documentation.

### Computers

When GenericWrite permissions are held over a computer object, an attacker can manipulate the msds-KeyCredentialLink attribute. This manipulation enables the creation of "Shadow Credentials" on the computer object, allowing an adversary to authenticate as that computer principal via Kerberos PKINIT. For further details on this attack technique, refer to the [AddKeyCredentialLink](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/AddKeyCredentialLink/README.md) edge documentation.

Alternatively, you can perform a resource-based constrained delegation attack against the computer. See the [AddAllowedToAct](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/AddAllowedToAct/README.md) edge abuse information for more information about that attack.

### GPO

When GenericWrite permissions are held over a GPO it's possible to take full control of a GPO to modify its settings, which will then apply to its targeted users and computers. Select the target object, open the gpedit GUI, and inject a malicious policy—such as an immediate scheduled task with item‑level targeting—into the GPO. Wait at least two hours for the Group Policy Client to retrieve and execute the new policy. See the [GPOWrite](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/GPOWrite/README.md) edge documentation for abuse details.

### Certificate Template

When an attacker has **GenericWrite** permissions over a Certificate Template, they can modify its settings to introduce the **ESC1** vulnerability.\
This misconfiguration can then be exploited to escalate privileges and ultimately gain **Domain Admin** rights.

For more details, see [WriteOverCertificateTemplate](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/WriteOverCertificateTemplate/README.md).

### Delegated Managed Service Account

If an attacker has **GenericWrite** permissions over a dMSA object, they can modify the **PrincipalsAllowedToRetrieveManagedPassword** property to include their own account. This grants the ability to retrieve the managed service account’s password.

Example:

```powershell
Set-ADServiceAccount -Identity MyAppSvc -PrincipalsAllowedToRetrieveManagedPassword adam
```

Once added, the attacker can read the password. For more abuse details, see the [ReadGMSAPassword](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/ReadGMSAPassword/README.md) edge.

## Mitigation

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

**1.** Open `Active Directory Users and Computers`.

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

**3.** In this tab, click the dangerous Access Control Entry.

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

**5.** Click OK and Apply to save changes.

![ADUC](/files/fgJTKqaN9BxtP8Q0JrlH)

## Detection

Adding new Access Control Entries to 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 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> |

## References

* [Generic Access Rights (Microsoft Docs)](https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights)
* [Abusing AD DACL GenericWrite (Hacking Articles)](https://www.hackingarticles.in/abusing-ad-dacl-genericwrite/)
* [Pwning the Domain: DACL Abuse (Hadess.io)](https://hadess.io/pwning-the-domain-dacl-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/genericwrite.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.
