CAN_RDP

Summary

FSProtect ACL Alias

CAN_RDP

Affected Object Types

Users, Groups

Exploitation Certainty

Certain

Description

The CAN_RDP permission in Active Directory grants an account the ability to initiate and manage Remote Desktop Protocol (RDP) sessions on designated systems. This permission is useful for system administrators because it enables remote troubleshooting, maintenance, and management of servers and workstations, providing efficient access to critical infrastructure from remote locations. To use the CAN_RDP permission, the user must be added to the local "Remote Desktop Users" group on the target system, which allows them to establish RDP connections.

However, if misconfigured, the CAN_RDP permission can introduce significant security risks. An attacker who gains or abuses this permission could use RDP to bypass local access controls, execute arbitrary code, or exploit known vulnerabilities in the RDP service. Such exploitation might allow unauthorized remote access, facilitate lateral movement across the network, and lead to privilege escalation — potentially compromising sensitive data and critical systems.

Identification

Powershell

Active Directory Module

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

1. Find-CAN_RDP function

function Find-CAN_RDP {
    [CmdletBinding()]
    param( [string[]]$Target      = $null,  [string]  $SearchBase  = $null,  [string]  $OutputPath  = "CAN_RDP.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 {
                    $computers += (Get-ADComputer -Identity $t -ErrorAction Stop | Select-Object -ExpandProperty Name)
                } 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
    }
    $computers = $computers | Select-Object -Unique
    Write-Host "Enumerating local 'Remote Desktop Users' on $($computers.Count) computer(s)..."
    $results = New-Object System.Collections.Generic.List[object]
    foreach ($c in $computers) {
        try {
            $opt  = New-CimSessionOption -Protocol Dcom
            $sess = New-CimSession -ComputerName $c -SessionOption $opt -OperationTimeoutSec $TimeoutSec -ErrorAction Stop
            try {
                # Don't use LocalAccount=TRUE to avoid excluding DCs (no local SAM)
                $grp = Get-CimInstance -CimSession $sess -ClassName Win32_Group -Filter "Name='Remote Desktop Users'" -ErrorAction Stop
                if (-not $grp) {
                    Write-Warning "'Remote Desktop 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 Desktop 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 Desktop Users' members found across the scanned computers."}
}

2. Scan all computers in the domain

Find-CAN_RDP

3. Scan a specific computer object

Find-CAN_RDP -Target FSWS01

4. Using SearchBase to limit the Scope

Find-CAN_RDP -SearchBase "CN=Computers,DC=forestall,DC=labs"

.NET Directory Services

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

1. Find-CAN_RDPSimple function

function Find-CAN_RDPSimple {
    [CmdletBinding()]
    param( [string]$Target,[string]$SearchBase,[string]$OutputPath = "CAN_RDP.csv",  [int]   $TimeoutSec = 6 )
    Write-Verbose "Building list of computers via LDAP (no AD module)..."
    try {
        $entries = @()
        if ($Target) {
            try {
                $targetEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target")
                $oc = @($targetEntry.Properties["objectClass"])
                if ($oc -and ($oc -contains "computer")) {
                    $entries = @($targetEntry)
                    Write-Verbose "Target is a single computer DN: $Target"
                }
                else {
                    Write-Verbose "Target treated as container/OU. Enumerating descendant computers under $Target"
                    $searcher = [System.DirectoryServices.DirectorySearcher]::new($targetEntry)
                    $searcher.Filter = "(objectCategory=computer)"
                    $searcher.PageSize = 1000
                    [void]$searcher.PropertiesToLoad.AddRange(@("distinguishedName","dNSHostName","name"))
                    $hits = $searcher.FindAll()
                    $entries = foreach ($h in $hits) { try { $h.GetDirectoryEntry() } catch { } }
                }
            } catch {
                Write-Error "Failed to bind to Target DN '$Target': $_"
                return
            }
        }
        else {
            $root   = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
            $baseDN = if ($SearchBase) { $SearchBase } else { $root.Properties["defaultNamingContext"].Value }
            $base   = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$baseDN")
            $searcher = [System.DirectoryServices.DirectorySearcher]::new($base)
            $searcher.Filter = "(objectCategory=computer)"
            $searcher.PageSize = 1000
            [void]$searcher.PropertiesToLoad.AddRange(@("distinguishedName","dNSHostName","name"))
            $hits = $searcher.FindAll()
            $entries = foreach ($h in $hits) { try { $h.GetDirectoryEntry() } catch { } }
        }
        if (-not $entries -or $entries.Count -eq 0) {
            Write-Output "No computer objects found to process."
            return
        }
        $computers =
            $entries |
            ForEach-Object {
                $dns = $_.Properties["dNSHostName"]
                $nm  = $_.Properties["name"]
                if ($dns -and $dns.Count -gt 0 -and $dns[0]) { [string]$dns[0] }
                elseif ($nm -and $nm.Count -gt 0 -and $nm[0]) { [string]$nm[0] }
            } |
            Where-Object { $_ } |
            Select-Object -Unique

    } catch {
        Write-Error "LDAP enumeration failed: $_"
        return
    }
    Write-Host "Enumerating local 'Remote Desktop Users' on $($computers.Count) computer(s)..."
    $results = New-Object System.Collections.Generic.List[object]
    foreach ($c in $computers) {
        try {
            $opt  = New-CimSessionOption -Protocol Dcom
            $sess = New-CimSession -ComputerName $c -SessionOption $opt -OperationTimeoutSec $TimeoutSec -ErrorAction Stop
            try {
                $grp = Get-CimInstance -CimSession $sess -ClassName Win32_Group -Filter "Name='Remote Desktop Users'" -ErrorAction Stop
                if (-not $grp) {
                    Write-Warning "'Remote Desktop 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 Desktop 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 Desktop Users' members found across the scanned computers."}
}

2. Scan all Computers in the domain

Find-CAN_RDPSimple

3. Scan specific computer object

 Find-CAN_RDPSimple -Target "CN=fssql,CN=Computers,DC=Forestall,Dc=labs"

4. Using SearchBase to limit the Scope

Find-CAN_RDPSimple -SearchBase "CN=Computers,DC=forestall,DC=labs"

Computer Management

Note: This edge cannot be identified directly using ADUC but can be identified using Computer Management.

1. Open Computer Management.

2. From the Action menu, choose "Connect to Another Computer" if you need to manage a different machine.

3. Select the desired computer to manage.

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

5. Open "Remote Desktop Users" under Local Users and Groups.

6. In the Members list, locate the users and groups that have access.

7. Click OK to close the dialogs.

Exploitation

Windows

Using the Remote Desktop Client on Windows (mstsc.exe):

Linux

Using xfreerdp on Linux (example from Kali):

xfreerdp3 /v:<ip> /u:<user> /p:'<pass>' /d:<domain>

Example:

xfreerdp3 /v:192.168.121.110 /u:adam /p:'Temp123!' /d:forestall.labs

Mitigation

You can mitigate risks associated with CAN_RDP by removing unwanted users or groups from the local "Remote Desktop Users" group on the target machine.

1. Open Computer Management.

2. From the Action menu, choose "Connect to Another Computer" if you need to manage a different machine.

3. Select the desired computer to manage.

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

5. Open "Remote Desktop Users."

6. In the Members list, select any unwanted user or group and click Remove to revoke access.

7. Click OK to close the dialogs.

Detection

Adding new Access Control Entries (ACEs) on Active Directory objects changes the ntSecurityDescriptor attribute of the objects themselves. These changes can be detected using Event IDs 5136 and 4662 to identify potentially risky modifications. Event ID 4624 indicates a successful logon to a Windows system (Logon Type 10 refers to RemoteInteractive), while 1149 indicates a Remote Desktop (RDP) logon.

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

4624

An account was successfully logged on.

Logon Type (type 10 refers to the RemoteInteractive), Subject, New Logon

https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4624

1149

User Authentication succeeded

User, Computer

https://www.cybertriage.com/artifact/terminalservices_remoteconnectionmanager_log/terminalservices_remoteconnectionmanager_operational_1149/

References

Last updated

Was this helpful?