A SIEM succeeds only when collection, normalization, detection, investigation and response are designed together. Start with use cases, not a license: identify the behavior to detect, the evidence required, the owners of that evidence, retention needed for the investigation window, and the response decision the alert must support. Build a source register for identity, endpoint, DNS, proxy, firewall, email, cloud control plane and critical applications. For every source record the transport, parser, expected fields, event volume, time zone, retention, sensitivity, owner and a heartbeat that proves data is still arriving.
Common deployment sequence
Define the first three use cases.Example: password spray, privileged-group change and suspicious PowerShell. Name entities, fields, thresholds, allowed exceptions and the action an analyst should take.
Prepare time, identity and assets.Synchronize systems with NTP, normalize users and hosts, import asset criticality, and decide how NAT, proxies and service accounts will be resolved.
Onboard one source end to end.Send through the supported collector, verify parsing and timestamps, compare source counts with SIEM counts, inspect dropped or delayed events, then document the production path.
Normalize and quality-test.Map required fields to the platform schema, reject ambiguous field meanings, and create checks for missing hosts, null users, future timestamps, duplicates and sudden volume change.
Build, replay and tune.Test true-positive, false-positive and edge cases. Use owned, expiring allowlists and peer-group context rather than broad suppression.
Release with a runbook.Record rule owner, schedule, severity, MITRE mapping, evidence links, investigation steps, containment options, data dependencies and rollback.
Splunk: architecture and setup
A small deployment can use one Splunk Enterprise instance for search and indexing; production separates the search tier from the indexer tier. Universal Forwarders collect host data, Heavy Forwarders handle routing or parsing when required, indexers store searchable buckets, and Search Heads run SPL, dashboards and alerts. A Deployment Server manages forwarder applications; clustered indexers improve availability and search factor, and a Search Head Cluster supports multiple coordinated search heads. Parsing normally belongs on the first full Splunk instance receiving the stream, not on Universal Forwarders.
- Create indexes by retention and access need—for example
windows, network and cloud—instead of placing everything in main. - Install supported technology add-ons, configure inputs and outputs, then verify
host, source, sourcetype and timestamp extraction. - Map data to the Common Information Model so reusable detections can use normalized fields. Accelerate only the data models that justify the storage and compute cost.
- Use roles and index restrictions for least privilege; protect search-head knowledge objects and service accounts.
- Monitor forwarding queues, skipped searches, license volume, indexer capacity and source freshness before enabling production alerts.
[WinEventLog://Security]
disabled = 0
index = windows
whitelist = 4624,4625,4688,4728,4732,4756
[tcpout]
defaultGroup = primary_indexers
[tcpout:primary_indexers]
server = idx1.example.test:9997,idx2.example.test:9997
On the receiver, enable the Splunk-to-Splunk port, install the Windows add-on on the parsing/search tiers as its documentation requires, deploy the input through a forwarder app, and restart only the affected component. Verify with index=windows | stats count min(_time) max(_time) by host sourcetype; compare counts with the source host, inspect _internal for blocked queues, and roll back the deployment app if parsing or volume is wrong.
SPL 1 · Password spray across many users
Find a source that fails against many distinct accounts, then succeeds shortly afterward.
index=windows sourcetype=WinEventLog:Security (EventCode=4625 OR EventCode=4624) earliest=-20m
| sort 0 IpAddress _time
| streamstats current=f dc(eval(if(EventCode=4625,TargetUserName,null()))) as prior_failed_users
sum(eval(if(EventCode=4625,1,0))) as prior_failures by IpAddress
| where EventCode=4624 AND prior_failed_users >= 8 AND prior_failures >= 12
| table _time IpAddress TargetUserName prior_failed_users prior_failures
Tune out domain controllers, scanners and known federation proxies only with an owner and expiry. Validate whether IpAddress is the true client after load balancers. Test: replay 12 failures across 8 accounts and then one success from the same IP; expect one row only after the success.
SPL 2 · Encoded PowerShell with network follow-on
index=endpoint sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational
(EventCode=1 OR EventCode=3) earliest=-30m
| eventstats values(eval(if(EventCode=1,CommandLine,null()))) as process_cmd
values(eval(if(EventCode=1,ParentImage,null()))) as parent
min(eval(if(EventCode=1,_time,null()))) as process_time by host ProcessGuid
| eval process_cmd=lower(mvindex(process_cmd,0))
| where EventCode=3
AND (like(process_cmd,"%powershell%") OR like(process_cmd,"%pwsh%"))
AND (like(process_cmd,"%-enc%") OR like(process_cmd,"%frombase64string%"))
AND _time >= process_time AND _time <= process_time+300
| table process_time _time host User ProcessGuid parent process_cmd DestinationIp DestinationPort
Sysmon's process GUID avoids false joins when operating-system process IDs are reused. Investigate the parent, decoded command, signer, user context and destination. Test: a matching network event within five minutes after process creation returns one row; an earlier event, a later event, or the same PID with a different ProcessGuid does not.
SPL 3 · Privileged-group membership change
index=windows sourcetype=WinEventLog:Security (EventCode=4728 OR EventCode=4732 OR EventCode=4756)
| eval actor=coalesce(SubjectUserName,user), added=coalesce(MemberName,member), group=TargetUserName
| where NOT match(actor,"(?i)^svc_(iam|provisioning)$")
| table _time host actor added group EventCode
| sort - _time
Correlate the actor with the approved change, the target account's recent sign-ins and subsequent privileged actions. Test: an Event 4728 from an ordinary administrator appears; the named provisioning service account is excluded.
IBM QRadar: architecture and setup
QRadar separates collection, processing and investigation. Event Collectors receive syslog, JDBC, API and other protocols; Event Processors parse, normalize and correlate events; Flow Collectors/Processors summarize network conversations; the Console provides administration, searches and offense management. Data Nodes add storage and search capacity. Device Support Modules (DSMs) parse vendor logs into normalized properties, while custom properties extract fields that the DSM does not expose. The Custom Rules Engine correlates events, flows, reference sets, building blocks and asset context into offenses.
- Add a log source with the exact source type and protocol. Use a gateway only when many devices require aggregation and preserve the original source identifier.
- Verify events in Log Activity, check parsing status, event time, source/destination, username, QID and category. Fix unknown or stored events before writing rules.
- Populate network hierarchy, assets and reference sets so rules understand local networks, critical servers, approved scanners and privileged identities.
- Build reusable building blocks, then correlation rules. Set a meaningful rule response: offense naming, magnitude inputs, rule test order and limiter.
- Monitor EPS/FPM capacity, accumulator performance, deployment status, license drops, inactive log sources and offense closure reasons.
Concrete onboarding example: for Windows security events, install/configure WinCollect on an approved collector, then in QRadar open Admin → Data Sources → Events → Log Sources → Add. Choose the Microsoft Windows Security Event Log type, WinCollect protocol, a unique hostname/FQDN log-source identifier, target Event Collector and required credentials through the approved secret process. Save, deploy changes, generate a lab Event 4625, and confirm in Log Activity that the QID, username, source IP, event time and payload are correct. If it appears as Unknown/Stored, fix the DSM, protocol or identifier before correlation; if absent, check WinCollect status, network path, certificate/TLS where used, collector queues and license drops.
AQL 1 · Failed logins across distinct accounts
SELECT sourceip, COUNT(*) AS failures,
UNIQUECOUNT(username) AS distinct_users,
MIN(starttime) AS first_seen, MAX(starttime) AS last_seen
FROM events
WHERE category = 16003
AND username IS NOT NULL
GROUP BY sourceip
HAVING failures >= 12 AND distinct_users >= 8
LAST 20 MINUTES
Confirm the category/QID in the local deployment; normalized categories vary with DSM quality. Add successful authentication as a second rule condition to raise confidence. Test: 12 failed events for 8 usernames from one IP yield one aggregate; seven usernames remain below threshold.
AQL 2 · High-volume outbound transfer from a critical host
SELECT sourceip, destinationip, destinationport,
SUM(sourcebytes + destinationbytes) AS total_bytes,
COUNT(*) AS flows
FROM flows
WHERE REFERENCESETCONTAINS('Critical Servers', sourceip)
AND NOT INCIDR('10.0.0.0/8', destinationip)
GROUP BY sourceip, destinationip, destinationport
HAVING total_bytes > 1073741824
ORDER BY total_bytes DESC
LAST 1 HOURS
Baseline backup, patch and replication destinations first. Investigate DNS, proxy and endpoint evidence before calling the transfer exfiltration. Test: add a lab source to the Critical Servers reference set and generate flows totaling 1.1 GB to a public destination; expect one grouped row.
AQL 3 · Administrative change outside the approved source set
SELECT starttime, username, sourceip, destinationip,
QIDNAME(qid) AS event_name, UTF8(payload) AS raw_event
FROM events
WHERE REFERENCESETCONTAINS('Privileged Accounts', username)
AND NOT REFERENCESETCONTAINS('Approved Admin Hosts', sourceip)
AND (LOWER(QIDNAME(qid)) LIKE '%group%member%'
OR LOWER(QIDNAME(qid)) LIKE '%policy%change%')
ORDER BY starttime DESC
LAST 24 HOURS
Use building blocks for privileged identities and administrative actions so rule logic stays readable and consistently maintained. Test: a privileged lab account acting from an unapproved source appears; the same action from a member of Approved Admin Hosts does not.
Microsoft Sentinel: architecture and setup
Sentinel runs on an Azure Log Analytics workspace. Data connectors use Azure Monitor agents, diagnostic settings, native service APIs, syslog/CEF collectors or partner connectors. Data lands in native or custom tables; KQL powers hunting, workbooks and analytics rules. Scheduled, near-real-time and Microsoft security analytics create incidents; automation rules and Logic App playbooks enrich or respond. Watchlists provide reference data, UEBA adds entity behavior, and Content Hub packages connectors, rules and workbooks.
- Choose workspace boundaries around data residency, access, retention and operations. Apply RBAC at workspace/resource level and separate content authors from responders where practical.
- Enable required diagnostic settings and connectors, then confirm table,
TimeGenerated, identity, IP, result and resource fields. Use data collection rules and transformations deliberately. - Set table retention and archive from investigation requirements. Control cost with filtering, basic/analytics table choices where supported, and a monitored daily cap—not by silently losing critical logs.
- Develop in Logs, save as hunting queries, then promote tested logic to analytics rules with entity mappings, custom details, grouping and suppression only when justified.
- Connect automation after the incident schema is stable. Keep disruptive playbook actions behind explicit approval and managed identities with narrow permissions.
Concrete onboarding example: install the Microsoft Entra ID solution from Content Hub, open its data connector, satisfy the listed tenant permissions, connect sign-in and audit logs, then run SigninLogs | take 10. For Azure Activity, create a diagnostic setting or policy assignment that sends subscription activity to the same workspace and verify with AzureActivity | summarize count() by SubscriptionId. A connected status without rows is not success: check the selected tenant/subscriptions, workspace, permissions, diagnostic-setting destination and ingestion latency. Remove or disable only the new connector/setting to roll back without deleting historical data.
KQL 1 · Password spray followed by success
let lookback = 30m;
let sprays = SigninLogs
| where TimeGenerated > ago(lookback) and ResultType != "0"
| summarize FailedUsers=dcount(UserPrincipalName), Failures=count(), Targets=make_set(UserPrincipalName, 20)
by IPAddress, bin(TimeGenerated, 5m)
| where FailedUsers >= 8 and Failures >= 12;
let success = SigninLogs
| where TimeGenerated > ago(lookback) and ResultType == "0"
| project SuccessTime=TimeGenerated, IPAddress, SuccessfulUser=UserPrincipalName;
sprays
| join kind=inner success on IPAddress
| where SuccessTime between (TimeGenerated .. TimeGenerated + 15m)
| project SprayTime=TimeGenerated, IPAddress, FailedUsers, Failures, Targets, SuccessTime, SuccessfulUser
Map IP and account entities in the analytics rule. Review conditional-access result, device, application and user risk before containment. Test: failures in one five-minute bin followed by a success within 15 minutes produce one match; a success before the spray does not.
KQL 2 · Azure role assignment by a new or unusual actor
AzureActivity
| where TimeGenerated > ago(24h)
| where OperationNameValue =~ "MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE"
| extend Actor=tostring(Caller), Target=tostring(parse_json(Properties).resource)
| join kind=leftouter (
SigninLogs | where TimeGenerated > ago(24h)
| summarize SignInIPs=make_set(IPAddress), Apps=make_set(AppDisplayName) by UserPrincipalName
) on $left.Actor == $right.UserPrincipalName
| project TimeGenerated, Actor, Target, ResourceGroup, SubscriptionId, SignInIPs, Apps, ActivityStatusValue
Enrich with privileged-role watchlists, ticket identifiers if logged, and whether the role scope is subscription, resource group or resource. Test: a lab role-assignment write returns actor, scope and sign-in context; a read operation is excluded.
KQL 3 · Encoded PowerShell and external connection
let suspicious = DeviceProcessEvents
| where TimeGenerated > ago(1h)
| where FileName in~ ("powershell.exe","pwsh.exe")
| where ProcessCommandLine has_any (" -enc ","EncodedCommand","FromBase64String")
| project DeviceId, DeviceName, AccountName, ProcessId, ProcessCommandLine, ProcessTime=TimeGenerated;
suspicious
| join kind=leftouter (
DeviceNetworkEvents
| where TimeGenerated > ago(1h) and RemoteIPType == "Public"
| project DeviceId, InitiatingProcessId, RemoteIP, RemoteUrl, RemotePort, NetworkTime=TimeGenerated
) on DeviceId
| where InitiatingProcessId == ProcessId and NetworkTime between (ProcessTime .. ProcessTime + 10m)
| project ProcessTime, DeviceName, AccountName, ProcessCommandLine, RemoteIP, RemoteUrl, RemotePort
Decode only in an isolated analysis workflow; preserve the original command. Parent process, script origin, signer and network context separate administration from abuse. Test: an encoded command with a public connection inside ten minutes joins; a network event outside the window is excluded.
Operate detections as production code
| Gate | Required evidence | Failure response |
|---|
| Data health | Freshness, volume, parsing, clock and required-field checks | Disable misleading alerting or mark partial coverage; fix pipeline |
| Logic test | Known-positive replay, expected benign case, boundary case | Revise query; do not tune by hiding the entire source |
| Analyst usability | Entities, raw evidence, severity rationale and first steps | Add enrichment and update runbook |
| Change control | Version, reviewer, test results, release and rollback | Restore prior rule and document regression |