-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path0511-logstash-powershell.conf
227 lines (195 loc) · 10 KB
/
0511-logstash-powershell.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
filter {
if [@meta][log][type] == "windows-wef" {
# PowerShell Operational Only
if [Channel] == "Microsoft-Windows-PowerShell/Operational" {
# EventID 4103
if [EventID] == 4103 {
mutate {
add_field => {
"PayLoadInvocation" => "%{Payload}"
"PayLoadParams" => "%{Payload}"
}
gsub => [
# Normalize ContextInfo
"ContextInfo", " ", "",
"ContextInfo", " = ", "="
]
}
# Parse ContextInfo
kv {
source => "ContextInfo"
field_split => "\r\n"
value_split => "="
remove_char_key => " "
allow_duplicate_values => false
# Set only allowed keys/fields incase ever an error parsing where something could contain a similar value_split of "="
include_keys => [ "Severity", "HostName", "HostVersion", "HostID", "HostApplication", "EngineVersion", "RunspaceID", "PipelineID", "CommandName", "CommandType", "ScriptName", "CommandPath", "SequenceNumber", "User", "ConnectedUser", "ShellID" ]
}
mutate {
gsub => [
# Prepare Payload CommandInvocation parsing
"PayLoadInvocation", "CommandInvocation\(.*\)", "CommandInvocation",
"PayLoadInvocation", "ParameterBinding.*\r\n", "",
# Prepare Payload ParameterBinding parsing
"PayLoadParams", "CommandInvocation.*\r\n", "",
"PayLoadParams", "ParameterBinding\(\S+\): ", "|||SPLITMEHEHE|||",
# Remove any commandinvocation and parameterbinding and any other known fields/keys and leave a remaining Payload field
"Payload", "CommandInvocation.*\r\n", "",
"Payload", "ParameterBinding.*\r\n", ""
]
}
# Parse payload field for all CommandInvocations
# Also add a field for non alphanumeric characters via https://twitter.com/jackcr/status/884875719972728833
kv {
source => "PayLoadInvocation"
field_split => "\n"
value_split => ":"
allow_duplicate_values => false
target => "[ps]"
include_keys => [ "CommandInvocation" ]
}
ruby {
code => "
params_split = event.get('PayLoadParams').split('|||SPLITMEHEHE|||')
params_split = params_split.drop(1)
params_split_length = params_split.length
all_names = Array.new
all_values = Array.new
all_values_non_alphanumeric = Array.new
for param in params_split
slice_and_dice = param.index('; value=')
name = param.slice(6..slice_and_dice-2)
value = param.slice(param.index('value=')..-1)[6..-1]
value = value.strip
value[0] = ''
value[-1] = ''
value_non_alphanumeric = value.gsub(/[A-Za-z0-9\s]+/i, '')
all_names.push(name)
all_values.push(value)
all_values_non_alphanumeric.push(value_non_alphanumeric)
end
all_names = all_names.uniq
all_values = all_values.uniq
event.set('[ps][param][name]', all_names)
event.set('[ps][param][value]', all_values)
event.set('[ps][param][value_nonalphanumeric]', all_values_non_alphanumeric)
"
}
# Cleanup and Conversions
mutate {
# Normalize ContextInfo field names
rename => {
"CommandName" => "[ps][command][name]"
"CommandPath" => "[ps][command][path]"
"CommandType" => "[ps][command][type]"
"ConnectedUser" => "[ps][connected_user][full]"
"EngineVersion" => "[ps][version][full]"
"HostApplication" => "[ps][src][application]"
"HostID" => "[ps][src][host_id]"
"HostName" => "[ps][src][name]"
"HostVersion" => "[ps][src][version]"
"PipelineID" => "[ps][pipeline_id]"
"RunspaceID" => "[ps][runspace_id]"
"ScriptName" => "[file][name]"
"SequenceNumber" => "[ps][seq_num]"
"ShellID" => "[ps][src][id]"
"User" => "[ps][user][full]"
"[ps][CommandInvocation]" => "[ps][invocation]"
"Payload" => "[ps][remaining_payload]"
}
# Remove unwanted fields
remove_field => [
"Severity",
"EventType",
"Keywords",
"message",
"Message",
"Opcode",
"port",
"SeverityValue",
"SourceModuleName",
"SourceModuleType",
"Version",
"ContextInfo",
"PayLoadInvocation",
"PayLoadParams"
]
# Set correct value types
convert => { "[ps][pipeline_id]" => "integer" }
convert => { "[ps][seq_num]" => "integer" }
lowercase => [
"[ps][command][name]",
"[ps][command][type]",
"[ps][src][application]",
"[ps][src][id]",
"[ps][src][name]",
"[ps][user][full]"
]
}
}
# EventID 4104
else if [EventID] == 4104 {
# Sometimes ScriptBlockText will not be parsed from the Message field. When this happens the other parameters (appear) to also never be parsed (ie: ScriptBlockId etc)
# So check if ScriptBlockText exists and if it does not then we will want to parse the parameters from the Message field
if [ScriptBlockText] {
mutate {
remove_field => [
"Message"
]
}
}
else {
# Lets use GSUB to make sure we can get things to split on / make it easier more efficient to split on
grok {
match => {
"Message" => "^Creating Scriptblock text \(%{INT:MessageNumber} of %{INT:MessageTotal}\):\r\n%{GREEDYDATA:ScriptBlockText}\r\n\r\nScriptBlock ID: %{UUID:ScriptBlockId}\r\nPath: %{DATA:Path}$"
}
break_on_match => true
keep_empty_captures => false
named_captures_only => true
tag_on_failure => [ "_grokparsefailure", "_parsefailure" ]
tag_on_timeout => "_groktimeout"
# Timeout 1.5 seconds
timeout_millis => 1500
remove_field => [ "Message" ]
}
}
mutate {
rename => {
"Path" => "[file][name]"
"ScriptBlockText" => "[ps][script_block][text]"
"ScriptBlockId" => "[ps][script_block][id]"
"MessageNumber" => "[ps][script_block][msg_num]"
"MessageTotal" => "[ps][script_block][msg_total]"
}
copy => { "Domain" => "[src_user][domain]"}
}
# Fingerprint the Script Block Text ---- useful for finding reoccuring scripts we want to exclude
fingerprint {
source => [ "[ps][script_block][text]" ]
method => "SHA1"
target => "[@meta][fp][ps][script_block][sha1]"
key => "logstash"
}
# Fingerprint Script Block Text and UserID/[@meta][src_user][sid] ---- because sometimes certain accounts should not run certain scripts, so filtering just Script Block Text could be a problem. Also, don't want to use AccountName because a local user with $X name could have same name as a domain user!
fingerprint {
source => [ "[ps][script_block][text]", "[@meta][src_user][sid]" ]
concatenate_sources => true
method => "SHA1"
target => "[@meta][fp][ps][script_block_and_sid][sha1]"
key => "logstash"
}
# Fingerprint UserID/[@meta][src_user][sid] and FileName---- because ScriptBlockText gets chopped up sometimes. Use this with caution for filtering
if [file][name] {
fingerprint {
source => [ "[file][name]", "[@meta][src_user][sid]" ]
concatenate_sources => true
method => "SHA1"
target => "[@meta][fp][ps][file_name_and_sid][sha1]"
key => "logstash"
}
}
}
}
}
}