diff --git a/.chloggen/feat_windows-evlog_security-and-execution.yaml b/.chloggen/feat_windows-evlog_security-and-execution.yaml new file mode 100755 index 000000000000..c4ea6f82f1c5 --- /dev/null +++ b/.chloggen/feat_windows-evlog_security-and-execution.yaml @@ -0,0 +1,22 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: windowseventlogreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add parsing for Security and Execution event fields. + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [27810] + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: ["user"] diff --git a/pkg/stanza/operator/input/windows/testdata/xmlSampleUserData.xml b/pkg/stanza/operator/input/windows/testdata/xmlSampleUserData.xml new file mode 100644 index 000000000000..5075f68036c4 --- /dev/null +++ b/pkg/stanza/operator/input/windows/testdata/xmlSampleUserData.xml @@ -0,0 +1,28 @@ + + + + 1102 + 1 + 4 + 104 + 0 + 0x4020000000000000 + + 2590526 + + + Security + test.example.com + + + + + S-1-5-21-1148437859-4135665037-1195073887-1000 + test_user + TEST + 0xa8bb72 + 4536 + 17732923532772643 + + + diff --git a/pkg/stanza/operator/input/windows/xml.go b/pkg/stanza/operator/input/windows/xml.go index 4c8a9b45a89c..654bef5f881d 100644 --- a/pkg/stanza/operator/input/windows/xml.go +++ b/pkg/stanza/operator/input/windows/xml.go @@ -28,6 +28,8 @@ type EventXML struct { Opcode string `xml:"System>Opcode"` RenderedKeywords []string `xml:"RenderingInfo>Keywords>Keyword"` Keywords []string `xml:"System>Keywords"` + Security *Security `xml:"System>Security"` + Execution *Execution `xml:"System>Execution"` EventData []EventDataEntry `xml:"EventData>Data"` } @@ -118,9 +120,21 @@ func (e *EventXML) parseBody() map[string]interface{} { "keywords": keywords, "event_data": parseEventData(e.EventData), } + if len(details) > 0 { body["details"] = details } + + if e.Security != nil && e.Security.UserID != "" { + body["security"] = map[string]any{ + "user_id": e.Security.UserID, + } + } + + if e.Execution != nil { + body["execution"] = e.Execution.asMap() + } + return body } @@ -181,3 +195,50 @@ type EventDataEntry struct { Name string `xml:"Name,attr"` Value string `xml:",chardata"` } + +// Security contains info pertaining to the user triggering the event. +type Security struct { + UserID string `xml:"UserID,attr"` +} + +// Execution contains info pertaining to the process that triggered the event. +type Execution struct { + // ProcessID and ThreadID are required on execution info + ProcessID uint `xml:"ProcessID,attr"` + ThreadID uint `xml:"ThreadID,attr"` + // These remaining fields are all optional for execution info + ProcessorID *uint `xml:"ProcessorID,attr"` + SessionID *uint `xml:"SessionID,attr"` + KernelTime *uint `xml:"KernelTime,attr"` + UserTime *uint `xml:"UserTime,attr"` + ProcessorTime *uint `xml:"ProcessorTime,attr"` +} + +func (e Execution) asMap() map[string]any { + result := map[string]any{ + "process_id": e.ProcessID, + "thread_id": e.ThreadID, + } + + if e.ProcessorID != nil { + result["processor_id"] = *e.ProcessorID + } + + if e.SessionID != nil { + result["session_id"] = *e.SessionID + } + + if e.KernelTime != nil { + result["kernel_time"] = *e.KernelTime + } + + if e.UserTime != nil { + result["user_time"] = *e.UserTime + } + + if e.ProcessorTime != nil { + result["processor_time"] = *e.ProcessorTime + } + + return result +} diff --git a/pkg/stanza/operator/input/windows/xml_test.go b/pkg/stanza/operator/input/windows/xml_test.go index 56ba2e595789..2fa3d177920c 100644 --- a/pkg/stanza/operator/input/windows/xml_test.go +++ b/pkg/stanza/operator/input/windows/xml_test.go @@ -116,6 +116,162 @@ func TestParseBody(t *testing.T) { require.Equal(t, expected, xml.parseBody()) } +func TestParseBodySecurityExecution(t *testing.T) { + xml := EventXML{ + EventID: EventID{ + ID: 1, + Qualifiers: 2, + }, + Provider: Provider{ + Name: "provider", + GUID: "guid", + EventSourceName: "event source", + }, + TimeCreated: TimeCreated{ + SystemTime: "2020-07-30T01:01:01.123456789Z", + }, + Computer: "computer", + Channel: "application", + RecordID: 1, + Level: "Information", + Message: "message", + Task: "task", + Opcode: "opcode", + Keywords: []string{"keyword"}, + EventData: []EventDataEntry{ + {Name: "name", Value: "value"}, {Name: "another_name", Value: "another_value"}, + }, + Execution: &Execution{ + ProcessID: 13, + ThreadID: 102, + }, + Security: &Security{ + UserID: "my-user-id", + }, + RenderedLevel: "rendered_level", + RenderedTask: "rendered_task", + RenderedOpcode: "rendered_opcode", + RenderedKeywords: []string{"RenderedKeywords"}, + } + + expected := map[string]interface{}{ + "event_id": map[string]interface{}{ + "id": uint32(1), + "qualifiers": uint16(2), + }, + "provider": map[string]interface{}{ + "name": "provider", + "guid": "guid", + "event_source": "event source", + }, + "system_time": "2020-07-30T01:01:01.123456789Z", + "computer": "computer", + "channel": "application", + "record_id": uint64(1), + "level": "rendered_level", + "message": "message", + "task": "rendered_task", + "opcode": "rendered_opcode", + "keywords": []string{"RenderedKeywords"}, + "execution": map[string]any{ + "process_id": uint(13), + "thread_id": uint(102), + }, + "security": map[string]any{ + "user_id": "my-user-id", + }, + "event_data": map[string]interface{}{"name": "value", "another_name": "another_value"}, + } + + require.Equal(t, expected, xml.parseBody()) +} + +func TestParseBodyFullExecution(t *testing.T) { + processorID := uint(3) + sessionID := uint(2) + kernelTime := uint(3) + userTime := uint(100) + processorTime := uint(200) + + xml := EventXML{ + EventID: EventID{ + ID: 1, + Qualifiers: 2, + }, + Provider: Provider{ + Name: "provider", + GUID: "guid", + EventSourceName: "event source", + }, + TimeCreated: TimeCreated{ + SystemTime: "2020-07-30T01:01:01.123456789Z", + }, + Computer: "computer", + Channel: "application", + RecordID: 1, + Level: "Information", + Message: "message", + Task: "task", + Opcode: "opcode", + Keywords: []string{"keyword"}, + EventData: []EventDataEntry{ + {Name: "name", Value: "value"}, {Name: "another_name", Value: "another_value"}, + }, + Execution: &Execution{ + ProcessID: 13, + ThreadID: 102, + ProcessorID: &processorID, + SessionID: &sessionID, + KernelTime: &kernelTime, + UserTime: &userTime, + ProcessorTime: &processorTime, + }, + Security: &Security{ + UserID: "my-user-id", + }, + RenderedLevel: "rendered_level", + RenderedTask: "rendered_task", + RenderedOpcode: "rendered_opcode", + RenderedKeywords: []string{"RenderedKeywords"}, + } + + expected := map[string]interface{}{ + "event_id": map[string]interface{}{ + "id": uint32(1), + "qualifiers": uint16(2), + }, + "provider": map[string]interface{}{ + "name": "provider", + "guid": "guid", + "event_source": "event source", + }, + "system_time": "2020-07-30T01:01:01.123456789Z", + "computer": "computer", + "channel": "application", + "record_id": uint64(1), + "level": "rendered_level", + "message": "message", + "task": "rendered_task", + "opcode": "rendered_opcode", + "keywords": []string{"RenderedKeywords"}, + "execution": map[string]any{ + "process_id": uint(13), + "thread_id": uint(102), + "processor_id": processorID, + "session_id": sessionID, + "kernel_time": kernelTime, + "user_time": userTime, + "processor_time": processorTime, + }, + "security": map[string]any{ + "user_id": "my-user-id", + }, + "event_data": map[string]interface{}{"name": "value", "another_name": "another_value"}, + } + + require.Equal(t, expected, xml.parseBody()) +} + func TestParseNoRendered(t *testing.T) { xml := EventXML{ EventID: EventID{ @@ -252,7 +408,7 @@ func TestInvalidUnmarshal(t *testing.T) { require.Error(t, err) } -func TestUnmarshal(t *testing.T) { +func TestUnmarshalWithEventData(t *testing.T) { data, err := os.ReadFile(filepath.Join("testdata", "xmlSample.xml")) require.NoError(t, err) @@ -283,7 +439,47 @@ func TestUnmarshal(t *testing.T) { {Name: "Time", Value: "2022-04-28T19:48:52Z"}, {Name: "Source", Value: "RulesEngine"}, }, - Keywords: []string{"0x80000000000000"}, + Keywords: []string{"0x80000000000000"}, + Security: &Security{}, + Execution: &Execution{}, + } + + require.Equal(t, xml, event) +} + +func TestUnmarshalWithUserData(t *testing.T) { + data, err := os.ReadFile(filepath.Join("testdata", "xmlSampleUserData.xml")) + require.NoError(t, err) + + event, err := unmarshalEventXML(data) + require.NoError(t, err) + + xml := EventXML{ + EventID: EventID{ + ID: 1102, + }, + Provider: Provider{ + Name: "Microsoft-Windows-Eventlog", + GUID: "{fc65ddd8-d6ef-4962-83d5-6e5cfe9ce148}", + }, + TimeCreated: TimeCreated{ + SystemTime: "2023-10-12T10:38:24.543506200Z", + }, + Computer: "test.example.com", + Channel: "Security", + RecordID: 2590526, + Level: "4", + Message: "", + Task: "104", + Opcode: "0", + Keywords: []string{"0x4020000000000000"}, + Security: &Security{ + UserID: "S-1-5-18", + }, + Execution: &Execution{ + ProcessID: 1472, + ThreadID: 7784, + }, } require.Equal(t, xml, event)