An attacker spent three weeks inside a corporate network. They created a backdoor user, installed a persistence service, and cleared the security log before leaving. Every single action generated a Windows Event — but nobody was watching.
TL;DR
- Windows logs security-critical events natively — but default audit settings miss most of them
- Around 15-20 Event IDs cover 90% of what you actually need for threat detection
- PowerShell
Get-WinEventlets you filter, correlate, and export logs in seconds- Logon Type is the single most underused field in Event ID 4624/4625
- You can export structured CSVs and drop them directly into the SOC Log Analyzer for automated threat detection
Why Windows Event Logs Are Your Most Reliable Forensic Source
Every Windows system — from a home laptop to a Server 2022 domain controller — keeps a detailed record of security-relevant events. Unlike network logs, which require external infrastructure, or EDR alerts, which require a commercial product, Event Logs are built-in and always on.
They’re also frequently ignored until something goes wrong.
This guide is for defenders, SOC analysts, and sysadmins who want to actually use those logs — not just know they exist.
Understanding the Log Architecture
Windows organizes logs into three classic categories plus several modern application-specific channels:
| Log | Location | Contains |
|---|---|---|
| Security | Security | Logons, account changes, privilege use, audit policy |
| System | System | Service installs, driver loads, system errors |
| Application | Application | Application-level events (varies by software) |
| PowerShell | Microsoft-Windows-PowerShell/Operational | Script execution, module loads |
| Task Scheduler | Microsoft-Windows-TaskScheduler/Operational | Scheduled task creation and execution |
| Sysmon | Microsoft-Windows-Sysmon/Operational | Process creation, network connections, registry (requires Sysmon install) |
The Security log is where the vast majority of relevant security events live. Everything else is secondary context.
Where logs are stored
C:\Windows\System32\winevt\Logs\├── Security.evtx├── System.evtx├── Application.evtx└── Microsoft-Windows-Sysmon%4Operational.evtxThese are .evtx files — a binary format you cannot read with a text editor. Use Event Viewer, PowerShell, or a tool like FullEventLogView to parse them.
Enable the Right Audit Policies First
Default audit settings on Windows 10, 11, and Server 2019/2022 log less than you’d expect. Before anything else, verify what’s actually being captured.
# Check current audit policy settingsauditpol /get /category:*The most critical categories to enable:
:: Run cmd or PowerShell as Administrator:: Using GUIDs — these work on all Windows languages (EN, FI, DE, etc.)
:: Logon events (4624, 4625)auditpol /set /subcategory:"{0CCE9215-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable
:: Credential Validation (4776, 4777)auditpol /set /subcategory:"{0CCE923F-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable
:: User Account Management (4720, 4722, 4725, 4738, 4740)auditpol /set /subcategory:"{0CCE9235-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable
:: Security Group Management (4728, 4732, 4756)auditpol /set /subcategory:"{0CCE9237-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable
:: Process Creation (4688)auditpol /set /subcategory:"{0CCE922B-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable
:: Audit Policy Change (4719)auditpol /set /subcategory:"{0CCE922F-69AE-11D9-BED3-505054503030}" /success:enable /failure:enable
:: Sensitive Privilege Use (4672)auditpol /set /subcategory:"{0CCE9228-69AE-11D9-BED3-505054503030}" /success:enable /failure:enableNote: Subcategory names passed to
auditpolare language-sensitive — they must match the OS language exactly. GUIDs are language-independent and work identically on English, Finnish, German, or any other Windows locale.
Additionally, enable process command line logging — without this, Event ID 4688 only shows the process name, not its arguments:
# Enable command line in process creation events (GPO path or registry)Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Audit" ` -Name "ProcessCreationIncludeCmdLine_Enabled" -Value 1 -Type DWordWindows version note: Group Policy paths for Advanced Audit Policy are identical across Windows 10, 11, Server 2019, and Server 2022. Domain environments should configure these through GPO (
Computer Configuration → Windows Settings → Security Settings → Advanced Audit Policy Configuration).
The Event IDs That Actually Matter
Authentication and Logons
These are your bread and butter for detecting unauthorized access, lateral movement, and brute force.
Event ID 4624 — Successful Logon
Generated every time a logon succeeds. The Logon Type field is critical:
| Logon Type | Meaning | Why it matters |
|---|---|---|
| 2 | Interactive (local console) | User sat at the keyboard |
| 3 | Network | SMB, file share, WMI access |
| 4 | Batch | Scheduled task ran |
| 5 | Service | A service started |
| 7 | Unlock | Workstation unlocked |
| 8 | NetworkCleartext | Credentials sent in cleartext (legacy web forms, BasicAuth) |
| 9 | NewCredentials | runas /netonly — common attacker technique |
| 10 | RemoteInteractive | RDP session — high priority to monitor |
| 11 | CachedInteractive | Offline logon using cached credentials |
A Logon Type 3 from an unusual source IP is lateral movement. Logon Type 10 is RDP — if you see this from an unexpected account or workstation, investigate.
Event ID 4625 — Failed Logon
The primary brute force indicator. The Sub Status Code tells you why the logon failed:
| Sub Status | Meaning |
|---|---|
0xC000006A | Wrong password — account exists |
0xC0000064 | Username doesn’t exist |
0xC000006D | Bad username or auth info |
0xC0000234 | Account locked out |
0xC0000072 | Account disabled |
0xC000015B | Account not granted logon type |
Multiple 0xC000006A failures = password spraying or brute force. Multiple 0xC0000064 failures = username enumeration.
Event ID 4648 — Logon with Explicit Credentials
Generated when a process uses credentials other than the current user’s — think runas, net use, or Pass-the-Hash tooling. Attackers regularly trigger this during lateral movement.
Event ID 4672 — Special Privileges Assigned to New Logon
Generated when an administrator-equivalent account logs on. The privileges listed include SeDebugPrivilege, SeTcbPrivilege, and similar. High-privilege logons outside business hours are worth investigating.
Account Management
Event ID 4720 — User Account Created
Any new account creation. In an environment without a provisioning system, this should be rare outside of your normal IT workflow.
Event ID 4728 / 4732 / 4756 — Member Added to Security Group
- 4728: Added to a global security group
- 4732: Added to a local security group
- 4756: Added to a universal security group
Attackers add themselves to Administrators, Remote Desktop Users, or Domain Admins after gaining access. Alert on any addition to privileged groups.
Event ID 4740 — Account Locked Out
Useful in bulk — a single lockout is noise, but 50 lockouts across 20 accounts in two minutes is a spraying attack in progress.
Process Execution
Event ID 4688 — New Process Created
Every process launch. Without command line logging enabled, you only see the executable path. With it enabled, you see the full command — which is the difference between seeing powershell.exe and seeing powershell.exe -enc JABjAGwAaQBlAG4AdA....
Watch for:
powershell.exe,cmd.exe,wscript.exe,cscript.exelaunched from unusual parent processes (e.g.,word.exespawningcmd.exe)- Base64-encoded arguments (
-enc,-EncodedCommand) - Execution from
%TEMP%,%APPDATA%, or user-writable directories - Living-off-the-land binaries:
certutil.exe,regsvr32.exe,mshta.exe,wmic.exe
Persistence
Event ID 4698 — Scheduled Task Created
One of the most common persistence mechanisms. The event includes the task name, XML definition, and creating user. A scheduled task created by a non-admin account or pointing to a temp directory is a red flag.
Event ID 7045 — New Service Installed (System log)
Recorded in the System log, not Security. Malware frequently installs itself as a service for persistence. The event includes the service name, binary path, and startup type.
Covering Tracks
Event ID 1102 — Audit Log Cleared (Security log)
The attacker cleared the Security log. This event itself is written to the now-empty log — it’s one of the few self-referential events Windows generates.
Event ID 104 — System Log Cleared (System log)
Same as 1102 but for the System log.
Any log clearing event should generate an immediate alert in a production environment.
PowerShell
Event ID 4104 — Script Block Logging
Captures the full text of executed PowerShell scripts, including dynamically generated code. This is where you’ll see deobfuscated payloads — PowerShell deobfuscates before logging.
Enable script block logging via GPO:
Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell→ Turn on PowerShell Script Block Logging: EnabledOr via registry:
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -ForceSet-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" ` -Name "EnableScriptBlockLogging" -Value 1Reading Logs with PowerShell
Event Viewer is useful for ad-hoc investigation, but for any real analysis you want PowerShell’s Get-WinEvent.
Basic filtering
# All failed logons in the last 24 hoursGet-WinEvent -FilterHashtable @{ LogName = 'Security' Id = 4625 StartTime = (Get-Date).AddHours(-24)} | Select-Object TimeCreated, MessageExtract specific fields from events
Events store structured data in Properties — each index maps to a specific field. This is faster than parsing the Message string.
# Failed logons with IP, username, and failure reasonGet-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} | ForEach-Object { [PSCustomObject]@{ Time = $_.TimeCreated Username = $_.Properties[5].Value # TargetUserName Domain = $_.Properties[6].Value # TargetDomainName LogonType = $_.Properties[10].Value # LogonType IP = $_.Properties[19].Value # IpAddress Port = $_.Properties[20].Value # IpPort SubStatus = '0x{0:X}' -f [int]$_.Properties[9].Value # SubStatus } }Detect brute force: IPs with many failures
$since = (Get-Date).AddHours(-1)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=$since} | ForEach-Object { $_.Properties[19].Value } | # IpAddress Where-Object { $_ -match '\d+\.\d+\.\d+\.\d+' } | Group-Object | Where-Object { $_.Count -ge 10 } | Sort-Object Count -Descending | Select-Object Name, Count | Format-Table -AutoSizeDetect username spraying: single IP hitting many accounts
$since = (Get-Date).AddHours(-1)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=$since} | ForEach-Object { [PSCustomObject]@{ IP = $_.Properties[19].Value User = $_.Properties[5].Value } } | Group-Object IP | ForEach-Object { [PSCustomObject]@{ IP = $_.Name UniqueUsers = ($_.Group.User | Sort-Object -Unique).Count TotalFails = $_.Count } } | Where-Object { $_.UniqueUsers -ge 5 } | Sort-Object UniqueUsers -DescendingDetect RDP sessions from unusual accounts
# Successful RDP logons (LogonType 10)Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} | Where-Object { $_.Properties[8].Value -eq 10 } | # LogonType = RemoteInteractive ForEach-Object { [PSCustomObject]@{ Time = $_.TimeCreated Username = $_.Properties[5].Value IP = $_.Properties[18].Value } } | Format-Table -AutoSizeFind new services installed in the last 7 days
Get-WinEvent -FilterHashtable @{ LogName = 'System' Id = 7045 StartTime = (Get-Date).AddDays(-7)} | ForEach-Object { [PSCustomObject]@{ Time = $_.TimeCreated ServiceName = $_.Properties[0].Value ImagePath = $_.Properties[1].Value StartType = $_.Properties[3].Value RunAs = $_.Properties[4].Value } } | Format-Table -AutoSizeAutomated Alerting: Scheduled Script
You don’t need a SIEM to get basic alerting. This script runs on schedule, checks for threats, and sends an email if anything looks suspicious.
# Schedule with Task Scheduler every 15 minutes
param( [string]$SmtpServer = "smtp.yourdomain.com", [string]$To = "soc@yourdomain.com", [string]$From = "alerts@yourdomain.com")
$since = (Get-Date).AddMinutes(-15)$threshold = 10 # failed logons per IP to trigger alert$alerts = @()
# --- Brute force check ---$failures = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=$since} -EA SilentlyContinue
if ($failures) { $bruteIPs = $failures | ForEach-Object { $_.Properties[19].Value } | Where-Object { $_ -match '\d+\.\d+\.\d+\.\d+' } | Group-Object | Where-Object { $_.Count -ge $threshold }
foreach ($ip in $bruteIPs) { $alerts += "BRUTE FORCE: $($ip.Name) — $($ip.Count) failed logons in 15 min" }}
# --- Log cleared check ---$cleared = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=1102; StartTime=$since} -EA SilentlyContinueif ($cleared) { $alerts += "LOG CLEARED: Security event log was cleared at $($cleared[0].TimeCreated)" }
# --- New service check ---$newSvc = Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045; StartTime=$since} -EA SilentlyContinueforeach ($svc in $newSvc) { $alerts += "NEW SERVICE: '$($svc.Properties[0].Value)' → $($svc.Properties[1].Value)"}
# --- Send if anything found ---if ($alerts.Count -gt 0) { $body = "Windows Security Alerts — $(Get-Date -f 'yyyy-MM-dd HH:mm')`n`n" + ($alerts -join "`n") Send-MailMessage -SmtpServer $SmtpServer -To $To -From $From ` -Subject "⚠️ Windows Security Alert: $($alerts.Count) finding(s)" -Body $body}Register it as a scheduled task:
$action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NonInteractive -File C:\Scripts\WinLogAlert.ps1"$trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 15) -Once -At (Get-Date)Register-ScheduledTask -TaskName "WinLogSecurityAlert" -Action $action -Trigger $trigger ` -RunLevel Highest -User "SYSTEM"Export to CSV for Tool-Based Analysis
If you want to analyze logs in a dedicated tool — including dropping them into the SOC Log Analyzer — export with properly named columns that the tool can automatically detect.
Failed logon export (brute force / spraying detection)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625} | ForEach-Object { [PSCustomObject]@{ timestamp = $_.TimeCreated.ToString("o") # ISO 8601 username = $_.Properties[5].Value ip = $_.Properties[19].Value status = "failure" logon_type= $_.Properties[10].Value substatus = '0x{0:X}' -f [int]$_.Properties[9].Value } } | Export-Csv -Path ".\failed_logons.csv" -NoTypeInformation -Encoding UTF8This exports columns named timestamp, username, ip, and status — exactly the column names the analyzer looks for. Drop the file in and it will automatically detect brute force patterns and username spraying.
Network logon export (lateral movement detection)
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4624} | Where-Object { $_.Properties[8].Value -in @(3, 10) } | # Network or RDP ForEach-Object { [PSCustomObject]@{ timestamp = $_.TimeCreated.ToString("o") username = $_.Properties[5].Value ip = $_.Properties[18].Value status = "success" logon_type = $_.Properties[8].Value } } | Export-Csv -Path ".\network_logons.csv" -NoTypeInformation -Encoding UTF8Windows Version Differences
| Feature | Windows 10 | Windows 11 | Server 2019 | Server 2022 |
|---|---|---|---|---|
| Advanced Audit Policy | ✓ | ✓ | ✓ | ✓ |
| PowerShell Script Block Logging | ✓ | ✓ | ✓ | ✓ |
| Default Security log size | 20 MB | 20 MB | 20 MB | 20 MB |
| Sysmon support | ✓ | ✓ | ✓ | ✓ |
| Windows Event Forwarding | ✓ | ✓ | ✓ | ✓ |
| Entra ID join event logging | Partial | ✓ | Partial | ✓ |
| Enhanced phishing protection events | ✗ | ✓ (22H2+) | ✗ | ✓ |
Increase log retention size — the default 20 MB fills up fast on active systems:
# Set Security log to 512 MB, overwrite oldest when fullwevtutil sl Security /ms:524288000 /rt:falsewevtutil sl System /ms:104857600 /rt:falseOr via PowerShell:
$log = Get-WinEvent -ListLog Security$log.MaximumSizeInBytes = 512MB$log.SaveChanges()Quick Reference: Event ID Cheat Sheet
| Event ID | Log | Meaning | Priority |
|---|---|---|---|
| 4624 | Security | Successful logon | Medium — filter by Logon Type |
| 4625 | Security | Failed logon | High — watch for volume |
| 4648 | Security | Logon with explicit credentials | High |
| 4672 | Security | Admin privilege logon | Medium |
| 4720 | Security | User account created | High |
| 4728/4732/4756 | Security | User added to security group | High |
| 4740 | Security | Account locked out | Medium |
| 4688 | Security | New process created | Medium — enable cmdline |
| 4698 | Security | Scheduled task created | High |
| 4702 | Security | Scheduled task updated | Medium |
| 1102 | Security | Security log cleared | Critical |
| 4719 | Security | Audit policy changed | Critical |
| 7045 | System | New service installed | High |
| 104 | System | System log cleared | Critical |
| 4104 | PowerShell | Script block executed | High — requires enabling |
What You Can Do Today
- Run
auditpol /get /category:*and check whether Logon and Account Management auditing is enabled with success and failure - Increase log sizes — 20 MB will be overwritten within hours on a busy server
- Enable command line logging for Event ID 4688 — it’s off by default and dramatically improves visibility
- Enable PowerShell Script Block Logging — required to catch PowerShell-based attacks
- Test your visibility — create a test user, add them to a group, then verify the events appear in Security log
- Export a sample and analyze it — run the failed logon export above, drop it into the SOC Log Analyzer, and see what it finds
- Monitor the Event Log service itself — attackers can crash the Windows Event Log service (
EventLogCrasher, patched but variants exist), making your SIEM blind. Alert on unexpected stops of theEventLogservice via System log or WMI monitoring - Use Chainsaw for rapid IR — if you’re responding to an incident and need to tear through large
.evtxfiles fast, Chainsaw parses them with Sigma rules and is significantly faster than PowerShell for bulk analysis
Related Posts
- AD Attack Chains: From Initial Access to Domain Admin — See exactly which Event IDs get triggered at each stage of an Active Directory attack, from initial access through domain compromise
- Threat Hunting with Wazuh — If you want centralized log collection and rule-based alerting, Wazuh ingests Windows Event Logs and has pre-built detection rules for most of the IDs covered here
- PathSentry: Detecting Windows PATH Hijacking — A deeper look at a specific persistence technique detectable through Event ID 4688 process creation events
- What It Really Takes to Become a True SOC Professional — Context for how Windows log analysis fits into the broader SOC skill set
Sources
- Microsoft Learn — Security Auditing Events
- Microsoft Learn — Events to Monitor (Appendix L)
- MITRE ATT&CK — Defense Evasion: Indicator Removal
- NSA/CISA — Event Forwarding Guidance
- Malware Archaeology — Windows Logging Cheat Sheets
- SwiftOnSecurity — Sysmon Config
- JPCERT — Windows Event Log Tips for Ransomware Detection
- WithSecure — Chainsaw: Rapid Windows Event Log Analysis
- Ultimate Windows Security — Event ID Encyclopedia