Skip to content

Instantly share code, notes, and snippets.

@neu5ron
Last active August 13, 2018 18:50
Show Gist options
  • Save neu5ron/450289373db61d5c8d7378e79455ef07 to your computer and use it in GitHub Desktop.
Save neu5ron/450289373db61d5c8d7378e79455ef07 to your computer and use it in GitHub Desktop.
Windows PowerShell Logstash Parser. Parses EventID's 4103 and 4104. Hash Script Block Text ---- useful for finding reoccuring scripts we want to whitelist/blacklist. Hash Script Block Text and UserID ---- because sometimes certain accounts should not run certain scripts, so filtering just by hash could be a problem.
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
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 ---- 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]", "UserID" ]
concatenate_sources => true
method => "SHA1"
target => "[@meta][fp][ps][script_block_and_sid][sha1]"
key => "logstash"
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment