이전에 ADFS Audit Log에 대해서 다룬 적이 있습니다.
2020.09.06 - [Windows Server] - ADFS Events and Logging
ADFS의 주요 로그인 이벤트는 1200~1207입니다. 아래의 항목들은 감사로그가 설정되면 확인할 수 있습니다.
이벤트 로그 상에서는 Security 항목으로 다음과 같이 기록됩니다.
상세로그는 XML 형식으로 되어 있습니다.
이번 글에서는 각각의 XML 의 속성 값들을 CSV로 내보내는 Script 제작에 대해서 다뤄보겠습니다.
우선 아래의 글에 작성된 스크립트를 참고하였습니다.
Report basic authentication Signins in ADFS | Exchange, Sharepoint, Office 365 (wordpress.com)
Sample Script는 다음과 같습니다. (Export-CSV 부분을 조금 수정하였습니다.)
$evts = Get-WinEvent -FilterHashtable @{Logname='security';Id=1200}
$report = @()
$evts |% {
$activityid = $_.properties.value[0]
[XML]$XML = $_.properties.value[1]
$myObject = [PSCustomObject]@{
'Eventid' = $_.id
'AuditResult' = $xml.auditbase.AuditResult
'FailureType' =$xml.auditbase.FailureType
'ErrorCode' = $xml.auditbase.ErrorCode
'timecreated' = $_.timecreated
'ActivityId' = $activityID
'User' = $xml.auditbase.contextComponents.component[0].userid
'RelyingParty' = $xml.auditbase.contextComponents.component[0].RelyingParty
'IpAddress' = $xml.auditbase.contextComponents.component[3].IpAddress
'NetworkLocation' = $xml.auditbase.contextComponents.component[3].NetworkLocation
'MfaMethod' = $xml.auditbase.contextComponents.component[2].MfaMethod
'AuthProtocol' = $xml.auditbase.contextComponents.component[3].AuthProtocol
'UserAgentString' = $xml.auditbase.contextComponents.component[3].UserAgentString
'EndPoint' = $xml.auditbase.contextComponents.component[3].endpoint
}
$report += $myObject
}
$date = get-date -Format ("yyyy-MM-dd-")
$path = ".\" + $date + $env:computername + ".log"
$report |sort timecreated -Descending:$false |export-csv -path $path -notypeinformation -Encoding UTF8
아래의 명령어는 LogName이 Security 이고 Id가 1200 인 이벤트 로그를 추출합니다.
Get-WinEvent -FilterHashtable @{Logname='security';Id=1200}
위 스크립트에서 가장 중요한 [XML]$XML 입니다. ChatGPT에게 확인해 보았습니다.
From ChatGPT
In PowerShell, [XML]$XML is used to cast or convert a string or variable into an XML object. The syntax [XML] is a type accelerator, which is a shorthand way to specify a type in PowerShell, in this case, the System.Xml.XmlDocument type. By casting a string or variable to XML, you can easily navigate and manipulate the XML data using PowerShell's built-in XML features.
PowerShell에서 [XML]$XML은 문자열이나 변수를 XML 개체로 캐스팅하거나 변환하는 데 사용됩니다. [XML] 구문은 PowerShell에서 형식(이 경우 System.Xml.XmlDocument 형식)을 지정하는 속기 방법인 형식 가속기입니다. 문자열이나 변수를 XML로 캐스팅하면 PowerShell의 기본 제공 XML 기능을 사용하여 XML 데이터를 쉽게 탐색하고 조작할 수 있습니다.
-> XML Data에서 각각의 속성 값을 추출할 때 활용할 수 있습니다.
아래는 ChatGPT가 생성해준 XML Script 예시입니다.
# Define a string containing XML data
$xmlData = @"
<root>
<child name="John">
<age>30</age>
</child>
<child name="Jane">
<age>25</age>
</child>
</root>
"@
# Cast the string to an XML object
[XML]$XML = $xmlData
# Access and manipulate the XML data
$XML.root.child | Where-Object { $_.age -gt 28 }
스크립트를 실행하면 다음과 같이 결과 값이 확인됩니다.
단순 결과 값보다는 XML 구조를 이해하는 것이 중요합니다.
상단의 Raw Data가 XML Data 구조를 정의합니다.
$XML 입력시 최상위 Root 라는 속성을 조회 합니다.
$XML.Root 같이 입력하면 다음 계층의 속성 값을 조회할 수 있습니다. 같은 방식으로 하위 속성을 조회할 수 있는 구조 입니다.
아래는 Event ID 1200 로그인 로그 샘플입니다.
Sample Script에서 아래의 부분이 이벤트로그 상에서 어떠한 속성에 해당하는지 확인해 보겠습니다.
우선 Sample Script를 실행하면 다음과 같이 CSV 결과를 확인할 수 있습니다.
Excel상에서 항목별로 정리된 것을 확인할 수 있습니다.
다만 실제 Event 상의 모든 항목을 기록하지는 않습니다.
이벤트 로그의 항목이 스크립트 상에서 어느 부분에 해당하는지 확인해 보겠습니다.
스크립트를 실행한 뒤, $XML 로 확인해보면 속성은 xml과 AuditBase가 확인됩니다.
아래와 같이 최상위 계층의 속성은 xml, AuditBase 라는 것은 확인할 수 있습니다. Xml는 단일 계층이기 때문에 AuditBase만 확인합니다.
$xml.AuditBase
2번째 계층은 다음과 같이 확인됩니다.
스크립트 상에서 아래의 영역에 해당합니다.
AuditType 속성은 스크립트 상에서는 없는 것을 확인할 수 있습니다.
해당 속성을 넣고 싶으면 다음과 같이 추가할 수 있습니다.
'AuditType' = $xml.AuditBase.AuditType
대부분의 속성이 위치하고 있는 ContextComponents 를 $XML.AuditBase.ContextComponents 로 조회해보면, 각각의 4개의 Component로 조회됩니다.
로그 상에서 다음과 같이 구분됩니다.
이 부분은 $xml.auditbase.contextComponents.component[1~4]로 구분하여 조회할 수 있습니다.
각각의 Type으로 구분한 것을 알 수 있습니다.
스크립트 상에서 아래의 부분에 해당하며, Sample에서는 일부 항목만 포함되었다는 것을 알 수 있습니다.
Event ID 기준을 1200~1207까지 포함, CSV파일로 추, 모든항목을 포함하도록 다음과 같이 스크립트를 수정하였습니다
$evts = Get-WinEvent -FilterHashtable @{Logname='security';Id=1200,1201,1202,1203,1204,1205,1206,1207}
$report = @()
$evts |% {
$activityid = $_.properties.value[0]
[XML]$XML = $_.properties.value[1]
$myObject = [PSCustomObject]@{
'timecreated' = $_.timecreated
'Eventid' = $_.id
'ActivityId' = $activityID
#Auditbase
'Audittype' = $xml.auditbase.AuditType
'AuditResult' = $xml.auditbase.AuditResult
'FailureType' =$xml.auditbase.FailureType
'ErrorCode' = $xml.auditbase.ErrorCode
#ResourceAuditComponent
'RelyingParty' = $xml.auditbase.contextComponents.component[0].RelyingParty
'ClaimsProvider' = $xml.auditbase.contextComponents.component[0].ClaimsProvider
'User' = $xml.auditbase.contextComponents.component[0].userid
#ResourceAuditComponent
'PrimaryAuth' = $xml.auditbase.contextComponents.component[1].PrimaryAuth
'DeviceAuth' = $xml.auditbase.contextComponents.component[1].DeviceId
'MfaPerformed' = $xml.auditbase.contextComponents.component[1].MfaPerformed
'MfaMethod' = $xml.auditbase.contextComponents.component[1].MfaMethod
'TokenBindingProvidedId' = $xml.auditbase.contextComponents.component[1].TokenBindingProvidedId
'TokenBindingReferredId' = $xml.auditbase.contextComponents.component[1].TokenBindingReferredId
'SsoBindingValidationLevel' = $xml.auditbase.contextComponents.component[1].SsoBindingValidationLevel
#ProtocolAuditComponent
'OAuthClientId' = $xml.auditbase.contextComponents.component[2].OAuthClientId
'OAuthGrant' = $xml.auditbase.contextComponents.component[2].OAuthGrant
#RequestAuditComponent
'Server' = $xml.auditbase.contextComponents.component[3].Server
'AuthProtocol' = $xml.auditbase.contextComponents.component[3].AuthProtocol
'NetworkLocation' = $xml.auditbase.contextComponents.component[3].NetworkLocation
'IpAddress' = $xml.auditbase.contextComponents.component[3].IpAddress
'ProxyIpAddress' = $xml.auditbase.contextComponents.component[3].ProxyIpAddress
'NetworkIpAddress' = $xml.auditbase.contextComponents.component[3].NetworkIpAddress
'ProxyServer' = $xml.auditbase.contextComponents.component[3].ProxyServer
'UserAgentString' = $xml.auditbase.contextComponents.component[3].UserAgentString
'EndPoint' = $xml.auditbase.contextComponents.component[3].endpoint
}
$report += $myObject
}
$date = get-date -Format ("yyyy-MM-dd-")
$path = ".\" + $date + $env:computername + ".csv"
$report |sort timecreated -Descending:$false |export-csv -path $path -notypeinformation -Encoding UTF8
아래와 같이 모든 항목이 조회되는 것을 알 수 있습니다.
Timecreated 항목의 경우 한글 텍스트가 들어가며, Excel 상에서 정렬하여 사용하기에는 다소 애매한 부분이 있습니다. 이 부분을 ChatGPT에게 문의하여 UTC 형태로 변환하는 방법에 대하여 확인하였습니다.
You can convert the timecreated property to UTC format so that it can be easily used in Excel. To do this, you can update the script to format the timecreated property as a standard UTC datetime string. You can use the ToUniversalTime() method to convert the datetime to UTC and the ToString() method with a standard format specifier to represent it as a string. Here's the updated portion of the script:
-> ToUniversalTime() 을 사용하여 변경할 수 있습니다.
$report = @()
$evts |% {
$activityid = $_.properties.value[0]
[XML]$XML = $_.properties.value[1]
$utcTimeCreated = $_.timecreated.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$myObject = [PSCustomObject]@{
'timecreated' = $utcTimeCreated
# Rest of the properties remain unchanged
}
$report += $myObject
}
최종 수정본은 다음과 같습니다.
$evts = Get-WinEvent -FilterHashtable @{Logname='security';Id=1200,1201,1202,1203,1204,1205,1206,1207}
$report = @()
$evts |% {
$activityid = $_.properties.value[0]
[XML]$XML = $_.properties.value[1]
$utcTimeCreated = $_.timecreated.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$myObject = [PSCustomObject]@{
'timecreated' = $utcTimeCreated
'Eventid' = $_.id
'ActivityId' = $activityID
#Auditbase
'Audittype' = $xml.auditbase.AuditType
'AuditResult' = $xml.auditbase.AuditResult
'FailureType' =$xml.auditbase.FailureType
'ErrorCode' = $xml.auditbase.ErrorCode
#ResourceAuditComponent
'RelyingParty' = $xml.auditbase.contextComponents.component[0].RelyingParty
'ClaimsProvider' = $xml.auditbase.contextComponents.component[0].ClaimsProvider
'User' = $xml.auditbase.contextComponents.component[0].userid
#ResourceAuditComponent
'PrimaryAuth' = $xml.auditbase.contextComponents.component[1].PrimaryAuth
'DeviceAuth' = $xml.auditbase.contextComponents.component[1].DeviceId
'MfaPerformed' = $xml.auditbase.contextComponents.component[1].MfaPerformed
'MfaMethod' = $xml.auditbase.contextComponents.component[1].MfaMethod
'TokenBindingProvidedId' = $xml.auditbase.contextComponents.component[1].TokenBindingProvidedId
'TokenBindingReferredId' = $xml.auditbase.contextComponents.component[1].TokenBindingReferredId
'SsoBindingValidationLevel' = $xml.auditbase.contextComponents.component[1].SsoBindingValidationLevel
#ProtocolAuditComponent
'OAuthClientId' = $xml.auditbase.contextComponents.component[2].OAuthClientId
'OAuthGrant' = $xml.auditbase.contextComponents.component[2].OAuthGrant
#RequestAuditComponent
'Server' = $xml.auditbase.contextComponents.component[3].Server
'AuthProtocol' = $xml.auditbase.contextComponents.component[3].AuthProtocol
'NetworkLocation' = $xml.auditbase.contextComponents.component[3].NetworkLocation
'IpAddress' = $xml.auditbase.contextComponents.component[3].IpAddress
'ProxyIpAddress' = $xml.auditbase.contextComponents.component[3].ProxyIpAddress
'NetworkIpAddress' = $xml.auditbase.contextComponents.component[3].NetworkIpAddress
'ProxyServer' = $xml.auditbase.contextComponents.component[3].ProxyServer
'UserAgentString' = $xml.auditbase.contextComponents.component[3].UserAgentString
'EndPoint' = $xml.auditbase.contextComponents.component[3].endpoint
}
$report += $myObject
}
$date = get-date -Format ("yyyy-MM-dd-")
$path = ".\" + $date + $env:computername + ".csv"
$report |sort timecreated -Descending:$false |export-csv -path $path -notypeinformation -Encoding UTF8
아래와 같이 변경된 것을 확인할 수 있습니다.
아래와 같이 각각의 속성을 확인하며 로그인 기록을 관리할 수 있습니다.