WAZUH – Integracja z VirusTotal i Gemini AI

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.

Bezpłatne szkolenie: Zbuduj 5 agentów AI w n8n!

Weź udział w intensywnym, praktycznym szkoleniu i naucz się tworzyć automatyzacje oraz agentów AI komunikujących się przez komunikator. W programie m.in.: RAG Chatbot, Voice Agent, Wirtualna Rada Nadzorcza, Asystentka głosowa i Claude Code Admin.

Zapisy do 23 kwietnia, 23:59

Sprawdź szczegóły: https://asdevops.pl/warsztaty/

 

 

CELKaż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.

KomponentRolaModel/WersjaKoszt
Wazuh ManagerSIEM, FIM, koordynacja integracji4.x+Open source
Wazuh AgentMonitoring hosta (Windows/Linux)4.x+Open source
VirusTotal APISkanowanie pliku w 66+ silnikach AVv2 public4 req/min, 500/dzień
Gemini AI APIAnaliza zagrożenia po polskugemini-2.5-flash-lite15 req/min, 1000/dzień
custom-gemini.pySkrypt integracyjny PythonPython 3.xBezpł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 IDLevelOpisUwagi
871000Parent rule VirusTotalReguła bazowa — nie generuje alertu
871013Rate limit reachedBłąd 204 — przekroczono 4 req/min
871023Check credentialsBłąd 403 — nieprawidłowy klucz API VT
871033No records in VTPlik nieznany bazie VirusTotal
871043No positives foundPlik znany, żaden AV nie wykrył zagrożenia
8710512MALICIOUS FILE DETECTEDWYZWALA GEMINI — plik oznaczony jako złośliwy
871063API timeoutBłąd 408 — timeout połączenia z VT
WAŻNEReguł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:

SerwisGdzie uzyskać kluczPlan darmowyLimity
VirusTotalvirustotal.com/gui/my-apikeyTak — rejestracja email4 req/min, 500 req/dzień
Google Geminiaistudio.google.com/apikeyTak — bez billingu15 req/min, 1000 req/dzień
UWAGA 2026Modele 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
TECHNICZNIEWazuh 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 / ObjawPrzyczynaRozwiązanie
integrations.log pusty, Gemini nie wywołanyZły rule_id — 23503-23506 to vulnerability-detector, nie VTZmień rule_id na 87105 w ossec.conf
error: 404, model not availablegemini-2.0-flash wycofany w marcu 2026Zmień model na gemini-2.5-flash-lite w skrypcie i ossec.conf
error: 429, quota exceededPrzekroczono limit darmowego APIPoczekaj na reset lub włącz billing Google Cloud
VT: exit status 4Zły klucz VT lub przekroczono 4 req/minSprawdź klucz lub poczekaj 60 sekund
found: 0, brak summaryGemini zwrócił pustą odpowiedźSprawdź error_description w integrations.log
Reguła 100210 nie matchujegemini.source.rule to liczba zamiast stringUpewnij się: str(rule.get(’id’,”)) w skrypcie
severity puste w opisie alertuseverity pobierane z vuln zamiast z levelSkrypt musi używać level_to_severity(rule.level)
Permission denied przy uruchomieniuNieprawidłowe uprawnienia plikuchmod 750 + chown root:wazuh na skrypcie

8. Limity API i optymalizacja

SerwisReq/minutęReq/dzieńResetBilling
VirusTotal (free)4500Co minutę / północ UTCNie wymagany
Gemini 2.5 Flash Lite151000Co minutę / północ PTNie wymagany
Gemini 2.5 Flash10250Co minutę / północ PTNie wymagany
Gemini (płatny)1000+Brak limituWymagany

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.

Bezpłatne szkolenie: Zbuduj 5 agentów AI w n8n!

Weź udział w intensywnym, praktycznym szkoleniu i naucz się tworzyć automatyzacje oraz agentów AI komunikujących się przez komunikator. W programie m.in.: RAG Chatbot, Voice Agent, Wirtualna Rada Nadzorcza, Asystentka głosowa i Claude Code Admin.

Zapisy do 23 kwietnia, 23:59

Sprawdź szczegóły: https://asdevops.pl/warsztaty/

 

 

 

 

Bezpłatny dostęp do warsztatów "Zbuduj 5 agentów AI w n8n!"

X