Example Scenario: An internal host infected with malware begins making periodic outbound connections to suspicious external IP addresses over uncommon ports, such as 53, 8081, or 1337. These communications follow a beacon-like pattern, often involve low-volume payloads, and occur at regular intervals (e.g., every 60 seconds). These are typical indicators of command-and-control (C2) activity, where the host checks in with an attacker-controlled server for further instructions.
Categorical features: To improve detection accuracy, anomalies are categorized by source IP address (id.orig_h). This ensures that the anomaly model learns a distinct baseline for each internal host. Without this separation, the communication behavior of one host might obscure that of another, masking malicious activity. By assigning a categorical field per host, the detector can catch host-specific deviations, like one system suddenly reaching out to rare IPs or ports.
To simulate today’s data, each event’s timestamp is shifted to the current date while preserving its original time. This is done via the ingestion script malware_communication/wazuh_ingest.py, located in the soar-radar folder:
python3 malware_communication/wazuh_ingest.py
timestamp_readable field to today@timestamp, event_hour, and normalizes numeric fieldswazuh-ad-malware-c2-2025.07.17wazuh-ad-malware-c2-*.@timestamp, id.orig_h, id.resp_h, service, proto, orig_bytes, resp_bytesmalware-c2-detectorwazuh-ad-malware-c2-*@timestamp5m with 1m delay| Feature name | Method | Field | Notes |
|---|---|---|---|
conn_count |
count() |
uid |
Counts outbound connections per host |
bytes_sent |
sum() |
orig_bytes |
Total outbound payload volume |
bytes_received |
sum() |
resp_bytes |
Total inbound response volume |
unique_dst_ips |
cardinality() |
id.resp_h |
Number of distinct contacted IPs |
unique_dst_ports |
cardinality() |
id.resp_p |
Number of destination ports used |
The OpenSearch UI does not support cardinality() directly, which is why we use a custom expression:
"unique_dst_ips": {
"cardinality": {
"field": "id.resp_h.keyword"
}
}
"unique_dst_ports": {
"cardinality": {
"field": "id.resp_h.keyword"
}
}
Under Categorical field, select id.orig_h. This ensures that each host has its own model. Anomalous behavior is then evaluated relative to each host’s typical communication behavior.
Click Next, validate the features, and confirm the model starts without errors. Once created, the model will begin analyzing live data.
malware-c2-detector view, click Create monitor.Malware-Communication-DetectedCondition:
anomaly_grade ≥ 0.8 AND confidence ≥ 0.85
http://192.168.0.28:8888/opensearch-alertAction Payload:
{
"monitor": {"name": ""},
"trigger": {"name": ""},
"entity": "",
"periodStart": "",
"periodEnd": ""
}
ad_alerts_webhook.py in the Wazuh manager/var/log/ad_alerts.logAdd this to ossec.conf:
<localfile>
<log_format>syslog</log_format>
<location>/var/log/ad_alerts.log</location>
</localfile>
100304 to trigger enrichmentid.orig_h (internal host IP), period_start, period_endid.resp_h)!firewalldrop.sh <dst_ip>!terminate_c2.sh <dst_ip>!quarantine_host.sh (blocks all outbound except loopback)ossec.conf on the manager, register and bind only the ad_context_malcom_active_response.py script. Script can be found in /soar-radar/malware_communication/active_responses/.<ossec_config>
<!-- 1) Command declaration -->
<command>
<name>ad_enrich</name>
<executable>ad_context__malcom_active_response.py</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<!-- 2) Active-response binding -->
<active-response>
<disabled>no</disabled>
<command>ad_enrich</command>
<location>server</location>
<rules_id>100304</rules_id>
<timeout>120</timeout>
</active-response>
</ossec_config>
ad_context_malcom_active_response.py.active_responses directory to /var/ossec/active-response/bin in the Wazuh manager.Note: remember to update the Wazuh access credentials (username, password) in the script based on your setup. Here we simply default values from the official “factory settings” of Wazuh.
chmod 750 /var/ossec/active-response/bin/ad_context_active_response.py
chown root:wazuh /var/ossec/active-response/bin/ad_context_active_response.py
python3 -m pip install requests
firewalldrop.sh: blocks outbound connection to C2 IPterminate_c2.sh: kills processes with connections to C2 IPquarantine_host.sh: drops all outbound connections (isolates host)These scripts are passed JSON via stdin:
terminate_c2.sh receives the C2 IP via extra_args[0]quarantine_host.sh extracts user_keyword (host IP) from alert.datajqsudo apt update
sudo apt install -y jq
sudo cp quarantine_host.sh /var/ossec/active-response/bin/
sudo cp terminate_c2.sh /var/ossec/active-response/bin/
sudo cp write_contextual_logs_malcom_active_response.py /var/ossec/active-response/bin/
sudo chown root:wazuh /var/ossec/active-response/bin/quarantine_host.sh
sudo chmod 750 /var/ossec/active-response/bin/quarantine_host.sh
sudo chown root:wazuh /var/ossec/active-response/bin/terminate_c2.sh
sudo chmod 750 /var/ossec/active-response/bin/terminate_c2.sh
sudo chown root:wazuh /var/ossec/active-response/bin/write_contextual_logs_malcom_active_response.py
sudo chmod 750 /var/ossec/active-response/bin/write_contextual_logs_malcom_active_response.py
Edit /var/ossec/etc/local_internal_options.conf
wazuh_command.remote_commands=1
ossec.conf. Edit /var/ossec/etc/ossec.conf. Under <ossec_config>:<command>
<name>quarantine_host.sh</name>
<executable>quarantine_host.sh</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>terminate_c2.sh</name>
<executable>terminate_c2.sh</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>write_contextual_logs_malcom_active_response.py</name>
<executable>write_contextual_logs_malcom_active_response.py</executable>
<timeout_allowed>no</timeout_allowed>
</command>
Our anomaly detection uses thresholds to prioritize and differentiate alerts based on severity. Two tiers are applied:
Tier 1 – Monitor & Investigate:
anomaly_grade >= 0.8confidence >= 0.85Tier 2 – Active Mitigation:
anomaly_grade >= 0.95confidence >= 0.95terminate_c2.sh or quarantine_host.sh.These levels balance precision and recall. Lowering the threshold improves detection rate but may increase false positives. Conversely, stricter thresholds reduce noise but risk missing early indicators.
To ensure operational trust, all thresholds are empirically tuned using the labeled dataset and real-world behavioral validation.
In a scenario where an endpoint begins initiating outbound connections to unfamiliar IP addresses on unusual ports—consistent with beaconing behavior to a potential command-and-control (C2) server—the risk is quantified using the following risk model:
R = C × I
C is the anomaly detection model’s confidence score, used as a proxy for the likelihood of malicious behavior.I is the impact severity derived from the Common Vulnerability Scoring System (CVSS).In the case of C2 communication detection:
To translate this into actionable security tiers, we define thresholds:
Under this framework, high-confidence detection of stealthy C2 communications (e.g., regular outbound connections to unfamiliar hosts) elevates the risk score toward the high end of the scale, often triggering Tier 2 or Tier 3 depending on historical baselines. This method ensures that alert severity reflects both behavioral certainty and standardized impact—adhering to the principle of Likelihood × Impact while enabling consistent, interpretable threat prioritization.
The dataset originates from Kaggle - Network Malware Detection Connection Analysis. It is derived from the CTU-13 Botnet Traffic dataset, featuring labeled connection records representing benign and malicious communication traces.