SIEM and security operations · Beginner
Detection as code with Sigma
Why it matters
Detection engineering, writing and maintaining the rules a SIEM runs, is a growing part of SOC work. Treating detections as code (versioned, reviewed, tested, portable between SIEMs) is how mature teams keep them accurate. Sigma is the open standard for this.
The concept
A Sigma rule is YAML: a
logsource(which logs), adetectionwith named selections and acondition, and metadata such aslevelandstatus. Converters ("backends") turn it into Splunk SPL, Microsoft Sentinel KQL, Elastic queries or SQL.Single-event rules match one log line, such as "a password login was accepted".
Correlations combine events: many failures followed by a success from the same source; activity from two countries within an hour. They catch attacks that single events miss.
Test detections against known-bad and known-good data before deploying, and measure how often they fire.
Map to MITRE ATT&CK so you know which attacker techniques you can and cannot see.
Worked example
The Sigma rule:
title: SSH password login accepted
id: 6f0b2a55-4c1e-4a4e-9d3b-2f6b8b1d9c01
status: experimental
description: A password (not key) login succeeded. On servers that should only allow keys, this is suspicious by itself.
logsource:
product: linux
service: sshd
detection:
selection:
event: accepted
method: password
condition: selection
fields: [ts, user, src_ip]
level: mediumParse the log into a table, convert the rule with pySigma's SQLite backend, and run it next to a correlation:
import re
import sqlite3
from datetime import datetime
from sigma.backends.sqlite import sqliteBackend
from sigma.collection import SigmaCollection
PATTERN = re.compile(
r"^(?P<ts>\w{3} \d{2} [\d:]{8}) (?P<host>\S+) sshd\[\d+\]: "
r"(?:(?P<result>Accepted|Failed) (?P<method>password|publickey) for (?P<user>\S+)|Invalid user (?P<invalid>\S+)) "
r"from (?P<src_ip>[\d.]+)"
)
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE logs (ts TEXT, host TEXT, event TEXT, method TEXT, user TEXT, src_ip TEXT)")
for line in open("auth.log"):
m = PATTERN.match(line)
if m:
event = m["result"].lower() if m["result"] else "invalid_user"
db.execute("INSERT INTO logs VALUES (?,?,?,?,?,?)",
(datetime.strptime("2026 " + m["ts"], "%Y %b %d %H:%M:%S").isoformat(sep=" "), m["host"], event, m["method"], m["user"] or m["invalid"], m["src_ip"]))
print("events parsed:", db.execute("SELECT COUNT(*) FROM logs").fetchone()[0])
# 1. A Sigma rule, converted to SQL by the SQLite backend.
rule = SigmaCollection.from_yaml(open("ssh_success_after_bruteforce.yml").read())
query = sqliteBackend().convert(rule)[0].replace("<TABLE_NAME>", "logs")
print("Sigma -> SQL:", query)
for row in db.execute(query):
print(" alert:", row[0], row[4], row[5])
# 2. A correlation a single-event rule cannot express: many failures, then a success, same source.
CORRELATION = """
WITH failures AS (
SELECT src_ip, user, COUNT(*) AS failed, MIN(ts) AS first_failure, MAX(ts) AS last_failure
FROM logs WHERE event = 'failed' GROUP BY src_ip, user HAVING COUNT(*) >= 10
)
SELECT f.src_ip, f.user, f.failed, f.first_failure, s.ts AS success_at
FROM failures f JOIN logs s
ON s.src_ip = f.src_ip AND s.user = f.user AND s.event = 'accepted' AND s.ts >= f.last_failure
"""
for row in db.execute(CORRELATION):
print(" HIGH: {} failures for '{}' from {} starting {}, then success at {}".format(row[2], row[1], row[0], row[3], row[4]))
print("top sources of failed or invalid logins:")
for row in db.execute("SELECT src_ip, COUNT(*) FROM logs WHERE event IN ('failed','invalid_user') GROUP BY src_ip ORDER BY 2 DESC LIMIT 3"):
print(" ", row)$ pip install pysigma pysigma-backend-sqlite
$ python3 detect.py
events parsed: 205
Sigma -> SQL: SELECT * FROM logs WHERE event='accepted' AND method='password'
alert: 2026-09-29 02:47:02 deploy 198.51.100.23
HIGH: 35 failures for 'deploy' from 198.51.100.23 starting 2026-09-29 02:40:00, then success at 2026-09-29 02:47:02
top sources of failed or invalid logins:
('203.0.113.50', 40)
('198.51.100.23', 35)
('10.20.1.22', 4)The Sigma rule was converted to SQL without writing any SQL by hand; the same file would convert to a Splunk or Sentinel query. It raises one alert, because this server's staff always use keys. The correlation adds what a single-event rule cannot: the success came right after 35 failures from the same address against the same account, which justifies a high severity. Tested versions of both used pySigma 1.5 and the SQLite backend 2.0.
Common mistakes
Rules nobody tests. A typo in a field name makes a rule silently never fire.
Rules without context. Write a description, a severity and triage steps in the rule itself.
Thresholds copied from blogs. Tune on your own data: how many failures does a normal day contain?
Only single-event rules. Many real attacks only show up as sequences.
Interview angle
"How would you detect a brute-force attack that succeeded?" (a correlation of failures followed by a success, by source and account, within a window). "How do you keep false positives down?" (baseline normal behaviour, allow-lists for known sources, tuning with data, and severity based on context). "What is Sigma and why use it?"
Practice task
Write a Sigma rule for "new user account created" (the
useraddline) and a matching test.Change the correlation to require the failures within 10 minutes of the success, and check it still fires.