<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Devyani Nandanwar</title>
    <description>The latest articles on DEV Community by Devyani Nandanwar (@devyani_nandanwar_5).</description>
    <link>https://dev.to/devyani_nandanwar_5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4176045%2Fe03d3b34-7b0d-4e69-81db-5b400f7d7806.jpg</url>
      <title>DEV Community: Devyani Nandanwar</title>
      <link>https://dev.to/devyani_nandanwar_5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devyani_nandanwar_5"/>
    <language>en</language>
    <item>
      <title>Detection Engineering Lab: Building a Wazuh SIEM and Writing a Custom Rule for SSH Brute Force</title>
      <dc:creator>Devyani Nandanwar</dc:creator>
      <pubDate>Sat, 10 Oct 2026 23:12:18 +0000</pubDate>
      <link>https://dev.to/devyani_nandanwar_5/detection-engineering-lab-building-a-wazuh-siem-and-writing-a-custom-rule-for-ssh-brute-force-12dd</link>
      <guid>https://dev.to/devyani_nandanwar_5/detection-engineering-lab-building-a-wazuh-siem-and-writing-a-custom-rule-for-ssh-brute-force-12dd</guid>
      <description>&lt;p&gt;This write-up documents a small SIEM detection lab I built to practice detection engineering from the defender's side: deploying a SIEM, generating adversary activity against a monitored host, validating that it is detected, and writing a custom correlation rule mapped to MITRE ATT&amp;amp;CK.&lt;/p&gt;

&lt;p&gt;Repository with the rule, configuration notes and all screenshots: &lt;a href="https://github.com/devyaninandanwar/wazuh-siem-detection-lab" rel="noopener noreferrer"&gt;https://github.com/devyaninandanwar/wazuh-siem-detection-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Objective&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To validate end to end that a SIEM can ingest endpoint logs, surface attacker activity, and support a custom detection, and to practice the triage thinking that goes with it, including recognizing a false positive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Three virtual machines in VirtualBox on a bridged network:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Host&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;wazuh-manager&lt;/td&gt;
&lt;td&gt;Wazuh server, indexer and dashboard (the SIEM)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;monitored-endpoint&lt;/td&gt;
&lt;td&gt;Ubuntu Server running OpenSSH and Apache, with the Wazuh agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kali Linux&lt;/td&gt;
&lt;td&gt;Adversary simulation host&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Wazuh agent on the endpoint collects local logs (journald for SSH authentication events, and the Apache access log) and forwards them to the manager. The manager decodes the events, evaluates them against its ruleset, and indexes alerts for the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi87zbf6dzr3p29ajanss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi87zbf6dzr3p29ajanss.png" alt="Dashboard showing one active agent" width="799" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Design decisions
&lt;/h2&gt;

&lt;p&gt;The lab uses three separate hosts instead of running everything on one machine, for four reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log integrity&lt;/strong&gt; &lt;br&gt;
An attacker who compromises a host can alter or delete its local logs. Forwarding events to a separate manager preserves the evidence even if the endpoint is compromised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Realistic data flow&lt;/strong&gt; &lt;br&gt;
In production, a SIEM collects telemetry from many hosts over the network. Separating the agent from the manager reproduces that flow end to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource isolation&lt;/strong&gt; &lt;br&gt;
The Wazuh stack (manager, indexer and dashboard) is memory-intensive. Keeping it off the endpoint avoids distorting the behavior of the host being monitored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observable attacker identity&lt;/strong&gt; &lt;br&gt;
Running the attacks from a dedicated Kali host means the alerts record a real source IP, which is the field an analyst uses to start an investigation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deployment issues
&lt;/h2&gt;

&lt;p&gt;Two infrastructure problems are worth recording, since they affect anyone running this on a laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt; &lt;br&gt;
The first manager VM had a 25 GB disk, and the all-in-one installer failed with "No space left on device". After rebuilding with 60 GB, the Ubuntu installer's LVM layout still allocated only about 29 GB to the root logical volume. The fix was &lt;code&gt;lvextend -l +100%FREE&lt;/code&gt; on the logical volume followed by &lt;code&gt;resize2fs&lt;/code&gt;, verified with &lt;code&gt;df -h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; &lt;br&gt;
With 4 GB of RAM, the Wazuh indexer, manager and dashboard compete for memory, and after a reboot some services failed to start. Starting the indexer first and the dependent services afterwards resolved it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Detection scenario 1: SSH brute force (MITRE ATT&amp;amp;CK T1110)
&lt;/h2&gt;

&lt;p&gt;Simulated from the Kali host with Hydra against the endpoint's SSH service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hydra &lt;span class="nt"&gt;-l&lt;/span&gt; ubuntu &lt;span class="nt"&gt;-P&lt;/span&gt; passwords.txt ssh://192.168.1.194
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fep8gk722yq92pjxeda0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fep8gk722yq92pjxeda0m.png" alt="Hydra attack from Kali (password redacted)" width="800" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent's authentication logs triggered Wazuh's built-in rule 5760 (sshd: authentication failed) repeatedly. The event detail includes the targeted account, the source IP and port, and the raw log line. Together, these give an analyst the data needed to start an investigation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvor1d93rwhu18xukkjka.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvor1d93rwhu18xukkjka.png" alt="Wazuh dashboard after the attack" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc50v6zooah5v37wkas70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc50v6zooah5v37wkas70.png" alt="Event detail for rule 5760 showing the source IP" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection scenario 2: Network reconnaissance (corresponds to MITRE ATT&amp;amp;CK T1595, Active Scanning)
&lt;/h2&gt;

&lt;p&gt;Before attacking a machine, an attacker usually scans it to find out what it is running. From the Kali host, I ran an Nmap service-version scan against the monitored endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nmap &lt;span class="nt"&gt;-sV&lt;/span&gt; 192.168.1.194
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint runs an Apache web server, so Nmap probed it too. Nmap deliberately requests web pages that don't exist, because the way a server answers for a missing page tells Nmap how that server behaves, and it needs that to interpret its other results. Apache returned a 404 error for each of these requests and recorded them in its access log on the endpoint.&lt;/p&gt;

&lt;p&gt;The Wazuh agent on the endpoint reads that log and forwards it to the manager. The manager flagged the 404 errors under its web rules (31101). The raw log line also shows the Nmap user agent, which tells you which tool made the requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb5g83987j8dt4z08b02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffb5g83987j8dt4z08b02.png" alt="Nmap detected through the Apache access log" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Detection engineering: a custom correlation rule
&lt;/h2&gt;

&lt;p&gt;The built-in rule 5760 alerts on every failed login. A single failure is normal operational noise, so alerting on it alone produces low-value alerts. The detection of interest is a burst of failures. I wrote a frequency-based correlation rule that fires when rule 5760 triggers five times within 120 seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"100002"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt; &lt;span class="na"&gt;frequency=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="na"&gt;timeframe=&lt;/span&gt;&lt;span class="s"&gt;"120"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;if_matched_sid&amp;gt;&lt;/span&gt;5760&lt;span class="nt"&gt;&amp;lt;/if_matched_sid&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;SSH brute-force attack detected&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;mitre&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;id&amp;gt;&lt;/span&gt;T1110&lt;span class="nt"&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/mitre&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;group&amp;gt;&lt;/span&gt;authentication_failures,pci_dss_11.4,mitre,&lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-running the Hydra attack triggered rule 100002 at level 10, confirming the detection works against the simulated activity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn9i2bpkwi94o797dy5n9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn9i2bpkwi94o797dy5n9.png" alt="Custom rule 100002 firing" width="799" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alert triage: a rootcheck false positive
&lt;/h2&gt;

&lt;p&gt;After deployment the SIEM raised 84 alerts for rule 510 (rootcheck), "Trojaned version of file detected", on system binaries such as &lt;code&gt;/usr/bin/md5sum&lt;/code&gt;. On a freshly built host this warrants investigation before dismissal. Reviewing the event showed the rootcheck signature is a generic string pattern that matches any file containing certain references (for example &lt;code&gt;/bin/sh&lt;/code&gt;), and no other indicators of compromise were present. My assessment was that this is a known false positive from generic rootcheck signatures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdntapl69sxv65dpkalrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdntapl69sxv65dpkalrp.png" alt="84 rootcheck alerts for rule 510" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqojb9vqeap44zzeimtcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqojb9vqeap44zzeimtcf.png" alt="Rule 510 event detail on /usr/bin/md5sum" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The appropriate response in an operational environment is a documented, scoped tuning exception for those files, not disabling the rule. I documented this approach but did not implement the exception in this lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadmap
&lt;/h2&gt;

&lt;p&gt;The lab is built to be extended. Planned next iterations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Same-source correlation&lt;/strong&gt; 
Tighten rule 100002 with &lt;code&gt;same_source_ip&lt;/code&gt; so it alerts only when the failures come from a single address, then retest against the same Hydra run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated response&lt;/strong&gt; 
Use Wazuh active response to block the offending IP once the rule fires, taking the lab from detection to containment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broader coverage&lt;/strong&gt; 
Add further ATT&amp;amp;CK techniques and log sources, such as file integrity monitoring and privilege escalation scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuning&lt;/strong&gt; 
Implement the scoped exception for the rootcheck false positive and document the before and after alert volume.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;The lab covered the full detection cycle: collecting endpoint logs in a SIEM, writing a rule for attacker behavior, validating it against simulated attacks, and triaging the alerts, including a false positive. &lt;/p&gt;

&lt;p&gt;Which attack techniques would you simulate next in a lab like this? Share your ideas in the comments!&lt;/p&gt;

</description>
      <category>security</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
