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
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
Find-GenericWrite
3. Scan a specific object
Find-GenericWrite -Target "CN=Users,DC=forestall,DC=labs"
4. To exclude default admin ACLs to improve visibility
Find-GenericWrite -ExcludeAdmins
5. Using SearchBase to limit the scope
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
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
Find-GenericWriteSimple
3. Scan a specific object
Find-GenericWriteSimple -Target "CN=Users,DC=forestall,DC=labs"
4. To exclude default admin ACLs to improve visibility
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.

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 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 theWriteSPN 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 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 edge documentation.
Alternatively, you can perform a resource-based constrained delegation attack against the computer. See the AddAllowedToAct 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 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 WriteCertificateTemplates.
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:
Set-ADServiceAccount -Identity MyAppSvc -PrincipalsAllowedToRetrieveManagedPassword adam
Once added, the attacker can read the password. For more abuse details, see the ReadGMSAPassword 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.

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.
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
Last updated
Was this helpful?