remote computerのRDP connection historyは、一つの承認済みFQDNへ限定してSecurity 4624/4625を照会し、XMLのnamed EventDataからLogonType 10だけを抽出します。User、IP、success/failure、RecordIdを出し、access/connect errorを0件と扱いません。1149とsession eventは別の補助表です。
一つのremote FQDNを承認対象にする
短縮名、wildcard、複数host配列は使いません。DNS解決できるexact FQDNとquery IDsをtokenへ含め、credentialは対話入力だけで取得してstateやfileへ保存しません。
$ErrorActionPreference='Stop'
$remoteFqdn='rdsh01.corp.example.com'
if([uri]::CheckHostName($remoteFqdn) -ne [UriHostNameType]::Dns -or $remoteFqdn -notmatch '\.'){throw 'one exact remote FQDN is required'}
$resolved=@(Resolve-DnsName -Name $remoteFqdn -Type A_AAAA -ErrorAction Stop)
if($resolved.Count -eq 0){throw 'approved remote FQDN did not resolve'}
$token="QUERY-RDP-HISTORY REMOTE=$remoteFqdn IDS=4624,4625 LOGONTYPE=10"
if((Read-Host "対象と監査承認を確認し、照会する場合は $token") -ne $token){throw 'remote query not approved'}
$credential=Get-Credential -Message "read Windows event logs on $remoteFqdn"
$start=(Get-Date).AddDays(-7)
4624/4625のnamed fieldsを解析する
remote Security queryはErrorAction Stopで失敗を伝播します。Message textではなくEventData Nameをhashtable化し、LogonType=10だけをUser、Domain、IpAddress、status、RecordIdへ変換します。
try{
$securityEvents=@(Get-WinEvent -ComputerName $remoteFqdn -Credential $credential -FilterHashtable @{LogName='Security';Id=4624,4625;StartTime=$start} -ErrorAction Stop)
}catch{throw "remote Security query failed; not treated as no-event: $($_.Exception.Message)"}
function Get-NamedEventData([Diagnostics.Eventing.Reader.EventRecord]$Event){
[xml]$xml=$Event.ToXml();$fields=@{}
foreach($node in @($xml.Event.EventData.Data)){$fields[[string]$node.Name]=[string]$node.'#text'}
$fields
}
$rdp=@($securityEvents|ForEach-Object {
$d=Get-NamedEventData $_
if($d['LogonType'] -eq '10'){
[pscustomobject]@{RemoteFqdn=$remoteFqdn;TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;EventId=[int]$_.Id;Status=$(if($_.Id -eq 4624){'Success'}else{'Failure'});Domain=[string]$d['TargetDomainName'];User=[string]$d['TargetUserName'];IpAddress=[string]$d['IpAddress'];IpPort=[string]$d['IpPort'];LogonType=[int]$d['LogonType'];FailureStatus=[string]$d['Status'];SubStatus=[string]$d['SubStatus']}
}
}|Sort-Object TimeCreated,RecordId)
if($rdp.Count -eq 0){[pscustomobject]@{Status='NoRemoteInteractiveSecurityEvents';RemoteFqdn=$remoteFqdn;Start=$start}}else{$rdp}
1149 authenticationを別queryにする
RemoteConnectionManager 1149はauthentication evidenceです。4624 successや4625 failureと意味が異なるため、Param fieldsを別rowにし、件数をSecurity結果へ足しません。
# 1149 authentication evidence is queried separately from Security success/failure.
$rmChannel='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'
try{$auth1149=@(Get-WinEvent -ComputerName $remoteFqdn -Credential $credential -FilterHashtable @{LogName=$rmChannel;Id=1149;StartTime=$start} -ErrorAction Stop)}catch{throw "remote 1149 query failed: $($_.Exception.Message)"}
$authRows=@($auth1149|ForEach-Object {
$d=Get-NamedEventData $_
[pscustomobject]@{RemoteFqdn=$remoteFqdn;Evidence='1149';TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;User=[string]$d['Param1'];Domain=[string]$d['Param2'];IpAddress=[string]$d['Param3']}
}|Sort-Object TimeCreated,RecordId)
$authRows
session lifecycleを別queryにする
LocalSessionManager 21/23/24/25はSessionIDを含むlifecycle evidenceです。明示したuser/IP/time windowでcorroborateできますが、Security statusを書き換えたり、単純な時刻近接だけで同一connectionと断定しません。
# LocalSessionManager is session corroboration, not a replacement for 4624/4625 status.
$lsmChannel='Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
try{$sessionEvents=@(Get-WinEvent -ComputerName $remoteFqdn -Credential $credential -FilterHashtable @{LogName=$lsmChannel;Id=21,23,24,25;StartTime=$start} -ErrorAction Stop)}catch{throw "remote session query failed: $($_.Exception.Message)"}
$sessionRows=@($sessionEvents|ForEach-Object {
$d=Get-NamedEventData $_
[pscustomobject]@{RemoteFqdn=$remoteFqdn;Evidence='SessionLifecycle';TimeCreated=$_.TimeCreated;RecordId=[long]$_.RecordId;EventId=[int]$_.Id;User=[string]$d['User'];SessionId=[string]$d['SessionID'];IpAddress=[string]$d['Address']}
}|Sort-Object TimeCreated,RecordId)
[pscustomobject]@{RemoteFqdn=$remoteFqdn;SecurityLogonType10Rows=$rdp.Count;Authentication1149Rows=$authRows.Count;SessionLifecycleRows=$sessionRows.Count;JoinPolicy='display separately; correlate by explicit user/IP/time window only'}
0件・拒否・接続不能を区別する
query成功かつLogonType 10が0件の場合だけNoRemoteInteractiveSecurityEventsです。RPC/firewall、credential、event log ACL、channel disable、network failureはerrorとして修復後に再照会します。
受入条件
fixtureでは4624/4625のnamed user/IP/status/RecordIdが再現され、LogonType 3などは除外されます。別hostのeventを混ぜず、1149とsession rowは独立countのまま提示されます。

コメント