Zabezpieczanie nowoczesnej infrastruktury IT to ciągła zabawa w kotka i myszkę. Botnety i hakerzy bez przerwy skanują sieć w poszukiwaniu luk, dlatego zespoły Security powinny robić dokładnie to samo. Wszystko po to, aby stale kontrolować swoją publiczną powierzchnię ataku i błyskawicznie reagować na pojawiające się podatności. Nie możesz przecież obronić czegoś, czego istnienia nie jesteś świadom, ani zabezpieczyć usługi, która bez Twojej wiedzy została wystawiona na świat. Ta sama zasada dotyczy sieci wewnętrznych, w których w każdej chwili może pojawić się nieautoryzowane urządzenie.

Dynamicznie zmieniająca się powierzchnia ataku oraz zjawisko Shadow IT (czyli właśnie niezatwierdzonych urządzeń, usług czy aplikacji, z których korzystają użytkownicy) spędzają sen z powiek każdemu administratorowi i bezpiecznikowi.

Zamiast jednak inwestować fortunę w drogie, komercyjne systemy skanujące, możemy zbudować potężny, zautomatyzowany ekosystem bezpieczeństwa, wykorzystując dwa świetne narzędzia open-source: flagowy system SIEM/XDR Wazuh oraz niezawodny skaner sieciowy Nmap.

W tym artykule pokażę Ci krok po kroku, jak zintegrować ze sobą te dwa rozwiązania, aby w czasie rzeczywistym mapować otwarte porty, analizować podatności oraz błyskawicznie wykrywać nieproszonych gości w Twojej sieci.

Wazuh jako serce systemu

Wazuh to otwartoźródłowe rozwiązanie klasy SIEM (odpowiedzialne za zbieranie logów i informacji o bezpieczeństwie z całej infrastruktury IT oraz ich korelację) i XDR (zapewniające rozbudowane wykrywanie i reagowanie na zagrożenia). Posiada on wiele funkcji, które już out-of-the-box czynią z niego potężne narzędzie, wychodzące daleko poza ramy klasycznego SIEM’a, jak choćby wsparcie w weryfikacji zgodności (compliance) czy utrzymaniu ogólnej higieny IT.

Dzięki otwartej architekturze system jest niezwykle elastyczny. Można go z łatwością integrować z innymi narzędziami open source oraz klasy enterprise przy pomocy języków skryptowych, takich jak Bash czy Python. W efekcie otrzymujemy rozwiązanie, które da się idealnie dopasować do potrzeb każdego środowiska, niezależnie od tego, czy mówimy o małej firmie z kilkoma serwerami, czy o wielkiej korporacji.

Nmap jako oczy i uszy systemu

Nmap to już legendarne narzędzie open source służące do skanowania sieci, mapowania otwartych portów, wykrywania usług, a nawet identyfikacji podatności. To prawdziwy szwajcarski scyzoryk, za pomocą którego możemy wykonać niemal każde zadanie związane z rekonesansem sieciowym. Ponieważ jest to narzędzie natywnie sterowane z poziomu wiersza poleceń (CLI), niezwykle łatwo zaprząc je do pracy w skryptach automatyzujących codzienne obowiązki.

Przykłady integracji: Wazuh + Nmap

Jak wspomniałem wcześniej, w tym artykule pokażę Wam, jak wykorzystać Nmapa w integracji z Wazuhem na trzech praktycznych przykładach:

  • cykliczne skanowanie portów i wykrywanie nasłuchujących usług,
  • automatyczne wykrywanie podatności,
  • wykrywanie nieautoryzowanych hostów w sieci lokalnej (Shadow IT).

Oczywiście Nmap (podobnie jak sam Wazuh) to prawdziwy szwajcarski scyzoryk. Z pewnością znaleźlibyście jeszcze mnóstwo innych zastosowań, w których te dwa narzędzia idealnie się uzupełniają. W tym przypadku sky is the limit!

Wszystkie opisywane integracje można wdrożyć bezpośrednio na serwerze Wazuh. W moim przypadku zdecydowałem się jednak na bezpieczniejsze, dedykowane rozwiązanie: skonfigurowałem osobną maszynę wirtualną o nazwie network-scanner. To właśnie na niej działa Nmap oraz skrypty Bash, które są wywoływane przez lokalnego wazuh-agent za pomocą modułu Wodles. Wszystkie wyniki działania skryptów są zapisywane do dedykowanego pliku .log, skąd agent na bieżąco je odczytuje i przesyła do analizy na centralny serwer Wazuha.

Takie podejście pozwala całkowicie odseparować skaner od serwera głównego, przeznaczając dla niego osobne, dedykowane zasoby (a te Nmap potrafi mocno skonsumować podczas skanowania dużych pul adresów sieciowych). Dodatkowo zyskujemy bezcenną stabilność. W przypadku błędnej konfiguracji harmonogramu Wodles (np. gdybyśmy niechcący ustawili wywoływanie skryptu co kilka minut, zanim poprzedni skan zdąży się zakończyć, agent zacząłby bez końca generować nowe, nakładające się na siebie procesy Nmap). Dzięki separacji ewentualne wysycenie zasobów i zawieszenie systemu dotknie jedynie maszyny skanującej, nie zakłócając pracy naszego głównego systemu SIEM/XDR.

Sama maszyna, z której będą uruchamiane skany, musi być odpowiednio umiejscowiona w infrastrukturze, tak, aby Nmap mógł swobodnie komunikować się z wybranymi hostami lub podsieciami. Jeśli w Twoim środowisku wdrożono zapory sieciowe (firewalle), segmentację VLAN czy lokalne systemy bezpieczeństwa bezpośrednio na serwerach, konieczne będzie autoryzowanie tej maszyny i jawne zezwolenie na generowany przez nią ruch sieciowy.

Wszystkie skrypty użyte w tym wpisie znajdziecie również na moim GitHubie, w bardziej czytelnej formie.

⚠️ Ważna uwaga dotycząca aspektów prawnych: Pamiętaj, że skanowanie sieci oraz urządzeń, których nie jesteś właścicielem lub nie posiadasz wyraźnej, pisemnej zgody ich administratora na przeprowadzenie takich działań, jest nielegalne i podlega odpowiedzialności karnej. Opisane w tym artykule techniki i narzędzia należy testować oraz wdrażać wyłącznie w środowiskach laboratoryjnych lub w sieciach firmowych, za które jesteś bezpośrednio odpowiedzialny.

Integracja nr.1: Cykliczne skanowanie portów

Zanim przejdziemy do konfiguracji skryptów, musimy zainstalować odpowiednie pakiety na naszej maszynie wirtualnej lub urządzeniu fizycznym, które będzie pełniło rolę skanera sieciowego (jeżeli nie zrobiliście tego wcześniej, na maszynie zainstalujcie również wazuh-agent i zarejestrujcie go w systemie Wazuh). W moim przypadku użyję menedżera pakietów apt, ponieważ całe środowisko opieram na systemie Debian 13:

sudo apt update && sudo apt upgrade
sudo apt install nmap

Po zainstalowaniu pakietów tworzymy nasz skrypt w ścieżce /var/ossec/bin/nmap_scan.sh. Uruchamia on Nmapa z flagami -sT (pełne ustanowienie połączeń TCP) oraz -p- (skanowanie całego zakresu 65 535 portów). Ponieważ jest to operacja mocno obciążająca procesor, ten skrypt zdecydowanie najlepiej uruchamiać na osobnej, dedykowanej maszynie o odpowiedniej wydajności:

#!/bin/bash

# Target configuration - can be passed as an argument (e.g., host or subnet)
if [ -z "$1" ]; then
    echo "Usage: $0 <target_or_subnet>"
    exit 1
fi

TARGET="$1"
LOG_FILE="/var/ossec/logs/nmap_scans.log"
SCAN_TIME=$(date -Iseconds)

# Run nmap scan and output to grepable format (-oG -) directly to memory/pipe
nmap -sT -p- --open "$TARGET" -oG - | while read -r line; do
    # Filter only lines containing host status and port information
    if echo "$line" | grep -q "Ports:"; then
        # Extract IP address
        host_ip=$(echo "$line" | awk '{print $2}')
        # Extract hostname if available (or empty)
        hostname=$(echo "$line" | awk '{print $3}' | tr -d '()')

        # Extract the ports section and split them by comma
        ports_str=$(echo "$line" | sed -e 's/.*Ports: //')

        IFS=',' read -ra ADDR <<< "$ports_str"
        for port_info in "${ADDR[@]}"; do
            # Trim leading/trailing whitespace
            port_info=$(echo "$port_info" | xargs)

            # Format in grepable output is: port/state/protocol/owner/service/rpcinfo/version
            # Example: 80/open/tcp//http//
            IFS='/' read -ra PORT_FIELDS <<< "$port_info"

            port="${PORT_FIELDS[0]}"
            state="${PORT_FIELDS[1]}"
            protocol="${PORT_FIELDS[2]}"
            service="${PORT_FIELDS[4]}"

            # Write structured JSON to the log file (using nmap_service_port to avoid mapping conflicts)
            echo "{\"integration\":\"nmap\",\"scan_time\":\"$SCAN_TIME\",\"host\":\"$host_ip\",\"hostname\":\"$hostname\",\"protocol\":\"$protocol\",\"nmap_service_port\":$port,\"state\":\"$state\",\"service\":\"$service\"}" >> "$LOG_FILE"
        done
    fi
done

Po dodaniu skryptu musimy nadać mu uprawnienia do wykonywania za pomocą polecenia chmod +x. Nie musimy natomiast zmieniać właściciela pliku, wazuh-agent uruchamia skrypty z modułu Wodles bezpośrednio z uprawnieniami roota.

sudo chmod +x /var/ossec/bin/nmap_scan.sh

Możemy też uruchomić skrypt ręcznie, aby sprawdzić jego działanie i zweryfikować, czy plik .log tworzy się prawidłowo. Jako argument podajemy adres IP, który chcemy zeskanować w poszukiwaniu otwartych portów i nasłuchujących usług, lub całą podsieć. Możemy również podać kilka adresów bądź podsieci, oddzielając je spacją, Nmap wykona wtedy skany po kolei dla wszystkich wskazanych celów.

./nmap_scan.sh 192.168.0.60

Po zakończeniu działania skryptu powinniśmy zweryfikować, czy plik z logami wygenerował się poprawnie. Powinien on znajdować się w katalogu /var/ossec/logs/nmap_scans.log. Zawartość pliku powinna wyglądać następująco:

{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":22,"state":"open","service":"ssh"}
{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":443,"state":"open","service":"https"}
{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":1514,"state":"open","service":"fujitsu-dtcns"}
{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":1515,"state":"open","service":"ifor-protocol"}
{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":9200,"state":"open","service":"wap-wsp"}
{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":9300,"state":"open","service":"vrace"}
{"integration":"nmap","scan_time":"2026-07-19T21:11:30+02:00","host":"192.168.0.60","hostname":"wazuh","protocol":"tcp","nmap_service_port":55000,"state":"open","service":""}

Zanim przejdziemy dalej, musimy nadać odpowiednie uprawnienia do utworzonego pliku .log, tak aby wazuh-agent był w stanie bez problemu go odczytać, a sam plik pozostawał zabezpieczony przed wglądem ze strony innych, nieuprawnionych użytkowników:

sudo chown root:wazuh /var/ossec/logs/nmap_scans.log
sudo chmod 660 /var/ossec/logs/nmap_scans.log

Kiedy wiemy już, że nasz skrypt działa, możemy przejść do konfiguracji modułu Wodles w wazuh-agent, czyli do ustawienia automatycznego, okresowego wywoływania komendy. Otwieramy plik konfiguracyjny agenta do edycji: /var/ossec/etc/ossec.conf i wklejamy poniższą zawartość wewnątrz głównego bloku <ossec_config>:

<wodle name="command">
  <disabled>no</disabled>
  <tag>nmap_network_scan</tag>
  <!-- Enter your target IP host or subnet here -->
  <command>/var/ossec/bin/nmap_scan.sh 192.168.100.0/24</command>
  <interval>1d</interval>
  <ignore_output>yes</ignore_output>
  <run_on_start>yes</run_on_start>
  <timeout>1800</timeout>
</wodle>

Blok <command> wskazuje ścieżkę do skryptu oraz przekazuje argumenty, czyli adresy IP lub zakresy sieciowe, które chcemy skanować. Jak wspomniałem wcześniej, możemy w tym miejscu podać kilka celów (np. wszystkie adresy publiczne należące do infrastruktury firmy). Z kolei blok <interval> definiuje, jak często ma być uruchamiane polecenie (w tym przypadku ustawiamy wartość 1d, co oznacza skanowanie co 24 godziny).

Po skonfigurowaniu modułu Wodles musimy w tym samym pliku konfiguracyjnym agenta wskazać plik .log, który ma być monitorowany i przesyłany do analizy. Robimy to za pomocą bloku <localfile>:

<localfile>
  <log_format>json</log_format>
  <location>/var/ossec/logs/nmap_scans.log</location>
</localfile>

Po dodaniu naszej konfiguracji zapisujemy zmiany w pliku i restartujemy usługę wazuh-agent.

sudo systemctl restart wazuh-agent

Teraz możemy przejść do konfiguracji usługi wazuh-manager na centralnym serwerze. Musimy dodać na nim odpowiednie reguły, aby system po otrzymaniu logów był w stanie wykryć w nich zagrożenia i wywołać alarm. Zazwyczaj w takich przypadkach konieczne jest również stworzenie dedykowanych dekoderów, które pozwalają Wazuhowi rozłożyć logi na części pierwsze. Na szczęście nasz skrypt formatuje wyniki z Nmap’a bezpośrednio do formatu JSON, który Wazuh rozumie natywnie i przetwarza automatycznie.

Po zalogowaniu się na serwer przy pomocy SSH otwieramy do edycji plik /var/ossec/etc/rules/local_rules.xml (alternatywnie możemy edytować go prosto z poziomu Web UI systemu, przechodząc w menu kolejno do: Server management ➔ Rules ➔ Manage rule files, a następnie wybierając plik local_rules.xml) i wklejamy do niego nastepujące reguły:

<group name="nmap_scan,">
  <rule id="100002" level="3">
    <decoded_as>json</decoded_as>
    <field name="integration">^nmap$</field>
    <description>Nmap: Open port $(nmap_service_port) detected on $(host)</description>
  </rule>

  <rule id="100003" level="8">
    <if_sid>100002</if_sid>
    <field name="nmap_service_port">21|23|5900|3389</field>
    <description>Nmap: Potentially insecure open port ($(nmap_service_port)/$(service)) detected on hos>
    <group>vulnerability_detector,pci_dss_1.2.1,</group>
  </rule>
</group>

Pierwsza, bazowa reguła o ID 100002 generuje alert o poziomie 3 (niski), gdy Nmap wykryje jakikolwiek otwarty port. Druga reguła bazuje na poprzedniej i wyzwala alarm o poziomie 8 (średni), jeżeli któryś ze znalezionych portów należy do grupy usług niezabezpieczonych, takich jak FTP, Telnet, VNC czy RDP. Zakres portów uznawanych za niebezpieczne możemy w każdej chwili dostosować, modyfikując wyrażenie regularne (regex) wewnątrz bloku <field>.

Po dodaniu nowej reguły musimy zrestartować usługę wazuh-manager, aby zaczęła ona działać.

sudo systemctl restart wazuh-manager

Aby przetestować działanie integracji, możemy poczekać, aż moduł Wodles automatycznie uruchomi skrypt, lub wywołać go ręcznie, tak jak robiliśmy to wcześniej. Kiedy nowa informacja trafi do pliku .log, wazuh-agent natychmiast ją odczyta i prześle do Wazuha w celu przetworzenia. Jeśli w logach zostaną wykryte zdefiniowane przez nas w regułach wyrażenia regex, system wyzwoli alarm, który w panelu Wazuha będzie wyglądać następująco:

Integracja nr.2: Automatyczne wykrywanie podatności

Zanim przejdziemy do konfiguracji samej integracji, musimy zainstalować wymagane pakiety na naszym hoście:

sudo apt update && sudo apt upgrade
sudo apt install nmap libxml2-utils
sudo nmap --script-update

Tym razem, oprócz standardowego Nmapa, będziemy potrzebować również pakietu libxml2-utils. Zawiera on narzędzia, które pozwolą naszemu skryptowi odpowiednio przekształcić wyniki skanowania (zapisane pierwotnie w formacie XML) na format JSON. Dodatkowo wykonanie polecenia nmap --script-update zaktualizuje bazę skryptów NSE (Nmap Scripting Engine), służących do wykrywania podatności w usługach nasłuchujących na otwartych portach.

W kolejnym kroku tworzymy skrypt w ścieżce /var/ossec/bin/nmap_vuln_scan.sh i wklejamy do niego poniższą zawartość. Uruchamia on Nmapa dla wszystkich wskazanych adresów IP lub zakresów sieciowych z flagami -sV (dokładne badanie wersji usług nasłuchujących na portach) oraz --script=vulners (próba wykrycia znanych podatności w zidentyfikowanych serwisach na podstawie ich wersji).

Warto pamiętać, że nie jest to aktywny atak eksploitujący system, a jedynie mapowanie wersji usług z bazą znanych podatności (version matching). Wyniki tego skanowania należy więc traktować jako informację do dalszej weryfikacji, a nie absolutny pewnik. Oryginalny wynik Nmapa zapisywany jest w formacie XML, jednak skrypt automatycznie przekształca go do formatu JSON, co umożliwia agentowi i serwerowi Wazuh jego bezproblemowe zdekodowanie oraz przeanalizowanie.

#!/bin/bash

# Target configuration - can be passed as an argument (e.g., host or subnet)
if [ -z "$1" ]; then
    echo "Usage: $0 <target_or_subnet>"
    exit 1
fi

TARGET="$1"
LOG_FILE="/var/ossec/logs/nmap_vulns.log"
TMP_XML="/tmp/nmap_vuln_result.xml"
SCAN_TIME=$(date -Iseconds)

# 1. Run Nmap scan and output directly to XML format
nmap -sV --script=vulners --open "$TARGET" -oX "$TMP_XML" > /dev/null 2>&1

if [ ! -f "$TMP_XML" ]; then
    exit 1
fi

# Ensure the log directory exists
mkdir -p "$(dirname "$LOG_FILE")"

# 2. Extract vulnerability tables using xmllint and process them block by block
# We normalize the XML structure into separate lines using sed for easier parsing
xmllint --xpath "
  //script[@id='vulners']/table/table[elem[@key='id']]
" "$TMP_XML" 2>/dev/null | tr -d '\n' | sed 's/<\/table>/<\/table>\n/g' | while read -r block; do

    [ -z "$block" ] && continue

    # Extract CVE ID and CVSS score from the single table block using regex matching
    cve_id=$(echo "$block" | grep -oP 'key="id">\K[^<]+')
    cvss_score=$(echo "$block" | grep -oP 'key="cvss">\K[^<]+')

    # If a valid vulnerability ID is found, track it back to its parent host and port
    if [ -n "$cve_id" ]; then
        # Query the XML structurally to find associated network details for this specific CVE
        port_info=$(xmllint --xpath "//host/ports/port[script[@id='vulners']/table/table/elem[@key='id' and text()='$cve_id']]/@portid" "$TMP_XML" 2>/dev/null | grep -oE '[0-9]+' | head -n 1)
        proto_info=$(xmllint --xpath "string(//host/ports/port[script[@id='vulners']/table/table/elem[@key='id' and text()='$cve_id']]/@protocol)" "$TMP_XML" 2>/dev/null)
        host_info=$(xmllint --xpath "string(//host[ports/port[script[@id='vulners']/table/table/elem[@key='id' and text()='$cve_id']]]/address[@addrtype='ipv4']/@addr)" "$TMP_XML" 2>/dev/null)
        service_info=$(xmllint --xpath "string(//host[ports/port[script[@id='vulners']/table/table/elem[@key='id' and text()='$cve_id']]]/ports/port[@portid='$port_info']/service/@name)" "$TMP_XML" 2>/dev/null)

        # Set fallbacks and guardrails
        [ -z "$cvss_score" ] && cvss_score="0.0"
        [ -z "$host_info" ] && continue

        # Write clean, structured single-line JSON to the log file monitored by Wazuh
        echo "{\"integration\":\"nmap_vuln\",\"scan_time\":\"$SCAN_TIME\",\"host\":\"$host_info\",\"protocol\":\"$proto_info\",\"nmap_service_port\":$port_info,\"service\":\"$service_info\",\"cve\":\"$cve_id\",\"cvss\":$cvss_score}" >> "$LOG_FILE"
    fi
done

# Cleanup temporary file
rm -f "$TMP_XML"

Po zapisaniu skryptu musimy nadać mu uprawnienia do wykonywania:

sudo chmod +x /var/ossec/bin/nmap_vuln_scan.sh

Działanie skryptu możemy przetestować, uruchamiając go ręcznie. Jako argument podajemy adres IP lub pulę sieciową, warto pamiętać, że oddzielając je spacją, można wskazać kilka celów jednocześnie.

./nmap_vuln_scan.sh 192.168.0.60

Po zakończeniu działania skryptu wyniki skanowania zostaną zapisane do pliku logu w lokalizacji /var/ossec/logs/nmap_vulns.log. Sama struktura logów wygląda następująco:

{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-60002","cvss":9.4}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-35414","cvss":8.1}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-35386","cvss":8.1}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-35385","cvss":8.1}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-60000","cvss":7.5}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-59999","cvss":7.5}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-60001","cvss":6.5}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-59998","cvss":6.5}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-35387","cvss":6.5}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-59997","cvss":5.4}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-59996","cvss":5.4}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2026-59995","cvss":5.4}
{"integration":"nmap_vuln","scan_time":"2026-07-17T13:30:11+02:00","host":"192.168.0.60","protocol":"tcp","nmap_service_port":22,"service":"ssh","cve":"CVE-2025-61985","cvss":3.6}

Zanim przejdziemy dalej, musimy nadać odpowiednie uprawnienia do pliku .log. Chcemy, aby wazuh-agent mógł bez problemu go odczytać, a jednocześnie, by żaden inny nieuprawniony użytkownik w systemie nie miał możliwości przeglądania jego zawartości.

sudo chown root:wazuh /var/ossec/logs/nmap_vulns.log
sudo chmod 660 /var/ossec/logs/nmap_vulns.log

Teraz możemy przejść do konfiguracji agenta. Najpierw skonfigurujemy moduł Wodles odpowiedzialny za automatyczne, cykliczne wywoływanie komend. W tym celu otwieramy do edycji plik /var/ossec/etc/ossec.conf na naszym hoście i wklejamy poniższą konfigurację wewnątrz głównego bloku <ossec_config>:

<wodle name="command">
  <disabled>no</disabled>
  <tag>nmap_vulnerability_scan</tag>
  <command>/var/ossec/bin/nmap_vuln_scan.sh 192.168.0.60</command>
  <interval>1d</interval>
  <ignore_output>yes</ignore_output>
  <run_on_start>yes</run_on_start>
  <timeout>7200</timeout>
</wodle>

Blok <command> wskazuje ścieżkę do skryptu oraz przekazuje argumenty, czyli adresy IP lub zakresy sieciowe, które chcemy skanować. Jak wspomniałem wcześniej, możemy w tym miejscu podać kilka celów (np. wszystkie adresy publiczne należące do infrastruktury firmy). Z kolei blok <interval> definiuje, jak często ma być uruchamiane polecenie (w tym przypadku ustawiamy wartość 1d, co oznacza skanowanie co 24 godziny).

Następnie musimy wskazać plik .log, który agent ma monitorować i z którego zdarzenia mają być przekazywane do Wazuha w celu analizy. Robimy to za pomocą bloku <localfile>, który ponownie wklejamy wewnątrz głównej sekcji <ossec_config> w pliku konfiguracyjnym agenta.

<localfile>
  <log_format>json</log_format>
  <location>/var/ossec/logs/nmap_vulns.log</location>
</localfile>

Po zapisaniu zmian w pliku musimy zrestartować usługę wazuh-agent, aby zmiany weszły w życie.

sudo systemctl restart wazuh-agent

Teraz możemy przejść do konfiguracji usługi wazuh-manager na centralnym serwerze. Musimy dodać na nim odpowiednie reguły, aby system po otrzymaniu logów był w stanie wykryć w nich zagrożenia i wywołać alarm.

Po zalogowaniu się na serwer przy pomocy SSH otwieramy do edycji plik /var/ossec/etc/rules/local_rules.xml (alternatywnie możemy edytować go prosto z poziomu Web UI systemu, przechodząc w menu kolejno do: Server management ➔ Rules ➔ Manage rule files, a następnie wybierając plik local_rules.xml) i wklejamy do niego nastepujące reguły:

<group name="nmap_vulnerabilities,">
  <!-- Base rule for Nmap vulnerability scanner integration -->
  <rule id="100010" level="3">
    <decoded_as>json</decoded_as>
    <field name="integration">^nmap_vuln$</field>
    <description>Nmap Vuln: Vulnerability scan reporting data for host $(host)</description>
  </rule>

  <!-- Level 5: Default alert for Low/Medium vulnerabilities (CVSS 0.0 - 6.9) -->
  <rule id="100011" level="8">
    <if_sid>100010</if_sid>
    <field name="cve">\.+</field>
    <description>Nmap Vuln: Found $(cve) [CVSS: $(cvss)] on host $(host) (Port: $(nmap_service_port)/$(service))</description>
    <group>vulnerability_detector,gdpr_IV_35.7.d,</group>
  </rule>

  <!-- Level 10: HIGH severity vulnerabilities (CVSS 7.0 - 8.9) -->
  <rule id="100012" level="12">
    <if_sid>100011</if_sid>
    <!-- Matches CVSS scores starting with 7 or 8 (e.g., 7.5, 8.1) -->
    <field name="cvss">^7|^8</field>
    <description>Nmap Vuln: HIGH severity vulnerability $(cve) [CVSS: $(cvss)] detected on host $(host)</description>
    <group>vulnerability_detector,pci_dss_6.2,</group>
  </rule>

  <!-- Level 13: CRITICAL severity vulnerabilities (CVSS 9.0 - 10.0) -->
  <rule id="100013" level="15">
    <if_sid>100011</if_sid>
    <!-- Matches CVSS scores starting with 9 or exactly 10 -->
    <field name="cvss">^9|^10</field>
    <description>Nmap Vuln: CRITICAL vulnerability $(cve) [CVSS: $(cvss)] detected on host $(host) - Action Required!</description>
    <group>vulnerability_detector,pci_dss_6.2,ts_critical,</group>
  </rule>
</group>

Bazowa reguła o identyfikatorze 100010 generuje alert na poziomie 3 (niski), informujący o wykryciu jakichkolwiek podatności na danym hoście. Pełni ona funkcję testu poprawności (sanity check), dzięki któremu zyskujemy pewność, że integracja działa prawidłowo. Kolejne reguły wykorzystują wyrażenia regularne (regex), aby inteligentnie określić stopień zagrożenia danej luki na podstawie jej punktacji CVSS (którą Nmap zwraca w logach) i na tej podstawie przypisać do alertu odpowiedni poziom krytyczności w panelu Wazuha. Dzięki temu, jeśli na hoście zostanie wykryta podatność o wysokim wskaźniku CVSS, Wazuh natychmiast zgłosi to jako incydent o wysokim stopniu priorytetu.

Po dodaniu nowej reguły musimy zrestartować usługę wazuh-manager, aby zaczęła ona działać.

sudo systemctl restart wazuh-manager

Aby przetestować działanie integracji, możemy poczekać, aż moduł Wodles automatycznie uruchomi skrypt, lub wywołać go ręcznie, tak jak robiliśmy to wcześniej. Kiedy nowa informacja trafi do pliku .log, wazuh-agent natychmiast ją odczyta i prześle do Wazuha w celu przetworzenia. Jeśli w logach zostaną wykryte zdefiniowane przez nas w regułach wyrażenia regex, system wyzwoli alarm, który w panelu Wazuha będzie wyglądać następująco:

Integracja nr.3: Wykrywanie nieautoryzowany hostów w sieci

Czas na naszą ostatnią integrację, która pozwoli nam wykrywać w sieci niezautoryzowane urządzenia. Standardowo, zanim przejdziemy do konfiguracji skryptów, musimy upewnić się, że na hoście znajduje się niezbędne oprogramowanie. W tym przypadku wystarczy sam Nmap:

sudo apt update && sudo apt upgrade
sudo apt install nmap

Tym razem, oprócz samego skryptu, będziemy potrzebować pliku z białą listą (whitelist) adresów MAC urządzeń, które uznajemy za zaufane. Tworzymy go w ścieżce /var/ossec/etc/allowed_macs.txt i wprowadzamy do niego adresy MAC (każdy w nowej linii). Plik warto uporządkować za pomocą komentarzy, dzieląc go na sekcje (np. serwery, stacje robocze użytkowników) lub dodając opisy konkretnych hostów. Przykładowa struktura pliku:

# Gateways & Infrastructure
00:11:22:33:44:55
# Server Room
aa:bb:cc:dd:ee:ff
11:22:33:44:55:66
# Authorized Workstations
88:99:aa:bb:cc:dd

Po stworzeniu pliku musimy nadać mu odpowiedniego właściciela oraz bezpieczne uprawnienia do odczytu, tak aby chronić jego zawartość przed nieautoryzowanym dostępem.

sudo chown root:wazuh /var/ossec/etc/allowed_macs.txt
sudo chmod 660 /var/ossec/etc/allowed_macs.txt

W kolejnym kroku tworzymy skrypt w ścieżce /var/ossec/bin/nmap_shadow_it.sh. Skrypt ten wykonuje skanowanie (ping scan) wskazanej podsieci w celu zmapowania aktywnych urządzeń i pobrania ich adresów MAC. Następnie zebrane dane są porównywane z przygotowaną wcześniej białą listą. Jeśli skrypt wykryje adres MAC, którego nie zdefiniowaliśmy w pliku konfiguracyjnym, zapisze takie zdarzenie jako naruszenie bezpieczeństwa bezpośrednio do pliku .log.

#!/bin/bash

if [ -z "$1" ]; then
    echo "Usage: $0 <target_subnet>"
    exit 1
fi

TARGET="$1"
ALLOWED_FILE="/var/ossec/etc/allowed_macs.txt"
LOG_FILE="/var/ossec/logs/nmap_shadow_it.log"
TMP_SCAN="/tmp/nmap_shadow_scan.txt"
SCAN_TIME=$(date -Iseconds)

if [ ! -f "$ALLOWED_FILE" ]; then
    echo "Error: Allowed MAC file not found at $ALLOWED_FILE"
    exit 1
fi

# Ensure log directory exists
mkdir -p "$(dirname "$LOG_FILE")"

# 1. Run local ping/ARP scan (requires root/sudo to see MAC addresses)
# -sn skips port scanning, making this extremely fast (seconds)
nmap -sn "$TARGET" -oN "$TMP_SCAN" > /dev/null 2>&1

if [ ! -f "$TMP_SCAN" ]; then
    exit 1
fi

# 2. Parse the Nmap output using a robust state loop
current_ip=""
current_mac=""
current_vendor=""

# Patterns extracted into variables to prevent Bash evaluation glitches
report_pattern="^Nmap scan report for (.+)"
ip_in_parentheses="\(([^)]+)\)"
mac_pattern="^MAC Address: ([0-9A-Fa-f:]{17}) \((.+)\)"

while read -r line; do
    # Extract IP address
    if [[ "$line" =~ $report_pattern ]]; then
        matched="${BASH_REMATCH[1]}"
        if [[ "$matched" =~ $ip_in_parentheses ]]; then
            current_ip="${BASH_REMATCH[1]}"
        else
            current_ip="$matched"
        fi
    fi

    # Extract MAC and Vendor
    if [[ "$line" =~ $mac_pattern ]]; then
        current_mac=$(echo "${BASH_REMATCH[1]}" | tr '[:upper:]' '[:lower:]')
        current_vendor="${BASH_REMATCH[2]}"

        # Check if this MAC is in the allowed file (ignoring case and comments)
        if ! grep -qsi "^${current_mac}" "$ALLOWED_FILE"; then
            # Rogue device detected! Write structured JSON to the log file
            echo "{\"integration\":\"nmap_shadow_it\",\"scan_time\":\"$SCAN_TIME\",\"host_ip\":\"$current_ip\",\"mac_address\":\"$current_mac\",\"vendor\":\"$current_vendor\",\"status\":\"unauthorized\"}" >> "$LOG_FILE"
        fi

        # Reset variables for the next host block
        current_ip=""
        current_mac=""
        current_vendor=""
    fi
done < "$TMP_SCAN"

# Cleanup
rm -f "$TMP_SCAN"

Po zapisaniu skryptu musimy nadać mu uprawnienia do wykonywania:

sudo chmod +x /var/ossec/bin/nmap_shadow_it.sh

Działanie skryptu możemy przetestować, uruchamiając go ręcznie. Jako argument podajemy pulę sieciową, warto pamiętać, że oddzielając je spacją, można wskazać kilka celów jednocześnie.

./nmap_shadow_it.sh 192.168.0.0/24

Po zakończeniu działania skryptu wyniki skanowania zostaną zapisane do pliku logu w lokalizacji /var/ossec/logs/nmap_shadow_it.log. Sama struktura logów wygląda następująco:

{"integration":"nmap_shadow_it","scan_time":"2026-07-19T20:54:20+02:00","host_ip":"192.168.0.60","mac_address":"08:00:27:77:28:72","vendor":"PCS Systemtechnik/Oracle VirtualBox virtual NIC","status":"unauthorized"}
{"integration":"nmap_shadow_it","scan_time":"2026-07-19T20:54:20+02:00","host_ip":"192.168.0.150","mac_address":"38:c8:04:02:2a:e4","vendor":"Hui Zhou Gaoshengda Technology","status":"unauthorized"}
{"integration":"nmap_shadow_it","scan_time":"2026-07-19T20:54:20+02:00","host_ip":"192.168.0.228","mac_address":"c6:84:7a:18:cc:42","vendor":"Unknown","status":"unauthorized"}

Jak widać, Nmap w większości przypadków jest w stanie wykryć producenta (vendora) danego urządzenia. Odbywa się to na podstawie prefiksu OUI (ang. Organizationally Unique Identifier), czyli pierwszych trzech bajtów adresu MAC, które są unikalne dla firm produkujących karty sieciowe.

Zanim przejdziemy dalej, musimy nadać odpowiednie uprawnienia do pliku .log. Chcemy, aby wazuh-agent mógł bez problemu go odczytać, a jednocześnie, by żaden inny nieuprawniony użytkownik w systemie nie miał możliwości przeglądania jego zawartości.

sudo chown root:wazuh /var/ossec/logs/nmap_shadow_it.log
sudo chmod 660 /var/ossec/logs/nmap_shadow_it.log

Teraz możemy przejść do konfiguracji agenta. Najpierw skonfigurujemy moduł Wodles odpowiedzialny za automatyczne, cykliczne wywoływanie komend. W tym celu otwieramy do edycji plik /var/ossec/etc/ossec.conf na naszym hoście i wklejamy poniższą konfigurację wewnątrz głównego bloku <ossec_config>:

<wodle name="command">
  <disabled>no</disabled>
  <tag>nmap_shadow_it_detection</tag>
  <command>/var/ossec/bin/nmap_shadow_it.sh 192.168.100.0/24</command>
  <interval>1d</interval>
  <ignore_output>yes</ignore_output>
  <run_on_start>yes</run_on_start>
  <timeout>600</timeout>
</wodle>

Blok <command> wskazuje ścieżkę do skryptu oraz przekazuje argumenty, czyli adresy IP lub zakresy sieciowe, które chcemy skanować. Jak wspomniałem wcześniej, możemy w tym miejscu podać kilka celów (np. wszystkie sieci VLAN, do których nie powinien mieć dostępu standardowy pracownik). Z kolei blok <interval> definiuje, jak często ma być uruchamiane polecenie – w tym przypadku ustawiamy wartość 1d, co oznacza skanowanie co 24 godziny.

Następnie musimy wskazać plik .log, który agent ma monitorować i z którego zdarzenia mają być przekazywane do Wazuha w celu analizy. Robimy to za pomocą bloku <localfile>, który ponownie wklejamy wewnątrz głównej sekcji <ossec_config> w pliku konfiguracyjnym agenta.

<localfile>
  <log_format>json</log_format>
  <location>/var/ossec/logs/nmap_shadow_it.log</location>
</localfile>

Po zapisaniu zmian w pliku musimy zrestartować usługę wazuh-agent, aby zmiany weszły w życie.

sudo systemctl restart wazuh-agent

Teraz możemy przejść do konfiguracji usługi wazuh-manager na centralnym serwerze. Musimy dodać na nim odpowiednie reguły, aby system po otrzymaniu logów był w stanie wykryć w nich zagrożenia i wywołać alarm.

Po zalogowaniu się na serwer przy pomocy SSH otwieramy do edycji plik /var/ossec/etc/rules/local_rules.xml (alternatywnie możemy edytować go prosto z poziomu Web UI systemu, przechodząc w menu kolejno do: Server management ➔ Rules ➔ Manage rule files, a następnie wybierając plik local_rules.xml) i wklejamy do niego nastepujące reguły:

<group name="nmap_vulnerabilities,">
  <rule id="100014" level="3">
    <decoded_as>json</decoded_as>
    <field name="integration">^nmap_shadow_it$</field>
    <description>Nmap Shadow IT: Network scanning activity monitored.</description>
  </rule>
  
  <rule id="100015" level="12">
    <if_sid>100014</if_sid>
    <match>unauthorized</match>
    <description>Shadow IT Alert: Unauthorized device detected in the network! IP: $(host_ip), MAC: $(mac_address) ($(vendor))</description>
    <group>pci_dss_11.1,security_misconfiguration,ts_shadow_it,</group>
  </rule>
</group>

Reguła o identyfikatorze 100014 to, podobnie jak wcześniej, test poprawności (sanity check). Dzięki niej wiemy, że integracja działa prawidłowo, a wskazane przez nas podsieci są cały czas monitorowane. Natomiast następna reguła (wykorzystująca poprzednią jako regułę bazową w celu uniknięcia fałszywych alertów) wyzwala alert o poziomie wysokim (High). Informuje nas ona, że w skanowanej podsieci wykryto hosta, którego adres MAC nie widnieje na liście, co oznacza, że urządzenie nie jest zaufane.

Po dodaniu nowej reguły musimy zrestartować usługę wazuh-manager, aby zaczęła ona działać.

sudo systemctl restart wazuh-manager

Aby przetestować działanie integracji, możemy poczekać, aż moduł Wodles automatycznie uruchomi skrypt, lub wywołać go ręcznie, tak jak robiliśmy to wcześniej. Kiedy nowa informacja trafi do pliku .log, wazuh-agent natychmiast ją odczyta i prześle do Wazuha w celu przetworzenia. Jeśli w logach zostaną wykryte zdefiniowane przez nas w regułach wyrażenia regex, system wyzwoli alarm, który w panelu Wazuha będzie wyglądać następująco:

Retencja logów

Zanim zakończymy, warto również wspomnieć o retencji plików .log generowanych przez zademonstrowane wyżej skrypty. Jeśli skonfigurujesz skanowanie i pozostawisz je bez nadzoru na kilka miesięcy, pliki logów mogą drastycznie zwiększyć swoją objętość i całkowicie zapełnić przestrzeń dyskową skanera sieciowego. W tym scenariuszu najlepiej sprawdzi się narzędzie logrotate, dostępne natywnie w większości dystrybucji systemu Linux. Pozwala ono na automatyczne zarządzenie cyklem życia logów poprzez definiowanie elastycznych reguł rotacji. Poniżej znajduje się przykładowa konfiguracja dla pliku nmap_shadow_it.log:

/var/ossec/logs/nmap_shadow_it.log {
    daily
    rotate 0
    missingok
    notifempty
    create 0660 root wazuh
}

W przedstawionej konfiguracji logrotate codziennie (co 24 godziny) rotuje plik poprzez jego usunięcie i utworzenie nowego, czystego pliku z odpowiednimi uprawnieniami, do którego skrypt może swobodnie zapisywać kolejne wyniki działań. Przechowywanie archiwalnych wersji logów lokalnie na agencie nie ma sensu, każda linia tekstu trafia bezpośrednio do centralnego serwera Wazuh, gdzie alerty są agregowane i przechowywane zgodnie z globalną polityką retencji danych.

Przydatne linki:

  • Wazuh SIEM/XDR – oficjalna strona produktu, tutaj znajdziesz specyfikację i pełną dokumentację systemu.
  • Program Ambasadorów Wazuh – dołącz do społeczności open-source i pomóż nam rozwijać system Wazuh.