1. Wstęp i cel integracji
Współczesne centrum operacji bezpieczeństwa (SOC) musi radzić sobie z rosnącą liczbą alertów, ograniczonym czasem analityków i coraz bardziej zaawansowanymi zagrożeniami. Ręczna analiza każdego podejrzanego pliku jest niepraktyczna w środowiskach produkcyjnych, gdzie setki zdarzeń mogą pojawiać się codziennie.
Niniejszy dokument opisuje kompletną integrację trzech systemów, która automatyzuje triage zagrożeń plikowych: Wazuh SIEM wykrywa zmiany w systemie plików, VirusTotal weryfikuje pliki w bazie 66+ silników antywirusowych, a Gemini AI generuje szczegółową analizę zagrożenia w języku polskim — bez udziału analityka.
| CEL | Każdy złośliwy plik wykryty przez FIM na monitorowanym hoście jest automatycznie przesyłany do VirusTotal, a następnie analizowany przez Gemini AI, który generuje opis zagrożenia, ocenę ryzyka i zalecenia po polsku w ciągu kilku sekund od wykrycia. |
1.1 Korzyści z automatyzacji
Integracja przynosi wymierne korzyści operacyjne dla zespołów SOC:
- Redukcja czasu triage — analityk zamiast ręcznie badać każdy alert otrzymuje gotową analizę z oceną zagrożenia i rekomendacjami
- Kontekst w języku polskim — eliminacja bariery językowej przy interpretacji wyników anglojęzycznych narzędzi
- Automatyczne wzbogacanie alertów — każdy alert VirusTotal jest wzbogacany o dane z modelu językowego bez dodatkowej pracy
- Skalowalność — system działa 24/7 niezależnie od liczby alertów i dostępności analityków
- Integracja z istniejącą infrastrukturą — wszystkie wzbogacone alerty trafiają bezpośrednio do dashboardu Wazuh
- Zerowy koszt — oba serwisy API dostępne są na darmowych planach bez konieczności konfigurowania rozliczeń
2. Architektura systemu
Integracja opiera się na natywnym mechanizmie integracji Wazuh (wazuh-integratord) i składa się z czterech warstw funkcjonalnych. Każde zdarzenie przechodzi przez następujący łańcuch przetwarzania: wykrycie pliku przez FIM, weryfikacja w VirusTotal, analiza przez Gemini AI, wygenerowanie alertu SOC po polsku.
| Komponent | Rola | Model/Wersja | Koszt |
| Wazuh Manager | SIEM, FIM, koordynacja integracji | 4.x+ | Open source |
| Wazuh Agent | Monitoring hosta (Windows/Linux) | 4.x+ | Open source |
| VirusTotal API | Skanowanie pliku w 66+ silnikach AV | v2 public | 4 req/min, 500/dzień |
| Gemini AI API | Analiza zagrożenia po polsku | gemini-2.5-flash-lite | 15 req/min, 1000/dzień |
| custom-gemini.py | Skrypt integracyjny Python | Python 3.x | Bezpłatny |
2.1 Kluczowe reguły VirusTotal w Wazuh
Wbudowane reguły Wazuh dla VirusTotal znajdują się w pliku /var/ossec/ruleset/rules/0490-virustotal_rules.xml. Integracja Gemini jest wyzwalana wyłącznie przez regułę 87105:
| Rule ID | Level | Opis | Uwagi |
| 87100 | 0 | Parent rule VirusTotal | Reguła bazowa — nie generuje alertu |
| 87101 | 3 | Rate limit reached | Błąd 204 — przekroczono 4 req/min |
| 87102 | 3 | Check credentials | Błąd 403 — nieprawidłowy klucz API VT |
| 87103 | 3 | No records in VT | Plik nieznany bazie VirusTotal |
| 87104 | 3 | No positives found | Plik znany, żaden AV nie wykrył zagrożenia |
| 87105 | 12 | MALICIOUS FILE DETECTED | WYZWALA GEMINI — plik oznaczony jako złośliwy |
| 87106 | 3 | API timeout | Błąd 408 — timeout połączenia z VT |
| WAŻNE | Reguły 23503-23506 w Wazuh to reguły vulnerability-detector (CVE), NIE VirusTotal. Częsty błąd konfiguracyjny to podanie tych reguł w rule_id integracji Gemini, co powoduje że skrypt nigdy nie jest wywoływany. |
3. Wymagania wstępne i klucze API
Do działania integracji potrzebne są dwa klucze API — oba dostępne bezpłatnie:
| Serwis | Gdzie uzyskać klucz | Plan darmowy | Limity |
| VirusTotal | virustotal.com/gui/my-apikey | Tak — rejestracja email | 4 req/min, 500 req/dzień |
| Google Gemini | aistudio.google.com/apikey | Tak — bez billingu | 15 req/min, 1000 req/dzień |
| UWAGA 2026 | Modele gemini-2.0-flash i gemini-2.0-flash-lite zostały wycofane przez Google w marcu 2026. Należy używać modelu gemini-2.5-flash-lite. Billing w Google Cloud nie jest wymagany do korzystania z darmowego tieru. |
4. Konfiguracja — trzy pliki
4.1 Skrypt custom-gemini.py
Skrypt umieszczamy w katalogu /var/ossec/integrations/ z odpowiednimi uprawnieniami. Wazuh-integratord uruchamia go automatycznie gdy pojawi się alert z rule_id podanym w ossec.conf, przekazując klucz API jako argument wiersza poleceń.
# Utwórz skrypt do katalogu integracji Wazuh
nano /var/ossec/integrations/custom-gemini.py

#!/var/ossec/framework/python/bin/python3
import json
import sys
import time
import os
from socket import socket, AF_UNIX, SOCK_DGRAM
try:
import requests
except Exception:
print("No module 'requests' found. Install: pip3 install requests")
sys.exit(1)
# ---------- Config ----------
debug_enabled = False
pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
log_file = f'{pwd}/logs/integrations.log'
socket_addr = f'{pwd}/queue/sockets/queue'
now = time.strftime("%a %b %d %H:%M:%S %Z %Y")
# Gemini API config
GEMINI_MODEL = "gemini-2.5-flash-lite"
GEMINI_ENDPOINT = f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL}:generateContent"
# ---------- Helpers ----------
def debug(msg):
if debug_enabled:
msg = f"{now}: {msg}\n"
print(msg)
try:
with open(log_file, "a") as f:
f.write(str(msg))
except Exception:
pass
def send_event(msg, agent=None):
if not agent or agent.get("id") == "000":
string = '1:gemini:{0}'.format(json.dumps(msg))
else:
string = '1:[{0}] ({1}) {2}->gemini:{3}'.format(
agent.get("id"),
agent.get("name"),
agent.get("ip") if "ip" in agent else "any",
json.dumps(msg),
)
debug(string)
sock = socket(AF_UNIX, SOCK_DGRAM)
sock.connect(socket_addr)
sock.send(string.encode())
sock.close()
def first(items, default=""):
if isinstance(items, list) and items:
return items[0]
return default
# ---------- Severity helper ----------
def level_to_severity(level):
"""Mapuje poziom reguły Wazuh na czytelną nazwę severity."""
try:
level = int(level)
except (TypeError, ValueError):
return "Unknown"
if level >= 12:
return "Critical"
elif level >= 9:
return "High"
elif level >= 6:
return "Medium"
else:
return "Low"
# ---------- Prompting ----------
def build_prompt(alert):
"""
Buduje prompt dla alertów VirusTotal + FIM (reguła 87105).
"""
# --- Pola reguły Wazuh ---
rule = alert.get("rule", {}) or {}
# --- Pola syscheck (FIM) ---
syscheck = alert.get("syscheck", {}) or {}
file_path = syscheck.get("path") or ""
file_md5 = syscheck.get("md5_after") or syscheck.get("md5_before") or ""
file_sha1 = syscheck.get("sha1_after") or syscheck.get("sha1_before") or ""
file_sha256 = syscheck.get("sha256_after") or syscheck.get("sha256_before") or ""
file_size = syscheck.get("size_after") or syscheck.get("size_before") or ""
file_owner = syscheck.get("uname_after") or syscheck.get("uname_before") or ""
event_type = syscheck.get("event") or "" # added / modified / deleted
# --- Pola VirusTotal ---
data = alert.get("data", {}) or {}
vt = data.get("virustotal", {}) or {}
positives = vt.get("positives") or "0"
total = vt.get("total") or "0"
scan_date = vt.get("scan_date") or ""
permalink = vt.get("permalink") or ""
vt_found = str(vt.get("found", "")).lower()
# Ścieżka pliku z VT jeśli nie ma w syscheck
vt_source = vt.get("source", {}) or {}
if not file_path:
file_path = vt_source.get("file") or ""
if not file_md5:
file_md5 = vt_source.get("md5") or ""
if not file_sha1:
file_sha1 = vt_source.get("sha1") or vt.get("sha1") or ""
# Top detekcje (nazwy malware od vendorów AV)
scans = vt.get("scans", {}) or {}
detections = []
for vendor, result in scans.items():
if isinstance(result, dict) and result.get("detected"):
name = result.get("result") or ""
if name:
detections.append(f"{vendor}: {name}")
detections = detections[:10]
# --- Budowanie kontekstu ---
bits = ["To jest alert Wazuh FIM (File Integrity Monitoring) + VirusTotal."]
groups = ", ".join((rule.get("groups") or [])[:6])
bits.append(
f"Reguła: id={rule.get('id')}, poziom={rule.get('level')}, grupy=[{groups}]"
)
if rule.get("description"):
bits.append(f"Opis reguły: {rule.get('description')}")
if event_type:
bits.append(f"Typ zdarzenia FIM: {event_type}")
if file_path:
bits.append(f"Ścieżka pliku: {file_path}")
if file_size:
bits.append(f"Rozmiar pliku: {file_size} bajtów")
if file_owner:
bits.append(f"Właściciel pliku: {file_owner}")
if file_sha256:
bits.append(f"SHA256: {file_sha256}")
if file_sha1:
bits.append(f"SHA1: {file_sha1}")
if file_md5:
bits.append(f"MD5: {file_md5}")
bits.append(f"Detekcje VirusTotal: {positives}/{total} silników oznaczyło plik jako złośliwy")
if scan_date:
bits.append(f"Data skanu: {scan_date}")
if permalink:
bits.append(f"Raport VirusTotal: {permalink}")
if detections:
bits.append("Wykryte nazwy malware (próbka):\n" + "\n".join(f" - {d}" for d in detections))
elif vt_found == "false" or positives == "0":
bits.append("Żaden silnik antywirusowy nie oznaczył pliku jako złośliwego.")
context = "\n".join(bits)
prompt = (
"Jesteś analitykiem SOC wykonującym triage zagrożeń plikowych.\n"
"Na podstawie WYŁĄCZNIE podanych danych poniżej:\n"
"1) Oceń poziom zagrożenia tego pliku i określ jakiego typu malware lub zagrożenie prawdopodobnie reprezentuje.\n"
"2) Wyjaśnij jakie ryzyko ten plik stanowi dla zainfekowanego systemu.\n"
"3) Zaproponuj natychmiastowe działania zaradcze (np. kwarantanna, blokada hasha, dochodzenie).\n"
"4) Wskaż podejrzane wskaźniki w ścieżce pliku, nazwie lub lokalizacji.\n"
"Napisz 4-6 zwięzłych, konkretnych zdań. Nie wymyślaj szczegółów których nie ma w danych.\n\n"
f"{context}"
)
return prompt
# ---------- Gemini ----------
def call_gemini(prompt, api_key):
headers = {
"Content-Type": "application/json",
"x-goog-api-key": api_key,
}
payload = {
"contents": [
{
"parts": [
{"text": prompt}
]
}
]
}
try:
resp = requests.post(GEMINI_ENDPOINT, headers=headers, json=payload, timeout=30)
except requests.RequestException as e:
return {"error": "transport_error", "description": str(e)}
if resp.status_code != 200:
try:
j = resp.json()
desc = json.dumps(j)[:2048]
except Exception:
desc = resp.text[:2048]
return {"error": str(resp.status_code), "description": desc}
try:
j = resp.json()
text = ""
resp_id = j.get("responseId")
model_ver = j.get("modelVersion")
if "candidates" in j and j["candidates"]:
content = j["candidates"][0].get("content", {})
parts = content.get("parts", [])
if parts and isinstance(parts, list):
text = parts[0].get("text", "") or ""
return {
"text": text.strip(),
"response_id": resp_id,
"model_version": model_ver,
}
except Exception as e:
return {"error": "parse_error", "description": str(e)}
# ---------- Core ----------
def build_output(alert, prompt_used, gemini_result):
rule = alert.get("rule", {}) or {}
syscheck = alert.get("syscheck", {}) or {}
data = alert.get("data", {}) or {}
vt = data.get("virustotal", {}) or {}
vt_src = vt.get("source", {}) or {}
severity = level_to_severity(rule.get("level"))
# Ścieżka pliku — syscheck ma priorytet, fallback do VT source
file_path = syscheck.get("path") or vt_src.get("file") or ""
out = {
"integration": "custom-gemini",
"gemini": {
"found": 0,
"source": {
"alert_id": alert.get("id"),
"rule": str(rule.get("id", "")), # string — wymagany przez regex w local_rules
"description": rule.get("description"),
"level": rule.get("level"),
"severity": severity, # wypełnia $(gemini.source.severity)
"groups": rule.get("groups"),
"mitre": rule.get("mitre"),
"location": alert.get("location"),
"timestamp": alert.get("timestamp"),
# Pola FIM / VT (przydatne w dashboardach)
"file_path": file_path,
"vt_positives": vt.get("positives") or "0",
"vt_total": vt.get("total") or "0",
"permalink": vt.get("permalink") or "",
},
"prompt_used": prompt_used[:2000]
}
}
if gemini_result.get("error"):
out["gemini"]["error"] = gemini_result["error"]
out["gemini"]["error_description"] = gemini_result.get("description")
return out
text = gemini_result.get("text", "")
if text:
out["gemini"]["found"] = 1
out["gemini"]["summary"] = text
meta = {}
if gemini_result.get("response_id"):
meta["response_id"] = gemini_result["response_id"]
if gemini_result.get("model_version"):
meta["model_version"] = gemini_result["model_version"]
if meta:
out["gemini"]["model_meta"] = meta
else:
out["gemini"]["note"] = "Pusta lub zablokowana odpowiedź modelu."
return out
def process_alert(alert, api_key):
prompt = build_prompt(alert)
result = call_gemini(prompt, api_key)
return build_output(alert, prompt, result)
# ---------- Entrypoint ----------
def main(args):
debug("# Starting custom-gemini (virustotal/FIM) integration")
if len(args) < 3:
debug("# Exiting: Bad arguments.")
sys.exit(1)
alert_file = args[1]
api_key = args[2]
debug("# API key (masked): " + ((api_key[:5] + "...") if api_key else "MISSING"))
debug("# Alert file: " + alert_file)
with open(alert_file, "r") as f:
alert = json.load(f)
msg = process_alert(alert, api_key)
if msg:
send_event(msg, alert.get("agent"))
if __name__ == "__main__":
try:
if len(sys.argv) >= 4:
_msg = '{0} {1} {2} {3} {4}'.format(
now,
sys.argv[1],
sys.argv[2],
sys.argv[3] if len(sys.argv) > 3 else "",
sys.argv[4] if len(sys.argv) > 4 else ""
)
debug_enabled = (len(sys.argv) > 4 and sys.argv[4] == 'debug')
else:
_msg = f'{now} Wrong arguments'
with open(log_file, 'a') as f:
f.write(str(_msg) + '\n')
if len(sys.argv) < 3:
sys.exit(1)
main(sys.argv)
except Exception as e:
debug(str(e))
# Ustaw uprawnienia (wymagane przez wazuh-integratord)
chmod 750 /var/ossec/integrations/custom-gemini.py
chown root:wazuh /var/ossec/integrations/custom-gemini.py
# Weryfikacja — oczekiwany wynik:
# -rwxr-x— 1 root wazuh XXXX custom-gemini.py

Kluczowe funkcje skryptu
- build_prompt() — wyciąga dane z alertu (ścieżka pliku, hashe MD5/SHA1/SHA256, wyniki VT) i buduje prompt po polsku
- call_gemini() — wysyła zapytanie HTTP do API Gemini i obsługuje błędy: rate limiting (429), wycofany model (404), timeout
- build_output() — buduje strukturę JSON z polami gemini.source.rule (string!), gemini.source.severity i gemini.summary
- level_to_severity() — mapuje poziom reguły Wazuh na: Critical (12+), High (9-11), Medium (6-8), Low (1-5)
- send_event() — wysyła wzbogacony alert do kolejki Wazuh przez Unix socket — analysisd dopasowuje go do reguły 100210
| TECHNICZNIE | Wazuh automatycznie przekazuje klucz API z ossec.conf jako args[2] do skryptu. Nigdy nie należy hardkodować kluczy bezpośrednio w skrypcie. Hook_url jest dostępny jako args[3] — skrypt może go odczytywać dynamicznie lub używać stałej. |
4.2 Konfiguracja ossec.conf
W pliku /var/ossec/etc/ossec.conf należy dodać dwa bloki integracji:
Integracja VirusTotal (monitoruje wszystkie zdarzenia FIM)
nano /var/ossec/etc/ossec.conf
<integration>
<n>virustotal</n>
<api_key>TWÓJ_KLUCZ_VIRUSTOTAL</api_key>
<!-- Wyzwalana dla wszystkich zdarzeń syscheck (FIM) -->
<group>syscheck</group>
<alert_format>json</alert_format>
</integration>
Koniecznie pod sekcją Osquery integration

Po zarejestrowaniu się i zalogowaniu na stronie virustotal.com, przejdź do Swojego konta i kliknij API Key

Po przekierowaniu strony skopiuj klucz i wklej go pomiędzy <api_key>TWÓJ_KLUCZ_VIRUSTOTAL</api_key>

Integracja Gemini (wyzwalana tylko przez rule 87105)
Pod integracją VirusTotal

<integration>
<n>custom-gemini.py</n>
<hook_url>https://generativelanguage.googleapis.com/v1beta/
models/gemini-2.5-flash-lite:generateContent</hook_url>
<api_key>TWÓJ_KLUCZ_GEMINI</api_key>
<!-- Wyzwalana TYLKO gdy VT wykryje złośliwy plik (87105) -->
<rule_id>87105</rule_id>
<alert_format>json</alert_format>
</integration>
Po zarejestrowaniu i zalogowaniu się na https://aistudio.google.com/ przejdź pod adresi utwórz klucz, klikając na Create API key:
https://aistudio.google.com/apikey

Po utworzeniu klucza kliknij na (przykład) i go skopiuj pomiędzy <api_key>TWÓJ_KLUCZ_GEMINI</api_key>:


4.3 Reguły w local_rules.xml
Plik /var/ossec/etc/rules/local_rules.xml definiuje regułę dopasowującą alerty zwrócone przez skrypt Gemini:
nano /var/ossec/etc/rules/local_rules.xml
<rule id="100210" level="13">
<field name="gemini.source.rule">^87105$</field>
<field name="gemini.summary">\.+</field>
<description>[Gemini - $(gemini.source.severity) severity] $(gemini.source.description)</description>
<options>no_full_log</options>
</rule>

5. Szczegółowe działanie — co się dzieje krok po kroku
Poniżej opis pełnego cyklu życia alertu od wykrycia pliku do pojawienia się analizy Gemini w Wazuh:
Krok 1 — FIM wykrywa plik
Agent Wazuh monitoruje katalogi zdefiniowane w sekcji <syscheck> ossec.conf agenta. Gdy plik pojawia się w monitorowanym katalogu (np. C:\Users\…\Downloads\), agent generuje alert syscheck z hashem SHA256 pliku.
Krok 2 — VirusTotal skanuje hash
Wazuh-integratord odbiera alert syscheck i przekazuje hash pliku do API VirusTotal. VT odpowiada listą wyników od poszczególnych silników AV. Jeśli plik jest złośliwy, wazuh-analysisd generuje alert z regułą 87105 (level 12).
Krok 3 — Gemini analizuje alert
Wazuh-integratord wykrywa alert 87105 i uruchamia skrypt custom-gemini.py z plikiem JSON alertu i kluczem API. Skrypt buduje prompt z danych alertu i wysyła go do API Gemini. Gemini zwraca analizę po polsku w ciągu 2-5 sekund.
Krok 4 — Alert SOC z analizą
Skrypt wysyła wynik do kolejki Wazuh przez Unix socket. Analysisd dopasowuje go do reguły 100210 (level 13) i tworzy finalny alert widoczny w Security Events z pełną analizą Gemini w opisie.
| PRZYKŁAD ALERTU | [Gemini – Critical severity] VirusTotal: Alert – c:\users\arek\downloads\plik.exe – 63 engines detected this file | Pole gemini.summary: Plik wykryty przez 63/66 silników AV reprezentuje poważne zagrożenie — prawdopodobnie trojan lub ransomware. Lokalizacja w katalogu Downloads użytkownika wskazuje na pobranie z internetu. Zaleca się natychmiastową kwarantannę hosta, izolację od sieci i blokadę hasha SHA1: 33958560… we wszystkich systemach EDR. |

6. Weryfikacja i diagnostyka
6.1 Restart i potwierdzenie uruchomienia
systemctl restart wazuh-manager
Ostatnim krokiem jest zrestartowanie usługi wazuh-manager (musisz poczekać na restart, aż komenda „przeskoczy” do nowej linii)
systemctl restart wazuh-manager

# Sprawdź czy obie integrację załadowane
grep -i 'gemini\|virustotal\|integrat' /var/ossec/logs/ossec.log | tail -10
# Oczekiwany wynik:
# wazuh-integratord: INFO: Enabling integration for: 'virustotal’.
# wazuh-integratord: INFO: Enabling integration for: 'custom-gemini.py’.

6.2 Test z użyciem pliku EICAR (Windows)
Przejdź do maszyny z zainstalowanym agentem Wazuh (w moim przypadku Windows 11), kliknij na ikonkę Microsoft Defender

Następnie wybierz sekcję Ochrona przed wirusami i zagrożeniami

Ustawienia ochrony przed wirusami i zagrożeniami, kliknij na Zarządzaj ustawieniami

Wyłącz wszystkie pola, w celu wyeliminowania konfliktu VirusTotal – Windows Defender

Konfiguracja FIM na agencie Windows
Przejdź do folderu C:\Program Files (x86)\ossec-agent, uruchom aplikację win32ui, następnie View / View Config


Pod sekcją Frequency that syscheck is executed default every 12 hours wklej, gdzie „arek” to nazwa Twojego użytkownika:
<directories realtime="yes" check_all="yes">C:\Users\arek\Downloads</directories>

Przejdź na stronę i pobierz plik, klikając prawym myszy na Download
https://www.eicar.org/download-anti-malware-testfile/

Po zapisaniu kliknij na 3 kropki oraz na Zachowaj oraz Zachowaj mimo to


Przejdź do Panelu administracyjnego Wazuh, wybierz system na którym ściągnąłeś plik EICAR, następnie kliknij w Treat Hunting / Events. Pojawią się nowe zdarzenia z VirusTotal oraz Gemini

Kliknij na „lupę” tam gdzie jest Alert z Gemini, rozwinie się lista, w data.gemini.prompt_used mamy szczegóły wcześniej ustawionego promptu w skrypcie custom-gemini.py. A co najważniejsze w data.gemini.summary mamy wyniki działania integracji Gemini !!
7. Typowe błędy i rozwiązania
| Błąd / Objaw | Przyczyna | Rozwiązanie |
| integrations.log pusty, Gemini nie wywołany | Zły rule_id — 23503-23506 to vulnerability-detector, nie VT | Zmień rule_id na 87105 w ossec.conf |
| error: 404, model not available | gemini-2.0-flash wycofany w marcu 2026 | Zmień model na gemini-2.5-flash-lite w skrypcie i ossec.conf |
| error: 429, quota exceeded | Przekroczono limit darmowego API | Poczekaj na reset lub włącz billing Google Cloud |
| VT: exit status 4 | Zły klucz VT lub przekroczono 4 req/min | Sprawdź klucz lub poczekaj 60 sekund |
| found: 0, brak summary | Gemini zwrócił pustą odpowiedź | Sprawdź error_description w integrations.log |
| Reguła 100210 nie matchuje | gemini.source.rule to liczba zamiast string | Upewnij się: str(rule.get(’id’,”)) w skrypcie |
| severity puste w opisie alertu | severity pobierane z vuln zamiast z level | Skrypt musi używać level_to_severity(rule.level) |
| Permission denied przy uruchomieniu | Nieprawidłowe uprawnienia pliku | chmod 750 + chown root:wazuh na skrypcie |
8. Limity API i optymalizacja
| Serwis | Req/minutę | Req/dzień | Reset | Billing |
| VirusTotal (free) | 4 | 500 | Co minutę / północ UTC | Nie wymagany |
| Gemini 2.5 Flash Lite | 15 | 1000 | Co minutę / północ PT | Nie wymagany |
| Gemini 2.5 Flash | 10 | 250 | Co minutę / północ PT | Nie wymagany |
| Gemini (płatny) | 1000+ | Brak limitu | — | Wymagany |
Strategie optymalizacji przy dużej liczbie alertów:
- Gemini wyzwalany tylko przez 87105 — eliminuje wywołania dla plików czystych i błędów API
- Cache SHA256 — rozszerzenie skryptu o słownik wykrytych hashów by nie analizować wielokrotnie tego samego pliku
- Filtrowanie katalogów FIM — monitorowanie tylko kluczowych ścieżek systemowych zamiast całego dysku
- Deduplikacja w Wazuh — parametr ignore w regule lub reguły dekodujące firedtimes
9. Aspekty bezpieczeństwa i prywatności
9.1 Ochrona kluczy API
Klucze API przechowywane są w ossec.conf, który jest chroniony uprawnieniami systemu plików. Skrypt nigdy nie powinien zawierać kluczy na stałe (hardcoded):
# Sprawdź uprawnienia pliku konfiguracyjnego
ls -la /var/ossec/etc/ossec.conf
# Oczekiwane: -rw-r—– 1 root wazuh
# Jeśli za szerokie — napraw:
chmod 640 /var/ossec/etc/ossec.conf
chown root:wazuh /var/ossec/etc/ossec.conf
9.2 Dane wysyłane do Google Gemini
Skrypt stosuje zasadę minimalizacji danych — do zewnętrznego API wysyłane są wyłącznie:
- Ścieżka pliku na hoście (może zawierać nazwę użytkownika systemu)
- Hashe kryptograficzne: MD5, SHA1, SHA256 — nieszkodliwe, publiczne identyfikatory
- Liczba detekcji VirusTotal i link do raportu publicznego VT
- ID i opis reguły Wazuh, poziom alertu, grupy
NIE są wysyłane do Gemini: zawartość pliku, dane logowania, adresy IP hostów, nazwy hostów, dane użytkowników, logi systemowe.
10. Możliwe rozszerzenia
10.1 Integracja z vulnerability-detector
Ten sam mechanizm można zastosować do analizy podatności CVE. Wymaga zmiany rule_id na 23501-23506 w ossec.conf oraz przepisania funkcji build_prompt() w skrypcie tak by odczytywała pola z obiektu data.vulnerability zamiast data.virustotal.
10.2 Aktywna reakcja (active response)
Na podstawie alertu 100210 można skonfigurować automatyczne akcje: izolacja hosta od sieci, blokada hasha w EDR, wysłanie powiadomienia na Slack lub Teams. Wymaga konfiguracji sekcji active-response w ossec.conf oraz dedykowanego skryptu akcji.
10.3 Integracja z systemami ticketing
Alerty z reguły 100210 można automatycznie przekształcać w tickety w Jira, ServiceNow lub TheHive. Pole gemini.summary jest idealnym kandydatem na opis ticketu — zawiera gotową, ustrukturyzowaną analizę po polsku.
10.4 Dashboard w OpenSearch
Wzbogacone alerty zawierają ustrukturyzowane pola (gemini.source.severity, gemini.source.vt_positives, gemini.source.file_path, gemini.source.permalink) które umożliwiają budowę dedykowanego dashboardu w OpenSearch Dashboards pokazującego trendy zagrożeń plikowych z podziałem na hosty i kategorie malware.
Podsumowanie
Integracja Wazuh + VirusTotal + Gemini AI tworzy kompletny, automatyczny łańcuch analizy zagrożeń plikowych. Zamiast ręcznej interpretacji alertów VirusTotal, analityk SOC otrzymuje gotową analizę w języku polskim z oceną zagrożenia, opisem ryzyka i konkretnymi rekomendacjami — w ciągu kilku sekund od wykrycia pliku przez FIM.
Kluczowe zalety rozwiązania to zerowy koszt wdrożenia, brak modyfikacji istniejącej infrastruktury Wazuh, pełna kontrola nad danymi wysyłanymi do zewnętrznych API oraz łatwość rozszerzenia o kolejne źródła alertów takie jak podatności CVE, autentykacja SSH czy anomalie sieciowe.

