# ManageGPLink

## Summary

|                            |                                      |
| -------------------------- | ------------------------------------ |
| **FSProtect ACL Alias**    | ManageGPLink                         |
| **AD Alias**               | Write gPLink                         |
| **Affected Object Types**  | OU                                   |
| **Exploitation Certainty** | Likely                               |
| **Attribute**              | GP-Link                              |
| **Attribute Guid**         | f30e3bbe-9ff0-11d1-b603-0000f80367c1 |
| **AD Right**               | WriteProperty                        |

## Description

The `ManageGPLink` permission in Active Directory allows an account to link or unlink `Group Policy Objects` (GPOs) to an `Organizational Unit` (OU), domain, or site. This permission is crucial for administrators to manage the distribution and application of policies across different segments of the network, ensuring that users and computers receive the appropriate configurations and security settings.

However, if misconfigured, the `ManageGPLink` permission can introduce significant security vulnerabilities. An attacker with this permission can link a malicious GPO to an OU, thereby affecting all users and computers within that OU. This could enable the attacker to execute arbitrary code, deploy malware, or alter security settings across multiple systems. Exploiting this vulnerability could lead to unauthorized access, privilege escalation, and widespread compromise of network resources.

## Identification

### PowerShell

#### Active Directory Module

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

**1.** Find-ManageGPLink function

```powershell
function Find-ManageGPLink {
    [CmdletBinding()]
    param(
        [Parameter(
            Position             = 0,
            ValueFromPipeline    = $true,
            ValueFromPipelineByPropertyName = $true
        )] [string[]]$Target,  [Parameter(Position = 1)][string]$OutputPath = "ManageGPLink.csv")
    begin {
        Import-Module ActiveDirectory -ErrorAction Stop
        $allowType  = [System.Security.AccessControl.AccessControlType]::Allow
        $writeRight = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
        $gpLinkGuid = [Guid]"f30e3bbe-9ff0-11d1-b603-0000f80367c1"
        $results = [System.Collections.Generic.List[PSObject]]::new()
    }
    process {
        if ($Target) {
            try {$ous = $Target | ForEach-Object { Get-ADOrganizationalUnit -Identity $_ -ErrorAction Stop }
            }
            catch { Write-Warning "Failed to resolve one or more OUs: $_" ;return}
        }
        else { $ous = Get-ADOrganizationalUnit -Filter * }
        foreach ($ou in $ous) {
            $ouDN = $ou.DistinguishedName
            (Get-Acl -Path "AD:$ouDN").Access |
                Where-Object {
                    $_.AccessControlType     -eq $allowType                 -and
                    ($_.ActiveDirectoryRights -band $writeRight) -eq $writeRight -and
                    $_.ObjectType            -eq $gpLinkGuid               -and
                    -not $_.IsInherited
                } |
                ForEach-Object {
                    $obj = [PSCustomObject]@{
                        VulnerableOU = $ouDN
                        Account      = $_.IdentityReference.Value
                        Rights       = $_.ActiveDirectoryRights
                    }
                    $results.Add($obj)
                }
        }
    }
    end {
        try {
            $results |  Export-Csv -Path $OutputPath -NoTypeInformation -Force
            Write-Host "Results exported to $OutputPath"
        }
        catch { Write-Warning "Failed to write CSV to '$OutputPath': $_"}
    }
}
```

**2.** Scan all domain OUs.

```powershell
Find-ManageGPLink
```

**3.** Scan a specific OU

```powershell
Find-ManageGPLink -Target "OU=Workstations,DC=forestall,DC=labs"
```

#### .NET Directory Services

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

**1.** Find-ManageGPLink function

```powershell
function Find-ManageGPLinkSimple {
    [CmdletBinding()]
    param([string]$Target)
    $allowType  = [System.Security.AccessControl.AccessControlType]::Allow
    $writeRight = [System.DirectoryServices.ActiveDirectoryRights]::WriteProperty
    $gpLinkGuid = [Guid]"f30e3bbe-9ff0-11d1-b603-0000f80367c1"
    if ($Target) {
        Write-Verbose "Binding directly to object: $Target"
        try { $entries = @( New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target") )}
        catch {Write-Error "Failed to bind to '$Target': $_" ; return }
    }
    else {
        Write-Verbose "Binding to local RootDSE..."
        try {
            $root       = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
            $baseDN     = $root.Properties["defaultNamingContext"].Value
            $ldapPath   = "LDAP://$baseDN"
            $searchRoot = New-Object System.DirectoryServices.DirectoryEntry($ldapPath)
            Write-Verbose "Searching for all OUs under $ldapPath..."
            $searcher = [System.DirectoryServices.DirectorySearcher]::new($searchRoot)
            $searcher.Filter           = "(objectCategory=organizationalUnit)"
            $searcher.PageSize         = 1000
            [void]$searcher.PropertiesToLoad.Add("distinguishedName")
            $hits = $searcher.FindAll()
            Write-Verbose "Found $($hits.Count) OU(s)."
        }
        catch {
            Write-Error "LDAP enumeration failed: $_" ; return
        }
        $entries = foreach ($hit in $hits) {
            try { $hit.GetDirectoryEntry() }
            catch { Write-Warning "Could not bind entry: $_"; continue }
        }
    }
    $found = foreach ($entry in $entries) {
        $dn = $entry.distinguishedName
        Write-Verbose "Checking ACLs on: $dn"
        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       -eq $allowType                              -and
                ($ace.ActiveDirectoryRights -band $writeRight) -eq $writeRight             -and
                $ace.ObjectType              -eq $gpLinkGuid                              -and
                -not $ace.IsInherited
            ) {
                $who = try {
                    $ace.IdentityReference.Translate([System.Security.Principal.NTAccount]).Value
                } catch {
                    $ace.IdentityReference.Value
                }
                [PSCustomObject]@{
                    OU      = $dn.ToString();
                    Account = $who
                    Rights  = $ace.ActiveDirectoryRights
                }
            }
        }
    }
    if ($found) {
        $found | Export-Csv -Path ".\ManageGPLinkAcls.csv" -NoTypeInformation -Encoding UTF8
        Write-Host "Exported $($found.Count) entr$(if($found.Count -eq 1){'y'}else{'ies'}) to ManageGPLinkAcls.csv"
    }
    else {Write-Host "No 'gplink' ACLs found."}
}
```

**2.** Scan all domain OUs.

```powershell
Find-ManageGPLinkSimple 
```

**3.** Scan a specific OU

```powershell
Find-ManageGPLinkSimple -Target "OU=Workstations,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 OU 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 gPLink`.

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

![ADUC](/files/ecCmU5SEGfHdXSm74GYa)

## Exploitation

This edge is exploitable only if you already control an existing Group Policy Object or have the privileges to create a new one.

With the WriteProperty right on the OU’s gPLink attribute, an attacker can inject or modify the list of GPOs linked to that OU. Once a malicious GPO is linked, every computer and user within the OU will process its settings at the next Group Policy refresh. For a step‑by‑step exploit walkthrough and PowerShell examples, see the [GPWrite](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/GPOWrite/README.md) edge.

### Windows

An attacker can create a new or modify `gPLink` with these cmdlets on Windows. (You should import the `GroupPolicy` module by running [GroupPolicy](https://learn.microsoft.com/en-us/powershell/module/grouppolicy/new-gplink?view=windowsserver2022-ps))

To create a new GPO:

```powershell
New-GPLink -Name "<GPO to be added>" -Target "<Vulnerable OUs distinguishedName>"
```

Example:

```powershell
New-GPLink -Name "Admins_GPO" -Target "OU=Hackers,DC=forestall,DC=labs"
```

To update a GPO link:

```powershell
Set-GPLink -Name "<GPO to be added>" -Target "<Vulnerable OUs distinguishedName>"
```

Example:

```powershell
Set-GPLink -Name "Admins_GPO" -Target "OU=Hackers,DC=forestall,DC=labs"
```

After adding a GPLink, attackers can control objects in the OU. For further exploitation details, please refer to [GPWrite](https://gitlab.com/forestall/fsprotect-knowledge-base/-/blob/main/edges/GPOWrite/README.md).

![Set Link using powershell](/files/b2TMiXb0K9ofO56QNuN9)

### Linux

Open a PowerShell session and run PowerShell commands on Linux systems with this cmdlet (Impacket tools should be installed before running the command):

```bash
impacket-psexec '<domain>/<user login name>:<password>@<IP Address>'
```

Example:

```bash
impacket-psexec 'FORESTALL/ANGEL_ROSA:Test123.!@192.168.231.21'
```

![Execute using PsExec](/files/3zpbzcbyhOCnlvGLWpxi)

## Mitigation

Dangerous Access Control Entries should be removed by following the steps below.

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

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

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

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

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

![ADUC](/files/ecCmU5SEGfHdXSm74GYa)

## 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. | AttributeLDAPDisplayName: gPLink, ObjectDN (the OU), New/Old Value | <https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-5136> |
| 4662     | An operation was performed on an object. | AccessMask / Properties indicating write to gPLink                 | <https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4662> |

## References

[New-GPLink (Microsoft Docs)](https://learn.microsoft.com/en-us/powershell/module/grouppolicy/new-gplink?view=windowsserver2022-ps)

[OUnedPy: Exploiting Hidden Organizational Units ACL Attack Vectors in Active Directory (Synacktiv)](https://www.synacktiv.com/publications/ounedpy-exploiting-hidden-organizational-units-acl-attack-vectors-in-active-directory)


---

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