Lab — Week 7: Detection and Monitoring
Due: 14 August 2026
Submission: GitLab repo secdevops-s26-<CECS>
- Overview
- Prerequisites
- Part 1: Wazuh Rules, Tuning, and Active Response
- Part 2: Suricata Integration and File Integrity Monitoring
- Submission
Overview
Build the detection layer on top of the preventive controls: knowing when an attack is happening, what it’s doing, and how to respond.
- Part 1 — Wazuh Rules, Tuning, and Active Response writes custom detection rules, tests them by triggering the attack from Kali, and configures an active response that automatically blocks a brute-force source.
- Part 2 — Suricata Integration and File Integrity Monitoring adds network-layer detection by integrating Suricata with Wazuh, writes a custom Suricata rule, and completes FIM — closing the final gaps in your detection stack.
Prerequisites
- Week 4 Part 2 complete — Wazuh agent active on ubuntu-server
- Week 6 Part 2 complete — nftables running on ubuntu-server (required for the firewall-drop active response in Part 1)
- Suricata installed on ubuntu-server (by Ansible) — for Part 2
Part 1: Wazuh Rules, Tuning, and Active Response
Built-in Wazuh rules cover standard Linux events well but know nothing about DVWA’s specific log format or your threat model’s priority threats. This part writes two custom detection rules, tests them by triggering the attack from Kali, and configures an active response that automatically blocks a brute-force source.
Custom Rule: DVWA Login Brute Force
-
On the Wazuh manager (10.10.10.30), create
/var/ossec/etc/rules/local_rules.xmlif it does not exist. - Write a rule pair:
- A parent rule (level 3, id in range 100001–100099) that matches any nginx access log line referencing
/dvwa/login.php - A child rule (level 10) using
frequencyandtimeframethat fires when the parent rule matches 5 or more times from the same source IP within 60 seconds
Keep those levels in that order. The parent matches a single login request, which is normal traffic; the composite is the attack. Giving the parent a higher level than the child inverts the severity of your own detection and buries the real alert under one alert per request. Use
<same_source_ip />on the child so five requests from five different addresses do not add up to a brute force.If your rule matches in
wazuh-logtestbut produces no alerts in production, suspect a built-in rule matching first: Wazuh’s rule 31108 (Ignored URLs) is level 0 and swallows web-access events before custom children see them.<if_sid>31100</if_sid>parents your rule under the access-log group, and you can override 31108 withoverwrite="yes"and a narrower pattern if it is eating your traffic. - A parent rule (level 3, id in range 100001–100099) that matches any nginx access log line referencing
- Test the rule syntax without restarting:
/var/ossec/bin/wazuh-logtestPaste a sample nginx log line for
/dvwa/login.phpand confirm the parent rule matches. -
Restart the manager:
sudo systemctl restart wazuh-manager - From Kali, trigger the brute-force rule (use the host port your nginx container publishes —
8080if you followed Week 3):for i in {1..7}; do curl -s -o /dev/null -X POST http://10.10.10.20:8080/dvwa/login.php \ -d "username=admin&password=wrong&Login=Login" done - Confirm the level-10 alert fires in the Wazuh dashboard. Take a screenshot showing the rule ID, level, and source IP.
Custom Rule: Unexpected Container Spawn
-
Write a second rule (level 12, id in range 100100–100199) in the same
local_rules.xmlthat fires when adocker runordocker execcommand appears in the auditd log outside of thedocker composeprocess tree.Use
<match>or<regex>targeting theexecvesyscall withdocker runin the arguments, and<if_matched_group>or<different_user>to distinguish ad-hoc runs from compose-managed ones if possible. -
Test from ubuntu-server:
docker run --rm hello-world -
Confirm the alert fires in the dashboard. Take a screenshot.
Active Response
- On the Wazuh manager, configure
firewall-dropto trigger when the DVWA brute-force rule (your level-10 rule) fires:<active-response> <command>firewall-drop</command> <location>local</location> <rules_id>YOUR_RULE_ID</rules_id> <timeout>3600</timeout> </active-response>Add this stanza to
/var/ossec/etc/ossec.confon the manager, inside<ossec_config>. -
Restart the manager:
sudo systemctl restart wazuh-manager -
From Kali, trigger the brute-force rule again (5+ POST requests in 60 seconds).
- On ubuntu-server, confirm the active response fired:
sudo tail -20 /var/ossec/logs/active-responses.log sudo nft list ruleset | grep 10.10.10.10 -
Take a screenshot showing the blocked IP in nftables.
- Unblock Kali:
sudo nft delete element inet ... @blocked_ips { 10.10.10.10 }or wait for the 3600-second timeout.
Deliverables
Create lab13/ in your repo containing:
local_rules.xml— both custom ruleswazuh-logtestoutput — showing the parent brute-force rule matching a sample log line- Brute-force alert screenshot — level-10 alert with correct rule ID and source IP
- Container spawn alert screenshot — level-12 alert for
docker run hello-world - Active response log —
active-responses.logentries showing the block action - nftables screenshot — Kali IP blocked after active response fires
lab13/lab13.md— for each rule: what it detects, which threat model entry it addresses, and one scenario that would evade it (honest gap analysis)
Part 2: Suricata Integration and File Integrity Monitoring
Network and host detection are complementary layers. Suricata sees packet contents; auditd sees syscalls. Neither sees everything the other does. This part adds network-layer detection by integrating Suricata EVE logs with Wazuh, writes a custom Suricata rule targeting a known attack pattern, and completes the FIM configuration to detect unauthorized changes to critical files — closing the final gaps in your detection stack.
Part 1 must be complete — Wazuh custom rules working.
Suricata Setup
- Enable and start Suricata in IDS mode on ubuntu-server, monitoring the interface that carries lab traffic. This is not the “primary” interface: the default route goes out the management NIC on 172.20.100.0/24, and Kali never appears there. Suricata must watch the interface holding the
10.10.10.20address, or it will run perfectly and see none of your attacks. Find it, do not guess it:ip -br addr show | grep 10\.10\.10\. # names the interface, e.g. ens19 or eth0 IFACE=$(ip -o -4 addr show | awk '$4 ~ /^10\.10\.10\./ {print $2}') sudo sed -i "0,/- interface: .*/s//- interface: $IFACE/" /etc/suricata/suricata.yaml sudo suricata -T -c /etc/suricata/suricata.yaml # validate before starting sudo systemctl enable --now suricata sudo suricata --build-info | grep "AF_PACKET" # confirm AF_PACKET supportIf
systemctl enable --now suricatareports that the control process exited with an error, the config failed validation —suricata -Tabove names the offending line, andjournalctl -xeu suricata.serviceshows the rest. - Confirm Suricata is writing EVE JSON:
sudo tail -5 /var/log/suricata/eve.json | jq .eve.jsonis JSON Lines — one complete JSON object per line, not one document.python3 -m json.toolparses a single document and fails on the second line withExtra data: line 2 column 1, which looks like Suricata produced garbage when in fact it produced several valid records.jqreads a stream and handles it correctly.
Wazuh–Suricata Integration
- On ubuntu-server, add the following to
/var/ossec/etc/ossec.confinside<ossec_config>:<localfile> <log_format>json</log_format> <location>/var/log/suricata/eve.json</location> </localfile> -
Restart the Wazuh agent:
sudo systemctl restart wazuh-agent - From Kali, generate traffic that will produce Suricata events:
nmap -sV 10.10.10.20 - Confirm Suricata alert events appear in the Wazuh dashboard filtered by
rule.groups: suricata. Take a screenshot.
Custom Suricata Rule
- Write a custom Suricata rule in
/etc/suricata/rules/local.rulesthat detects nmap SYN scans:alert tcp any any -> $HOME_NET any ( msg:"Nmap SYN scan detected"; flags:S,12; threshold:type threshold, track by_src, count 10, seconds 2; sid:9000001; rev:1; ) -
Reference the rule file in
/etc/suricata/suricata.yamlunderrule-files:. -
Reload Suricata rules without restarting:
sudo suricatasc -c ruleset-reload-rulesThe command was
reload-rulesin older releases; current Suricata documents it asruleset-reload-rules. If neither is accepted,sudo suricatasc -c command-listprints what your build supports. -
Trigger the rule from Kali:
nmap -sS 10.10.10.20 - Confirm the custom rule fires in both
eve.jsonand the Wazuh dashboard. Take a screenshot of the Wazuh alert showing your rule’smsgfield.
File Integrity Monitoring
- On ubuntu-server, configure Wazuh FIM in
/var/ossec/etc/ossec.confto monitor:/etc(all files,check_all="yes")- The directory containing your
docker-compose.yml,nginx.conf, andseccomp-dvwa.json /etc/audit/rules.d/— the auditd rules from Week 6, since disabling logging is a standard first move after a compromise
Set
<frequency>300</frequency>(5-minute check interval).Monitor the host-side Compose project directory rather than
/etc/nginx/. Your nginx runs in a container, so there is no/etc/nginx/nginx.confon ubuntu-server to watch — the file that matters is thenginx.confin your project directory, which is the one that gets copied into the image at build time. Files inside a running container are not visible to host FIM at all. -
Restart the Wazuh agent:
sudo systemctl restart wazuh-agent - Force an immediate FIM baseline scan. Run this on the manager (10.10.10.30) —
agent_controlis a manager-side tool — using the agent ID fromagent_control -l:sudo /var/ossec/bin/agent_control -r -u <agent-id> - Simulate post-compromise tampering from ubuntu-server itself, against a file FIM is actually watching:
echo "# tampered" | sudo tee -a ~/compose/nginx.conf # your project path -
Wait up to 5 minutes, or force a rescan. Confirm the FIM alert fires in the Wazuh dashboard. Take a screenshot showing the modified file path and the before/after hash.
A “file added” alert instead of “file modified” means the path was not in the baseline — you appended to a file that did not exist, so there is no previous hash to compare against. That is a sign you are watching the wrong path, not a successful test.
- Restore the file:
sudo sed -i '/# tampered/d' ~/compose/nginx.conf
Threat Model — Final
- In
lab02/threat-model.md, mark all remaining OPEN threats with either MITIGATED (and the control), DETECTED (and the Wazuh/Suricata rule), or ACCEPTED RISK (with rationale). By the end of this lab, every original threat entry must have a non-OPEN status.
Deliverables
Create lab14/ in your repo containing:
- Wazuh–Suricata screenshot — Suricata events visible in Security Events dashboard
local.rules— your custom Suricata rule- Custom rule alert screenshot — Wazuh alert showing your rule’s
msgfield triggered by nmap - FIM configuration — the
<syscheck>block fromossec.conf - FIM alert screenshot — Wazuh alert showing your monitored
nginx.confmodified (not added) with before/after hashes - Fully updated
lab02/threat-model.md— every entry resolved (MITIGATED, DETECTED, or ACCEPTED RISK with rationale) lab14/lab14.md— a brief gap analysis: which threats are detected but not prevented, and which remain accepted risk with the reason why
Submission
Commit all files for both parts — lab13/ and lab14/ — and push to your GitLab repo. The lab is considered submitted when the commit appears in GitLab before the due date.