# CAN\_EXEC\_PWSH

## Summary

|                            |                 |
| -------------------------- | --------------- |
| **FSProtect ACL Alias**    | CAN\_EXEC\_PWSH |
| **Affected Object Types**  | Users, Groups   |
| **Exploitation Certainty** | Certain         |

## Description

The `CAN_EXEC_PWSH` permission in Active Directory grants an account the ability to execute PowerShell commands and scripts on domain-joined systems. This permission is crucial for administrators who rely on PowerShell’s versatile scripting capabilities to automate tasks, manage configurations, and troubleshoot issues at scale. To assign this permission on a specific system, the user must be added to the local Remote Management Users group, enabling them to initiate and manage remote PowerShell sessions. By leveraging the `CAN_EXEC_PWSH` permission, IT teams can efficiently deploy software, update system settings, and gather detailed diagnostic data across multiple machines in the network.

However, if misconfigured, the `CAN_EXEC_PWSH` permission can create significant security vulnerabilities. An attacker with membership in the Remote Management Users group and corresponding `CAN_EXEC_PWSH` rights can execute arbitrary PowerShell scripts, bypass security controls, and potentially create or modify scheduled tasks—effectively establishing a persistent foothold within the environment. Exploiting this access can lead to unauthorized data exfiltration, privilege escalation, and, ultimately, compromise the security of the entire network.

## Identification

### Powershell

#### Active Directory Module

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

**1.** Find-CanPSRemote function

```powershell
function Find-CanPSRemote {
    [CmdletBinding()]
    param([string[]]$Target      = $null,[string]  $SearchBase  = $null,[string]  $OutputPath  = "CanPSRemote.csv",[int]     $TimeoutSec  = 6)
    Import-Module ActiveDirectory -ErrorAction Stop
    Write-Host "Gathering computer objects from Active Directory..."
    try {
        $computers = @()
        if ($Target) {
            foreach ($t in $Target) {
                try {
                    $comp = Get-ADComputer -Identity $t -ErrorAction Stop | Select-Object -ExpandProperty Name
                    $computers += $comp
                } catch {Write-Warning "Target computer '$t' not found in AD: $($_.Exception.Message)" }
            }
            if ($computers) { Write-Host "Using explicit target computer(s): $($computers -join ', ')"}
        } elseif ($SearchBase) {
            Write-Host "Searching for computers within '$SearchBase'."
            $computers = Get-ADComputer -Filter * -SearchBase $SearchBase -ErrorAction Stop | Select-Object -ExpandProperty Name
        } else {
            Write-Host "Searching for all computers in the domain."
            $computers = Get-ADComputer -Filter * -ErrorAction Stop | Select-Object -ExpandProperty Name
        }
    } catch {
        Write-Error "Failed to retrieve computer objects from Active Directory: $($_.Exception.Message)"
        return
    }
    if (-not $computers) {
        Write-Output "No computer objects found to process."
        return
    }
    $results = New-Object System.Collections.Generic.List[object]
    Write-Host "Enumerating local 'Remote Management Users' on $($computers.Count) computer(s)..."
    foreach ($c in ($computers | Select-Object -Unique)) {
        try {
            $opt  = New-CimSessionOption -Protocol Dcom
            $sess = New-CimSession -ComputerName $c -SessionOption $opt -OperationTimeoutSec $TimeoutSec -ErrorAction Stop
            try {
                # Note: don't filter on LocalAccount=TRUE to also catch DCs (no local SAM)
                $grp = Get-CimInstance -CimSession $sess -ClassName Win32_Group -Filter "Name='Remote Management Users'" -ErrorAction Stop
                if (-not $grp) {
                    Write-Warning "'Remote Management Users' group not found on '$c'."
                    continue
                }
                $members = Get-CimAssociatedInstance -CimSession $sess -InputObject $grp -Association Win32_GroupUser -ErrorAction Stop
                foreach ($m in $members) {
                    $memberType = if ($m.CimClass.CimClassName -eq 'Win32_Group') { 'Group' } else { 'User' }
                    $memberName = if ($m.Domain) { "$($m.Domain)\$($m.Name)" } else { $m.Name }
                    $results.Add([PSCustomObject]@{
                        ComputerName = $c
                        MemberName   = $memberName
                        MemberType   = $memberType
                    })
                }
            } finally {
                if ($sess) { $sess | Remove-CimSession -ErrorAction SilentlyContinue }
            }
        } catch { Write-Warning "Unable to enumerate 'Remote Management Users' on '$c': $($_.Exception.Message)"}
    }
    if ($results.Count -gt 0) {
        try {
            $results |
                Select-Object ComputerName, MemberName, MemberType |
                Sort-Object ComputerName, MemberType, MemberName |
                Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop
            Write-Output "Results exported to '$OutputPath'"
        } catch {  Write-Error "Failed to export results to CSV: $($_.Exception.Message)" }
    } else { Write-Output "No 'Remote Management Users' members found across the scanned computers."  }
}

```

**2.** Scan all Computers in the domain

```powershell
Find-CanPSRemote
```

**3.** Scan a specific computer object

```powershell
Find-CanPSRemote -Target SDCA01
```

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

```powershell
Find-CanPSRemote -SearchBase "CN=Computers,DC=forestall,DC=labs"
```

#### .NET Directory Services

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

**1.** Find-CanPSRemoteSimple function

```powershell
function Find-CanPSRemoteSimple {
    [CmdletBinding()]
    param( [string]$Target = $null,  [string]$SearchBase = $null,[string]$OutputPath = "CanPSRemote.csv",  [int]$TimeoutSec = 6)
    $computers = @()
    try {
        if ($Target) {
            try {
                # Allow DN or a plain computer name/FQDN
                if ($Target -match '(^CN=|^OU=|^DC=)') {
                    $entry = [ADSI]"LDAP://$Target"
                    $name  = $entry.Properties['dNSHostName'].Value
                    if ([string]::IsNullOrWhiteSpace($name)) { $name = $entry.Properties['name'].Value }
                } else {
                    $name = $Target
                }
                if ($name) { $computers = @($name) } else { Write-Error "Could not resolve hostname from '$Target'"; return }
            } catch { Write-Error "Failed to bind to '$Target': $_"; return }
        }
        else {
            try {
                $root   = [ADSI]"LDAP://RootDSE"
                $baseDN = if ($SearchBase) { $SearchBase } else { $root.defaultNamingContext }
                $searchRoot = [ADSI]"LDAP://$baseDN"
                $ds = New-Object System.DirectoryServices.DirectorySearcher($searchRoot)
                $ds.Filter = "(objectCategory=computer)"
                $ds.PageSize = 1000
                [void]$ds.PropertiesToLoad.Add("dNSHostName")
                [void]$ds.PropertiesToLoad.Add("name")
                $res = $ds.FindAll()
                foreach ($r in $res) {
                    $n = $null
                    if ($r.Properties.Contains('dnshostname') -and $r.Properties['dnshostname'].Count -gt 0) {
                        $n = [string]$r.Properties['dnshostname'][0]
                    } elseif ($r.Properties.Contains('name') -and $r.Properties['name'].Count -gt 0) {
                        $n = [string]$r.Properties['name'][0]
                    }
                    if ($n) { $computers += $n }
                }
                $res.Dispose()
            } catch { Write-Error "LDAP enumeration failed: $_"; return }
        }
    } catch { Write-Error "Failed to build computer list: $_"; return }
    if (-not $computers) { Write-Output "No computer objects found to process."; return }
    $results = New-Object System.Collections.Generic.List[object]
    $opt  = New-CimSessionOption -Protocol Dcom
    foreach ($c in ($computers | Sort-Object -Unique)) {
        try {
            $sess = New-CimSession -ComputerName $c -SessionOption $opt -OperationTimeoutSec $TimeoutSec -ErrorAction Stop
            try {
                $grp = Get-CimInstance -CimSession $sess -ClassName Win32_Group -Filter "LocalAccount=True AND Name='Remote Management Users'" -ErrorAction Stop
                if (-not $grp) { continue }
                $members = Get-CimAssociatedInstance -CimSession $sess -InputObject $grp -Association Win32_GroupUser -ErrorAction Stop
                foreach ($m in $members) {
                    $memberType = if ($m.CimClass.CimClassName -eq 'Win32_Group') { 'Group' } else { 'User' }
                    $memberName = if ($m.Domain) { "$($m.Domain)\$($m.Name)" } else { $m.Name }
                    $results.Add([PSCustomObject]@{
                        ComputerName = $c
                        MemberName   = $memberName
                        MemberType   = $memberType
                    })
                }
            } finally {
                if ($sess) { $sess | Remove-CimSession -ErrorAction SilentlyContinue }
            }
        } catch {
            continue
        }
    }
    if ($results.Count -gt 0) {
        try {
            $results |
                Select-Object ComputerName, MemberName, MemberType |
                Sort-Object ComputerName, MemberType, MemberName |
                Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -ErrorAction Stop
            Write-Output "Results exported to '$OutputPath'"
        } catch { Write-Error "Failed to export results to CSV: $($_.Exception.Message)" }
    } else { Write-Output "No 'Remote Management Users' members found across the scanned computers." }
}
```

**2.** Scan all computers in the domain

```powershell
Find-CanPSRemoteSimple
```

**3.** Scan a specific computer

```powershell
Find-CanPSRemoteSimple -Target "CN=fssql,CN=Computers,DC=Forestall,DC=Labs"
```

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

```powershell
Find-CanPSRemoteSimple -SearchBase "CN=Computers,DC=forestall,DC=labs"
```

### Computer Management

**Note**: This edge cannot be identified with `ADUC` directly but can be from `Computer Management`.

**1.** Open Computer Management.

**2.** Select Action from the menu, then Connect to Another Computer if another computer is required.

**3.** Select the desired machine to manage.

**4.** In the Computer Management window, navigate to the `Local Users and Groups` section.

**5.** In the `Local Users and Groups`, double-click and open `Remote Management Users`.

**6.** In the Members list, locate users and groups.

**7.** Click OK to close the dialogs.

![Computer Management](/files/48Jp5IDmrx3DqsB5RpIn)

## Exploitation

### Windows

```powershell
$pass = ConvertTo-SecureString -AsPlainText -Force '<pass>'
$creds = New-Object System.Management.Automation.PSCredential('<domain>\<user>',$pass);
Enter-PSSession -ComputerName <hostname> -Credential $creds
```

Example:

```powershell
$pass = ConvertTo-SecureString -AsPlainText -Force 'Temp123!'
$creds = New-Object System.Management.Automation.PSCredential('Forestall\adam',$pass);
Enter-PSSession -ComputerName vm01.forestall.labs -Credential $creds
```

![Powershell Remote Session](/files/YMIwWhCcTA9rRqXmFJYC)

### Linux

By using `Evil-winrm`

```bash
evil-winrm -i <ip or host> -u '<user>' -p '<pass>'
```

Example:

```bash
evil-winrm -i 192.168.121.110 -u adam -p 'Temp123!'
```

![Powershell Remote Session Evil-winrm](/files/PUXc0cCWM0iqoonbjU63)

## Mitigation

You can mitigate `CAN_EXEC_PWSH` with the following steps:

**1.** Open `Active Directory Users and Computers`.

**2.** Right-click on the computer.

**3.** Select `Manage` from the context menu.

**4.** In the Computer Management window, navigate to the `Local Users and Groups` section.

**5.** In the `Local Users and Groups`, double-click and open `Remote Management Users`.

**6.** In the Members list, locate and remove the unwanted user or group by clicking the entity and then the remove button.

**7.** Click OK to close the dialogs.

![Active Directory Users and Computers](/files/48Jp5IDmrx3DqsB5RpIn)

## Detection

Adding new Access Control Entries on 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. | 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

* [FSProtect Documentation - LocalGroup](https://docs.forestall.io/fsprotect/search-and-reports/localgroup)
* [FSProtect Documentation - LocalUser](https://docs.forestall.io/fsprotect/search-and-reports/localuser)
* [Enter-PSSession - PowerShell Module](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/enter-pssession?view=powershell-7.5)
* [Remote Management Users Group - Microsoft Learn](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-groups#remote-management-users)
* [Evil-WinRM - GitHub](https://github.com/Hackplayers/evil-winrm)


---

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