Sigma Rules: The Complete Guide for Detection Engineers
Master Sigma from rule anatomy to SIEM conversion: logsources, field modifiers, condition logic, correlation rules and real detection examples for PowerShell abuse, LSASS access and persistence.
Introduction: Why Sigma?
Sigma is to log events what YARA is to files. It is a vendor-neutral, YAML-based language for describing detections in log data: process creations, authentication events, DNS queries, registry writes. You write the logic once, then convert it to Splunk SPL, Elastic queries, Microsoft Sentinel KQL, QRadar AQL, or a dozen other backends.
Before Sigma, detection content was locked inside each SIEM. A Splunk search could not help an Elastic shop. A vendor migration meant rewriting hundreds of searches by hand. Sigma broke that lock-in, and the public SigmaHQ repository now ships thousands of community rules covering everything from LOLBin abuse to domain-specific APT tradecraft.
If you already write YARA (see our complete YARA guide), the mental model transfers: declarative patterns, boolean conditions, metadata. The difference is the target. YARA matches bytes in files and memory. Sigma matches fields in structured log events.
1. Anatomy of a Sigma Rule
Every Sigma rule is a single YAML document with a fixed set of top-level attributes:
title: Suspicious Something Happened
id: 4e8d9cd2-6a30-4f8b-9561-1f5c2f8a03d1
status: experimental
description: Detects a suspicious thing worth triaging
references:
- https://mlab.sh/blog/sigma-rules-complete-guide
author: Mlab Team
date: 2026-02-07
tags:
- attack.execution
- attack.t1059.001
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\powershell.exe'
CommandLine|contains: '-nop'
condition: selection
falsepositives:
- Administrative scripts using -NoProfile
level: medium
The parts that matter:
| Attribute | Role |
|---|---|
title / id |
Human name and stable UUID. The UUID is what your pipeline tracks. |
status |
experimental, test, stable, deprecated, unsupported |
logsource |
Where the events come from (see section 2) |
detection |
What to match: named selections plus a condition |
tags |
MITRE ATT&CK mapping (attack.t1059.001), CVE tags, custom namespaces |
level |
informational, low, medium, high, critical |
falsepositives |
Known benign matches. Write these. Your future self will thank you. |
Two habits pay off immediately: always generate a fresh UUID per rule (duplicated IDs break rule management), and always tag ATT&CK techniques, because that is what feeds coverage mapping later.
2. The Logsource Taxonomy
The logsource block is Sigma's abstraction layer. Instead of naming a concrete index or table, you declare the kind of telemetry the rule needs, using up to three keys:
logsource:
product: windows # OS or platform: windows, linux, macos, aws, azure
category: process_creation # event class, product-agnostic
service: security # a specific log channel: security, sysmon, sshd
You rarely need all three. The most common combinations:
| Logsource | Typical backend mapping |
|---|---|
category: process_creation, product: windows |
Sysmon EID 1, Security 4688, EDR process tables |
category: process_access, product: windows |
Sysmon EID 10 |
category: image_load, product: windows |
Sysmon EID 7 |
category: registry_set, product: windows |
Sysmon EID 13 |
category: dns_query, product: windows |
Sysmon EID 22, DNS analytics logs |
service: security, product: windows |
Windows Security event log |
category: network_connection, product: windows |
Sysmon EID 3, firewall or EDR network events |
service: cloudtrail, product: aws |
AWS CloudTrail |
At conversion time, a pipeline resolves the abstract logsource into your concrete environment: index names, table names, and field renames. This is why the same rule can become index=sysmon EventCode=1 in one shop and DeviceProcessEvents in another. Get the logsource right and conversion is mechanical. Get it wrong and the rule silently queries the wrong data.
3. Detection Logic: Selections and Values
The detection block contains one or more named selections and exactly one condition that combines them.
Maps: AND within, list means OR
A selection written as a map requires all its field conditions to hold (logical AND). A list of values under one field means any of them (logical OR):
detection:
selection:
Image|endswith: # field with modifier
- '\certutil.exe' # OR
- '\bitsadmin.exe' # OR
CommandLine|contains: 'http' # AND with the Image condition
condition: selection
Reads as: process is certutil or bitsadmin, and the command line contains http.
Lists of maps: OR between blocks
A selection that is itself a list of maps matches if any map matches fully:
detection:
selection:
- Image|endswith: '\rundll32.exe'
CommandLine|contains: 'javascript:'
- Image|endswith: '\mshta.exe'
CommandLine|contains: 'vbscript:'
condition: selection
Wildcards, null, and keywords
Plain values support * (any characters) and ? (single character). Escape a literal wildcard with a backslash. Matching is case-insensitive by default, which is the correct default for Windows telemetry.
detection:
selection:
CommandLine: '*\\Users\\Public\\*' # wildcard match
ParentImage: null # field absent from the event
keywords:
- 'Invoke-Mimikatz' # full-text search, no field
condition: selection or keywords
Use keywords sparingly. Field-less matching is expensive on most backends and noisy everywhere.
4. Field Modifiers
Modifiers are appended to field names with a pipe and transform how the value is matched. They are the workhorse of practical Sigma writing.
| Modifier | Effect |
|---|---|
contains |
Value appears anywhere in the field |
startswith |
Field begins with the value |
endswith |
Field ends with the value (the standard way to match executable names) |
re |
Regular expression match |
base64 / base64offset |
Match the value in its base64-encoded forms |
windash |
Also match Windows dash variants of a flag |
cased |
Force case-sensitive matching |
all |
Require every value in a list, not just one |
cidr |
Match an IP field against a CIDR range |
lt, lte, gt, gte |
Numeric comparisons |
exists |
Field presence check (true / false) |
The ones worth explaining
base64offset solves a subtle problem: a string encoded inside a base64 blob has three possible encodings depending on its byte offset (0, 1, or 2 bytes into the stream shift the alignment). The modifier generates all three variants so you can hunt plaintext inside encoded PowerShell without decoding anything:
detection:
selection:
CommandLine|base64offset|contains:
- 'IEX'
- 'Invoke-Expression'
- 'DownloadString'
condition: selection
windash handles the fact that Windows tools accept /param, -param, and several Unicode dash characters interchangeably. Attackers rotate them precisely to dodge naive string matching. One value, all variants covered:
detection:
selection:
CommandLine|contains|windash: ' -y '
# also matches ' /y ' and Unicode dash forms
condition: selection
contains|all turns a value list from OR into AND, which is how you require several substrings in one command line:
detection:
selection:
CommandLine|contains|all:
- 'reg'
- 'save'
- 'hklm\sam'
condition: selection
re is powerful but costly. Every backend translates it differently and some translate it badly. Prefer contains/startswith/endswith chains; reach for regex only when structure matters, like matching a run of hex or a specific flag format.
5. Condition Syntax
The condition is a boolean expression over selection names. Full grammar in six lines:
condition: selection # single selection
condition: selection1 and selection2 # AND
condition: selection1 or selection2 # OR
condition: selection and not filter # exclusion, the most common shape
condition: 1 of selection_* # at least one selection matching the prefix
condition: all of selection_* and not 1 of filter_*
The quantifier forms are what keep complex rules readable:
1 of selection_*: at least one selection whose name starts withselection_matches. Use it to enumerate independent variants of the same behavior.all of selection_*: every prefixed selection must match. Use it to stack constraints (right process AND right flag AND right parent).all of them/1 of them: every, or any, selection in the rule. Convenient in small rules, risky in big ones, because the next person who adds a filter selection just broke your logic. Prefer explicit prefixes.
The not filter pattern is the backbone of tuning. Name your exclusions filter_ or filter_main_, keep them separate from the detection logic, and the rule stays maintainable:
condition: all of selection_* and not 1 of filter_*
One structural note: modern Sigma allows exactly one condition per rule. If you find yourself wanting two, you want two rules.
6. Real-World Rules
Three complete rules you can convert and deploy today. Each targets a behavior that appears in a large share of real intrusions.
6.1 PowerShell encoded command execution
Encoded commands (-EncodedCommand, abbreviated down to -e) are the single most abused PowerShell feature. Legitimate automation uses them too, so the rule targets the short, evasive spellings and pairs them with a filter for known orchestration parents.
title: PowerShell Encoded Command Execution
id: b9f6d1a2-3c47-4f0e-8f21-7d5a9e0c4b6f
status: test
description: |
Detects PowerShell started with an encoded command flag, a common
delivery mechanism for loaders, droppers and offensive frameworks.
references:
- https://attack.mitre.org/techniques/T1059/001/
author: Mlab Team
date: 2026-02-07
tags:
- attack.execution
- attack.t1059.001
- attack.defense-evasion
- attack.t1027.010
logsource:
category: process_creation
product: windows
detection:
selection_img:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
selection_flag:
CommandLine|contains|windash:
- ' -e '
- ' -en '
- ' -enc '
- ' -enco'
- ' -encodedcommand'
filter_parent:
ParentImage|endswith:
- '\CcmExec.exe'
- '\monitoringhost.exe'
condition: all of selection_* and not filter_parent
falsepositives:
- Configuration management and monitoring agents pushing encoded scripts
- Some installers wrap PowerShell payloads this way
level: medium
Triage tip: decode the payload before judging it. A -enc blob that decodes to a UTF-16 one-liner pulling a second stage over HTTP is an incident. One that decodes to a vendor inventory script is a tuning entry for filter_parent.
6.2 LSASS memory access (credential dumping)
Sysmon Event ID 10 records one process opening a handle to another. Tools like Mimikatz request specific access masks against lsass.exe; 0x1010 (PROCESS_VM_READ plus PROCESS_QUERY_INFORMATION) and its variants are the classic tells.
title: Suspicious LSASS Process Access
id: 7c2e5f8a-91d4-4b3a-a6e0-2f8c1d9b5e37
status: test
description: |
Detects processes opening lsass.exe with access rights typically
requested by credential dumping tools.
references:
- https://attack.mitre.org/techniques/T1003/001/
author: Mlab Team
date: 2026-02-07
tags:
- attack.credential-access
- attack.t1003.001
logsource:
category: process_access
product: windows
detection:
selection:
TargetImage|endswith: '\lsass.exe'
GrantedAccess|contains:
- '0x1010'
- '0x1410'
- '0x1438'
- '0x143a'
- '0x1fffff'
filter_known:
SourceImage|endswith:
- '\MsMpEng.exe'
- '\csrss.exe'
- '\wmiprvse.exe'
SourceImage|startswith: 'C:\Program Files\'
condition: selection and not filter_known
falsepositives:
- Security products, backup agents and some EDR sensors legitimately
read LSASS; baseline before alerting at high severity
level: high
This rule illustrates the honest tradeoff in every LSASS detection: too strict and you miss renamed dumpers, too loose and your AV pages you hourly. The filter block is where your environment-specific baseline lives, and it will grow.
6.3 Suspicious scheduled task creation
Scheduled tasks are cheap persistence. The signal is not schtasks.exe /create itself (admins run it constantly) but the combination with payloads executing from writable, user-controlled paths.
title: Scheduled Task Executing From Suspicious Path
id: e4a1c6d9-58fb-4c72-b0d3-9a6e2f4c8115
status: test
description: |
Detects creation of a scheduled task whose action points to a
user-writable or staging directory, a common persistence pattern.
references:
- https://attack.mitre.org/techniques/T1053/005/
author: Mlab Team
date: 2026-02-07
tags:
- attack.persistence
- attack.t1053.005
logsource:
category: process_creation
product: windows
detection:
selection_create:
Image|endswith: '\schtasks.exe'
CommandLine|contains|windash: ' -create '
selection_path:
CommandLine|contains:
- '\Users\Public\'
- '\AppData\Local\Temp\'
- '\AppData\Roaming\'
- '\Windows\Temp\'
- '\ProgramData\'
- '%temp%'
- '%appdata%'
condition: all of selection_*
falsepositives:
- Some legitimate installers register update tasks from ProgramData;
filter by known task names or signed parent installers
level: medium
Pair this with a registry_set rule watching \Schedule\TaskCache\Tree\ for task creation that bypasses schtasks.exe entirely (direct API calls), and you cover both procedures of the same technique.
7. Correlation Rules
Single events lie. Ten failed logons followed by a success tells a story no single event can. Sigma's correlation rules (specified in Sigma v2) express these multi-event patterns while staying backend-neutral.
A correlation rule references one or more base rules by name and adds aggregation logic:
title: Failed Logon
name: failed_logon
logsource:
service: security
product: windows
detection:
selection:
EventID: 4625
condition: selection
---
title: Password Spraying Pattern
id: 0f3a7b2c-d4e8-49a1-b5c6-8e2d1f9a4c70
status: test
correlation:
type: value_count
rules:
- failed_logon
group-by:
- IpAddress
timespan: 10m
condition:
gte: 20
field: TargetUserName
level: high
This fires when a single source IP generates failed logons against 20 or more distinct usernames within 10 minutes: the spraying signature, invisible to any per-event rule.
The correlation types:
| Type | Meaning |
|---|---|
event_count |
N or more matching events in the window |
value_count |
N or more distinct values of a field in the window |
temporal |
Several different rules all match within the window |
temporal_ordered |
Same, but in a required sequence |
A temporal_ordered correlation chaining "encoded PowerShell" then "LSASS access" from the same host within 30 minutes is a far higher-fidelity alert than either rule alone. Backend support varies (aggregations translate to stats in SPL, summarize in KQL), so verify what your converter emits before trusting it in production.
8. Converting Rules with sigma-cli
Rules are only useful once they run in your SIEM. The reference toolchain is sigma-cli with per-backend plugins:
pip install sigma-cli
# install the backends you need
sigma plugin install splunk
sigma plugin install elasticsearch
sigma plugin install azure
To Splunk SPL
sigma convert -t splunk -p sysmon powershell_encoded_command.yml
Output:
Image IN ("*\\powershell.exe", "*\\pwsh.exe")
CommandLine IN ("* -e *", "* -en *", "* -enc *", "* -enco*",
"* -encodedcommand*", "* /e *", "* /en *", "* /enc *", "* /enco*",
"* /encodedcommand*")
NOT ParentImage IN ("*\\CcmExec.exe", "*\\monitoringhost.exe")
Note what the windash modifier did: every dash flag was expanded into its slash twin automatically. The -p sysmon pipeline mapped the abstract process_creation logsource onto Sysmon field names; swap in a different pipeline and the same rule targets Windows 4688 or your EDR's schema instead.
To Microsoft Sentinel / Defender KQL
sigma plugin install kusto
sigma convert -t kusto -p microsoft_xdr lsass_access.yml
Output:
DeviceEvents
| where ActionType == "OpenProcessApiCall"
| where FileName =~ "lsass.exe"
| where not(InitiatingProcessFolderPath startswith "C:\\Program Files\\")
The pipeline did real work here: TargetImage became FileName within the right ActionType, and paths were rewritten to Defender's schema. This is exactly why hand-porting rules between SIEMs breeds bugs, and why the pipeline abstraction earns its complexity.
Other backends
The same pattern covers Elastic (-t lucene or -t esql), QRadar AQL, LogPoint, Grafana Loki and more. For CI, sigma check rules/ validates syntax and metadata before anything converts.
If you want this loop without local tooling, hunt.mlab.sh runs it in the browser: write the YAML, get instant validation, and convert to Splunk, Elastic, QRadar or Sentinel from the same screen. It also tracks which ATT&CK techniques your rule set covers, which turns a pile of rules into a measurable detection posture. The mlab.sh API exposes the same conversion for pipelines.
9. Testing Sigma Rules
A rule that has never matched a positive sample is a hypothesis, not a detection.
Test against known-bad events. Projects like EVTX-ATTACK-SAMPLES and Security-Datasets publish event logs from real attack executions. Replay them through your converted query. If your LSASS rule does not fire on a recorded Mimikatz run, fix the rule, not the sample.
Test against known-good volume. Run the query over a week of production data before enabling alerting. Count matches. A "medium" rule matching 4,000 times a day is not medium, it is broken or it needs filters.
Detonate the behavior yourself. Atomic Red Team maps small executable tests to ATT&CK techniques. Invoke-AtomicTest T1053.005 on a lab host gives you ground-truth events for the scheduled task rule in minutes.
Pin your pipeline. Conversion output depends on backend and pipeline versions. Snapshot the generated queries in version control so an upgraded converter cannot silently change what your SIEM runs. This pairs naturally with a detection-as-code workflow: rules in git, sigma check in CI, conversion as a build artifact.
10. Common Pitfalls
Wildcard prefixes everywhere. CommandLine|contains compiles to a leading-wildcard search on most backends. Sometimes unavoidable, but when you can anchor with startswith or endswith, do it. Your SIEM bill is a detection engineering metric.
Case assumptions. Sigma matching is case-insensitive, but a backend or field mapping can break that guarantee. If a rule mysteriously misses, check case handling in the generated query first.
Overfitting to one procedure. A rule keyed on the literal string mimikatz detects one tool name, not credential dumping. Aim at the invariant: the access mask, the target process, the parent-child relationship. Attackers rename binaries for free; they cannot rename what the OS logs about their behavior.
Ignoring the logsource contract. A beautiful process_access rule detects nothing if Sysmon EID 10 is not collected. Every rule implicitly assumes telemetry; audit that assumption per rule, per environment.
Filters that eat the detection. filter: Image|startswith: 'C:\Program Files\' sounds safe until an attacker drops their tool into a Program Files subdirectory. Make filters as narrow as the false positive they suppress, and no narrower than that.
Copy-pasting community rules blind. SigmaHQ rules are a superb starting corpus, but their level and filters reflect someone else's environment. Import, then baseline, then tune, in that order.
11. Resources
- SigmaHQ : github.com/SigmaHQ/sigma (rule repository) and sigmahq.io (specification)
- sigma-cli and pySigma : the reference conversion toolchain
- hunt.mlab.sh : browser-based Sigma and YARA authoring, validation, SIEM conversion and ATT&CK coverage tracking
- Atomic Red Team : executable tests for rule validation
- EVTX-ATTACK-SAMPLES : real attack event logs for replay testing
- actors.mlab.sh : 500+ threat actor profiles to decide which techniques deserve rules first
Sigma will not detect anything by itself. It is a contract between the person who understood an attack and every SIEM that will ever hunt for it. Write the logic once, write it well, and let the converters do the boring part.