Repositório de estudo para integração do IDS SLIPS com o SIEM Wazuh.
Desafio_2_IA_BigData/
├── ml/ # Notebooks e modelos treinados
│ ├── 01_eda.ipynb
│ ├── 02_train_classifiers.ipynb
│ ├── 03_predict.py
│ ├── 04_lstm_anomaly.ipynb
│ ├── models/ # rf_darknet.joblib
│ └── metrics/ # classification_report, confusion_matrix, *.png
├── pipeline/ # Integrações Wazuh + Shuffle + SLIPS
│ ├── wazuh_rules/ # api_attacks.xml
│ ├── wazuh_decoders/ # slips.xml
│ ├── docker-compose.yml
│ ├── shuffle_workflow_wazuh.json
│ └── retrain_cron.sh
├── dashboard/ # Exports dos dashboards OpenSearch
│ └── wazuh_dashboards.ndjson
├── relatorio/ # Fonte .md + .odt final
├── pcaps/ # CICDarknet2020 (Dataset/PCAPs/)
└── docs/ # Diagramas, referências
A separação segue o princípio de separation of concerns: ML (offline), integração (runtime), apresentação (UI). Cada camada tem seu próprio versionamento em git, permitindo que um engenheiro reproduza o pipeline independentemente.
# requirements.txt
pandas>=2.0
numpy>=1.24
scikit-learn>=1.3
matplotlib>=3.7
seaborn>=0.12
shap>=0.42
jupyter>=1.0
joblib>=1.3
Bootstrap do ambiente (com uv):
cd /opt/Desafio_2_IA_BigData
uv venv .venv --python 3.11
source .venv/bin/activate
uv pip install -r requirements.txtimport pandas as pd, numpy as np, joblib
from pathlib import Path
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report
DATA = Path('pcaps/CICDarknet2020/Darknet.csv')
df = pd.read_csv(DATA, low_memory=False)
df = df.apply(pd.to_numeric, errors='coerce').fillna(0)
df = df.select_dtypes(include='number')
y = pd.read_csv(DATA, usecols=['Label'], low_memory=False)['Label']
Xtr, Xte, ytr, yte = train_test_split(df, y, test_size=0.2,
random_state=42, stratify=y)
rf = RandomForestClassifier(n_estimators=300, max_depth=20,
class_weight='balanced', n_jobs=-1, random_state=42)
rf.fit(Xtr, ytr)
ypred = rf.predict(Xte)
print(classification_report(yte, ypred, digits=4))
joblib.dump(rf, 'ml/models/rf_darknet.joblib')
joblib.dump(list(df.columns), 'ml/models/feature_names.json')Snippet LSTM/Autoencoder (apêndice, opcional):
import torch, torch.nn as nn
class LSTMAE(nn.Module):
def __init__(self, n_features, hidden=64):
super().__init__()
self.enc = nn.LSTM(n_features, hidden, batch_first=True)
self.dec = nn.LSTM(n_features, hidden, batch_first=True)
self.out = nn.Linear(hidden, n_features)
def forward(self, x):
_, (h, _) = self.enc(x)
h = h.repeat(x.size(1), 1, 1).permute(1, 0, 2)
return self.out(self.dec(h)[0])Regras customizadas (pipeline/wazuh_rules/api_attacks.xml):
<group name="api,">
<rule id="100100" level="10" frequency="10" timeframe="60">
<if_matched_sid>31100</if_matched_sid>
<regex>POST \/api\/v\d+\/(login|auth|token)</regex>
<description>API: brute-force / token abuse em /auth</description>
</rule>
<rule id="100110" level="12">
<decoded_as>json</decoded_as>
<field name="integration">modsecurity</field>
<field name="ruleId">^(942|941|932)$</field>
<description>API: WAF bloqueou SQLi/XSS/RCE</description>
</rule>
<rule id="100120" level="6">
<time>00:00-06:00</time>
<match>POST|PUT|DELETE</match>
<description>API: escrita fora do horário comercial (revisar)</description>
</rule>
</group>Decoder do SLIPS (pipeline/wazuh_decoders/slips.xml):
<decoder name="slips">
<program_name>slips</program_name>
</decoder>
<decoder name="slips-json">
<parent>slips</parent>
<json />
<order>srcip,dstip,srcport,dstport,profile,threat_level,evidence,confidence</order>
</decoder>Detector RCF failed-logins-anomaly:
{
"name": "failed-logins-anomaly",
"time_field": "timestamp",
"indices": ["wazuh-alerts-*"],
"filter_query": {
"bool": {
"must": [{"match_phrase": {"rule.id": "5503"}}]
}
},
"feature_attributes": [
{"feature_name": "failed_login_count", "feature_type": "count"}
],
"detection_interval": "10m"
}Detector RCF api-bursts-00h-06h:
{
"name": "api-bursts-00h-06h",
"time_field": "timestamp",
"indices": ["wazuh-alerts-*"],
"filter_query": {
"bool": {"must": [{"terms": {"rule.id": ["100100", "100110", "100120"]}}]}
},
"feature_attributes": [
{"feature_name": "api_writes_per_hour", "feature_type": "count"}
],
"detection_interval": "1h"
}Detector RCF tor-vpn-traffic-spike:
{
"name": "tor-vpn-traffic-spike",
"time_field": "timestamp",
"indices": ["wazuh-alerts-*"],
"filter_query": {
"bool": {
"must": [
{"match_phrase": {"decoder.name": "slips-json"}},
{"range": {"data.threat_level": {"gte": 3}}}
]
}
},
"feature_attributes": [
{"feature_name": "darknet_flows_per_min", "feature_type": "count"}
],
"detection_interval": "5m"
}version: "3.8"
# Wazuh 4.14 + Shuffle SOAR + SLIPS -- protótipo do pipeline
# Requisitos: 8 GB RAM, kernel >= 5.10 (cap_net_admin)
services:
wazuh.manager:
image: wazuh/wazuh-manager:4.14.2
hostname: wazuh-manager
network_mode: host
volumes:
- ./wazuh_rules/api_attacks.xml:/var/ossec/etc/rules/api_attacks.xml:ro
- ./wazuh_decoders:/var/ossec/etc/decoders:ro
- slips_alerts:/var/slips/output:ro
cap_drop: [ALL]
cap_add: [CHOWN, SETUID, SETGID]
shuffle:
image: ghcr.io/shuffle/shuffle:latest
ports: ["3443:3443"]
environment: [SHUFFLE_APP=shuffle]
depends_on: [wazuh.manager]
slips:
image: stratosphereips/slips:latest
command: ["python3", "slips.py", "-f", "/data/darknet.pcap",
"-o", "/var/slips/output", "-c", "/config/slips_darknet.yaml"]
volumes:
- ./pcaps:/data:ro
- slips_alerts:/var/slips/output
- ./slips_config:/config:ro
network_mode: host
cap_add: [NET_ADMIN]
volumes:
slips_alerts:Comando de inicialização (em ambiente de produção):
docker compose -f pipeline/docker-compose.yml up -d
docker compose ps # esperado: todos "healthy"Pseudo-workflow JSON (estrutura exportada do Shuffle UI):
{
"name": "Wazuh -> Shuffle -> Response",
"trigger": {"type": "webhook"},
"actions": [
{"name": "ParseAlert", "app": "Shuffle Tools", "action": "json"},
{"name": "AbuseIPDBLookup", "app": "AbuseIPDB", "action": "check"},
{"name": "SwitchScore", "app": "Shuffle Tools", "action": "if"},
{"name": "WazuhBlockIP", "app": "Wazuh", "action": "active-response"},
{"name": "SlackNotify", "app": "Slack", "action": "post-message"},
{"name": "JiraCreate", "app": "Jira", "action": "create-issue"}
]
}Lógica condicional do nó SwitchScore:
if abuse_confidence_score > 50:
call Wazuh.active-response(block IP)
call Slack.post-message(channel=#soc-alerts)
else if ml_confidence > 0.85:
call Wazuh.active-response(block IP)
else:
call Jira.create-issue(type=review, severity=medium)Configuração do webhook no Wazuh (/var/ossec/etc/ossec.conf):
<integration>
<name>shuffle</name>
<hook_url>https://<YOUR_SHUFFLE_URL>/api/v1/hooks/<HOOK_ID></hook_url>
<level>3</level>
<alert_format>json</alert_format>
</integration>