bokamba / logforge / parse / Windows Security Event

$ logforge parse windows-event

Parse Windows Security Event logs → regex, Grok, Wazuh & rsyslog

Windows Security events are the backbone of endpoint and domain detection, and they are natively a multi-line beast: on disk they live in the binary EVTX store and render as XML, with the human-readable message and a flat set of named EventData elements (SubjectUserName, TargetUserName, LogonType, IpAddress, and so on) rather than a single line of text. That XML/EVTX form is not what a text log parser sees. What THIS page targets is the flattened, single-line shape a SIEM pipeline actually ingests: a forwarder such as NXLog, Winlogbeat, or Snare reads the event and emits it as one line of key=value tokens (or JSON), collapsing the XML tree into EventID=4625 TargetUserName=admin LogonType=3 IpAddress=203.0.113.45 … . Be honest about this: if you are parsing raw EVTX you want an EVTX reader, not a regex — this parser is for the forwarded, line-oriented output.

The single most important field is EventID, because it names the event and dictates which other fields are present. 4624 is a successful logon and 4625 is a failed logon; 4634/4647 are logoff, 4672 is a privileged logon, 4720 is account creation, and 4688 is process creation. LogonType qualifies the 4624/4625 pair and is where a lot of the detection signal lives: 2 is an interactive (at-the-console) logon, 3 is a network logon (SMB, a share), 10 is RemoteInteractive (RDP), 4 is batch and 5 is service. TargetUserName is the account being authenticated, SubjectUserName is the account that requested it, and IpAddress plus WorkstationName give the origin. On a 4625 failure the Status/SubStatus carries an NTSTATUS code — 0xC000006D is a bad username or general logon failure, 0xC0000064 is 'user does not exist', 0xC000006A is a bad password, and 0xC0000234 is a locked-out account — which lets you distinguish password spraying from user enumeration.

Parsing traps are mostly artefacts of the flattening. The forwarder decides the key names and the delimiter, so IpAddress may be a dash ('-') for a local logon, WorkstationName can be blank, and free-text fields can themselves contain spaces or equals signs. The account fields are case-insensitive and machine accounts end in a '$'. For detection you correlate EventID + LogonType + TargetUserName + IpAddress: many 4625s from one IP against many usernames is spraying; repeated 4625 0xC0000064 is enumeration; a 4624 LogonType=10 from an unexpected source is suspicious RDP. Do not invent EventData field names the forwarder did not emit — extract what is actually on the line.

Open this in LogForge →

What a Windows Security Event line looks like

The key=value sample below is fed verbatim into the engine to produce every parser on this page.

EventID=4625 TargetUserName=admin LogonType=3 IpAddress=203.0.113.45 IpPort=51234 Status=0xC000006D WorkstationName=WKSTN-07
EventID=4624 TargetUserName=jdoe LogonType=2 IpAddress=192.0.2.10 IpPort=50022 Status=0xC0000064 WorkstationName=WKSTN-11

Detected fields

The engine classified this sample as kv and consolidated 7 fields across 2 lines. Fields marked literal were identical on every sample line, so they are baked into the pattern as anchors rather than captured.

  • eventid : number
  • targetusername : literal
  • logontype : number
  • ipaddress : ipv4
  • ipport : port
  • status : literal
  • workstationname : literal

Regex (named capture groups)

# sample: EventID=4625 TargetUserName=admin LogonType=3 IpAddress=203.0.113.45 IpPort=51234 Status=0xC000006D WorkstationName=WKSTN-07
# groups: eventid=4625, targetusername=admin, logontype=3, ipaddress=203.0.113.45, ipport=51234, status=0xC000006D, workstationname=WKSTN-07
^EventID=(?<eventid>-?\d+(?:\.\d+)?) TargetUserName=(?<targetusername>[A-Za-z]+) LogonType=(?<logontype>-?\d+(?:\.\d+)?) IpAddress=(?<ipaddress>\d{1,3}(?:\.\d{1,3}){3}) IpPort=(?<ipport>\d{1,5}) Status=(?<status>(?:\d+[A-Za-z]+\d+|\d+[A-Za-z]+\d+[A-Za-z]+)) WorkstationName=(?<workstationname>[A-Za-z]+-\d+)$

Grok pattern (Logstash / Elastic)

EventID=%{NUMBER:eventid} TargetUserName=%{NOTSPACE:targetusername} LogonType=%{NUMBER:logontype} IpAddress=%{IPV4:ipaddress} IpPort=%{INT:ipport} Status=%{NOTSPACE:status} WorkstationName=%{GREEDYDATA:workstationname}
  • note kv-structured input — consider the Logstash kv filter instead of (or after) grok

Wazuh decoder (OS_Regex XML)

<!--
  Generated by LogForge - Wazuh decoder (OS_Regex dialect, not PCRE)
  sample: EventID=4625 TargetUserName=admin LogonType=3 IpAddress=203.0.113.45 IpPort=51234 Status=0xC000006D WorkstationName=WKSTN-07
  test with: /var/ossec/bin/wazuh-logtest
-->

<decoder name="windows-event-kv">
  <prematch>^EventID=</prematch>
</decoder>

<decoder name="windows-event-kv">
  <parent>windows-event-kv</parent>
  <regex offset="after_parent">^(\d+) TargetUserName=(\w+) LogonType=(\d+) IpAddress=(\d+.\d+.\d+.\d+) IpPort=(\d+) Status=(\w+) WorkstationName=(\w+)</regex>
  <order>eventid, targetusername, logontype, ipaddress, ipport, status, workstationname</order>
</decoder>
  • note kv fields are extracted by same-named sibling decoders (offset="after_parent"), so per-line field order/absence is tolerated — the shared name is what makes Wazuh evaluate every sibling
  • note decoder order and prematch specificity may need site-specific tuning (other decoders in your ruleset can shadow these) — validate with /var/ossec/bin/wazuh-logtest

rsyslog template / liblognorm rulebase

version=2
# windows_event — liblognorm v2 rulebase (generated by LogForge)
# Usage with rsyslog (mmnormalize runs liblognorm):
#   module(load="mmnormalize")
#   action(type="mmnormalize" rulebase="/etc/rsyslog.d/windows_event.rb" useRawMsg="on")
# Literal "%" is escaped as "%%"; raw tabs are written as \x09.
rule=windows_event:EventID=%eventid:number% TargetUserName=%targetusername:word% LogonType=%logontype:number% IpAddress=%ipaddress:ipv4% IpPort=%ipport:number% Status=%status:word% WorkstationName=%workstationname:word%
  • note kv structure: rsyslog offers mmfields (fast, fixed single-char separator, untyped) and mmnormalize (this rulebase, typed fields + literal anchors); mmnormalize was chosen for typed extraction
  • note chosen parser types: eventid=number, targetusername=word, logontype=number, ipaddress=ipv4, ipport=number, status=word, workstationname=word

FAQ

Why does this parser target single-line Windows events instead of EVTX/XML?
Because raw Windows Security logs are binary EVTX that render as multi-line XML — a text regex cannot parse that reliably, and you should use an EVTX reader for it. In practice a SIEM ingests events after a forwarder (NXLog, Winlogbeat, Snare) has flattened each one to a single line of key=value or JSON. This page parses that forwarded single-line form, which is what actually hits your pipeline.
What does LogonType mean in event 4624 / 4625?
LogonType classifies how the logon happened: 2 is interactive (console), 3 is network (e.g. SMB/share access), 10 is RemoteInteractive (RDP), 4 is batch (scheduled task), and 5 is service. It is central to detection — a network or RDP logon from an unexpected source, or a burst of type-3 failures, reads very differently from a normal interactive login.
How do I tell password spraying from user enumeration in 4625 events?
Look at the NTSTATUS Status/SubStatus code plus the username spread. 0xC0000064 means the user does not exist (enumeration), 0xC000006A means the password was wrong for a real account, and 0xC000006D is a generic bad-username/logon failure. Many 4625s from one IpAddress across many distinct TargetUserNames indicates spraying; repeated 0xC0000064 against different names indicates enumeration.
Which fields matter most for Windows logon detection?
EventID (4624 success vs 4625 failure), LogonType, TargetUserName, SubjectUserName, IpAddress, and WorkstationName, plus Status/SubStatus on failures. Correlating EventID + LogonType + IpAddress + TargetUserName covers brute force, spraying, enumeration, and anomalous RDP or network logons.

Try it on your own Windows Security Event lines

Paste a few real lines, review the detected fields, and copy whichever format your stack needs. Free, no account, nothing uploaded.

Open this sample in LogForge →