Sổ tay An toàn thông tin 🌐 EN

Chương 8 — SIEM & Quản lý log tập trung

Tổng quan

Hồi mới học SIEM, mình cứ nghĩ giá trị của nó nằm ở dashboard đẹp và cảnh báo chạy realtime. Đến lần đầu ngồi điều tra một đợt quét thật, mình mới hiểu giá trị lớn nhất nằm ở chỗ khác hẳn: khả năng đặt câu hỏi lên dữ liệu log đã được gom về một chỗ. Chương này viết theo đúng mạch đó — SIEM là lớp phần mềm thu gom, chuẩn hóa, tương quan và lưu trữ log bảo mật từ khắp hạ tầng, còn Wazuh là nền tảng SIEM/HIDS mã nguồn mở hiện thực đầy đủ pipeline đó. Không thu được log thì không có gì để phát hiện hay điều tra — khả năng quan sát (visibility) luôn đi trước mọi khả năng phòng thủ.

Nửa đầu chương đi từ khái niệm nền tới cách phân biệt công cụ. Pipeline dữ liệu (collect → parse → normalize → enrich → correlate → alert → store) biến log thô hỗn tạp từ nhiều nguồn thành dạng đồng nhất để một rule dùng được cho mọi nguồn, còn ranh giới giữa AV, EDR, NDR, SIEM, SOAR và XDR là chỗ hay bị nhầm nhất — chọn sai lớp công cụ để lại điểm mù mà thường chỉ lộ ra khi đã bị khai thác. Từ đó chương đi sâu vào kiến trúc Wazuh: bốn thành phần agent/manager/indexer/dashboard, cơ chế enrollment để chỉ host hợp lệ mới gửi được dữ liệu, và file cấu hình ossec.conf điều khiển toàn bộ hành vi đó.

Phần còn lại là cách Wazuh biến log thành hành động cụ thể: decoder trích các trường có nghĩa, rule quyết định event nào thành alert và ở mức nào, FIM/Syscheck bắt thay đổi file quan trọng kể cả khi kẻ tấn công làm giả mtime, Active Response tự chặn rồi tự gỡ sau timeout, còn Vulnerability Detection và SCA lần lượt rà theo CVE feed và baseline cấu hình an toàn. Khung MITRE ATT&CK giúp gọi tên kỹ thuật tấn công bằng một ngôn ngữ chung cho việc viết và tinh chỉnh rule; quy trình điều tra ở 8.16 là cách lặp lại được để đi từ một cảnh báo tới bằng chứng cụ thể trên full_log; và SOAR đóng vai trò lớp tự động hóa phía trên cùng — bổ sung chứ không thay thế SIEM.

Mỗi mục trong chương đi kèm ví dụ chạy được trên Wazuh thật; phần nào là quan sát riêng của mình sẽ ghi rõ, không lẫn với nội dung đã kiểm chứng.


8.1. SIEM là gì và vì sao tồn tại

8.1.1. Định nghĩa và bài toán gốc

SIEM (Security Information and Event Management) là lớp phần mềm thu thập, chuẩn hóa, tương quan và lưu trữ sự kiện (event) bảo mật từ toàn bộ hạ tầng, nhằm phát hiện và điều tra mối đe dọa theo thời gian gần thực (near real-time) và phục vụ tuân thủ (compliance).

Thuật ngữ này hợp nhất hai dòng sản phẩm cũ:

Thuật ngữ Viết tắt của Trọng tâm gốc
SIM Security Information Management Lưu trữ log dài hạn, báo cáo compliance, forensic
SEM Security Event Management Tương quan thời gian thực, alert, dashboard
SIEM Hợp nhất SIM + SEM Cả hai: real-time correlation và long-term retention

Vì sao tồn tại: Trong một hệ thống thật, một cuộc tấn công không để lại dấu vết ở một nơi. Một lần đăng nhập SSH brute-force để lại: - Log /var/log/auth.log trên Linux host; - Log Netflow/connection trên firewall; - Log từ EDR ghi nhận tiến trình bất thường sau khi vào được; - Log từ Active Directory nếu attacker lateral movement.

Không một con người nào ngồi đọc song song hàng triệu dòng log/ngày từ hàng nghìn nguồn. SIEM giải bài toán gom về một chỗ + một mô hình dữ liệu chung + tương quan tự động.

8.1.2. Đơn vị dữ liệu: Event, Log, Alert

Cần phân biệt chính xác ba khái niệm thường bị dùng lẫn:

Khái niệm Định nghĩa Ví dụ
Log line / raw event Một dòng văn bản (hoặc bản ghi nhị phân) do nguồn sinh ra, định dạng tùy nguồn Jun 19 10:22:41 web01 sshd[2931]: Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2
Normalized event Bản ghi đã được parse thành các trường có tên chuẩn (structured) { "timestamp": ..., "program": "sshd", "srcip": "203.0.113.5", "srcuser": "admin", "action": "auth_failed" }
Alert Kết quả của một rule khớp với một hoặc nhiều event, kèm mức độ nghiêm trọng Rule 5710 (level 5): sshd: Attempt to login using a non-existent user

8.2. Kiến trúc SIEM & data pipeline

Mọi SIEM (Splunk, Elastic SIEM, QRadar, Wazuh, Sentinel...) đều thực thi cùng một đường ống logic. Hiểu pipeline này là chìa khóa để hiểu Wazuh ở phần sau.

            ┌─────────┐   ┌────────┐   ┌───────────┐   ┌────────┐   ┌──────────┐   ┌───────┐   ┌────────┐
  Sources──▶│ COLLECT │──▶│ PARSE  │──▶│ NORMALIZE │──▶│ ENRICH │──▶│CORRELATE │──▶│ ALERT │──▶│ STORE  │
            └─────────┘   └────────┘   └───────────┘   └────────┘   └──────────┘   └───────┘   └────────┘
             (ship)        (decode)     (field map)     (geoip,      (rules,         (notify)   (index/
                                                         threat       stateful)                  retain)
                                                         intel)

8.2.1. COLLECT (thu thập)

Là gì: Đưa event từ nguồn về collector. Hai mô hình:

Mô hình Cơ chế Ví dụ giao thức/port
Push (agent-based) Agent cài trên host đọc file/sự kiện, đẩy về manager Wazuh agent → manager (UDP/TCP 1514); Filebeat → Logstash (5044)
Pull / agentless Server kéo log từ nguồn, hoặc nhận qua syslog Syslog UDP/TCP 514; WMI; SNMP; API polling (cloud)

Cơ chế — Syslog (RFC 5424) ở mức byte. Vì syslog là phương tiện collect phổ biến nhất, ta mổ xẻ một bản ghi:

<34>1 2026-06-19T10:22:41.003Z web01 sshd 2931 ID47 - Failed password...
 └┬┘│ └──────────┬──────────┘ └─┬─┘ └─┬┘ └┬┘ └┬┘ │ └──────┬──────┘
  │ │            │              │     │    │    │  │        │
  │ │            │              │     │    │    │  │        └─ MSG (free-form UTF-8, có thể có BOM)
  │ │            │              │     │    │    │  └─ STRUCTURED-DATA ("-" = none)
  │ │            │              │     │    │    └─ MSGID
  │ │            │              │     │    └─ PROCID (PID)
  │ │            │              │     └─ APP-NAME
  │ │            │              └─ HOSTNAME
  │ │            └─ TIMESTAMP (ISO 8601 / RFC 3339)
  │ └─ VERSION (luôn = 1 trong RFC 5424)
  └─ PRI = "<34>"  (Priority value)

Trường PRI giải mã tới mức bit: PRI là số thập phân trong dấu < >, được tính:

PRI = Facility × 8 + Severity
Thành phần Bit Dải giá trị Ý nghĩa Ví dụ với PRI=34
Facility 5 bit cao (PRI >> 3) 0–23 Nguồn sinh log 34 >> 3 = 4 → "security/authorization (auth)"
Severity 3 bit thấp (PRI & 7) 0–7 Mức nghiêm trọng 34 & 7 = 2 → "Critical"

Bảng Severity (3 bit, RFC 5424 §6.2.1):

Mã Tên Ý nghĩa
0 Emergency Hệ thống không dùng được
1 Alert Phải xử lý ngay
2 Critical Tình trạng nghiêm trọng
3 Error Lỗi
4 Warning Cảnh báo
5 Notice Bình thường nhưng đáng chú ý
6 Informational Thông tin
7 Debug Gỡ lỗi

Lưu ý: RFC 3164 (BSD syslog cũ) giới hạn message ~1024 byte và timestamp không có năm/timezone, dễ gây lệch thời gian khi tương quan. RFC 5424 cho phép message dài hơn (giới hạn theo transport) và timestamp đầy đủ timezone — đây là lý do nên ưu tiên 5424.

Cảnh báo khi collect: - Syslog UDP 514 không xác thực, không mã hóa, không đảm bảo phân phối → attacker có thể spoof log (log injection) hoặc làm nghẽn để mất log. Ưu tiên TLS (RFC 5425, syslog over TLS, port 6514) hoặc kênh agent có mã hóa. - Mất log = mù. Cần đo độ trễ và tỷ lệ rớt gói.

8.2.2. PARSE (bóc tách / decode)

Là gì: Biến chuỗi văn bản tự do (MSG) thành các trường rời rạc. Đây là nơi regex/grok/decoder hoạt động.

Ví dụ MSG SSH thô:

Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2

Sau parse:

action  = "Failed password"
srcuser = "admin"
srcip   = "203.0.113.5"
srcport = 51244

8.2.3. NORMALIZE (chuẩn hóa)

Là gì: Ánh xạ các trường vừa parse về một schema chung để event từ nhiều nguồn khác nhau có thể so sánh/tương quan. Đây là điểm phân biệt SIEM thật với một công cụ grep log.

Vì sao cần: nguồn A gọi IP nguồn là src, nguồn B gọi là client_ip, nguồn C gọi là SourceAddress. Không chuẩn hóa thì không thể viết một rule "đếm số lần login fail theo IP nguồn" áp dụng cho cả ba.

Ví dụ trường trước/sau normalize (theo ECS — Elastic Common Schema, hoặc field chuẩn của Wazuh):

Nguồn Trường gốc Trường chuẩn hóa (Wazuh) Trường chuẩn hóa (ECS)
sshd from 203.0.113.5 srcip source.ip
Windows 4625 Source Network Address srcip / data.win.eventdata.ipAddress source.ip
nginx $remote_addr srcip source.ip

8.2.4. ENRICH (làm giàu)

Là gì: Thêm ngữ cảnh không có trong log gốc: - GeoIP: 203.0.113.5 → country=US, ASN=AS64500. (Wazuh hỗ trợ GeoIP qua tích hợp dashboard/indexer với GeoLite2 mmdb.) - Threat intel: đối chiếu IP/hash với feed IoC (ví dụ AlienVault OTX, AbuseIPDB) → gắn cờ malicious. - Asset context: host web01 thuộc nhóm "production-dmz", owner = team-web. - Vulnerability context: host này có CVE-2024-XXXX đang mở.

8.2.5. CORRELATE (tương quan)

Là gì: Logic phát hiện. Hai kiểu:

Kiểu Mô tả Ví dụ
Stateless 1 event khớp 1 pattern → alert "Có dòng Failed password" → alert level 5
Stateful Đếm/nhóm nhiều event theo thời gian/khóa → alert "≥ 8 lần Failed password từ cùng srcip trong 120 giây" → brute-force level 10

Stateful correlation là một state machine đếm sự kiện theo khóa (key) trong cửa sổ trượt (sliding window). Wazuh thực thi bằng cặp tham số frequency + timeframe (xem 8.7).

8.2.6. ALERT & STORE

  • ALERT: sinh thông báo (gửi email, webhook, Slack, kích hoạt SOAR/active response).
  • STORE: ghi vào index để truy vấn (Elasticsearch/OpenSearch/Wazuh indexer). Có chính sách retention (ví dụ hot 7 ngày, warm 30 ngày, cold/frozen cho compliance 1 năm).

8.3. Phân loại công cụ phòng thủ: AV / EDR / SIEM / SOAR / XDR / NDR

Vì sao phải phân biệt: các nhóm này hay bị marketing trộn lẫn, nhưng vị trí của chúng trong kiến trúc và dữ liệu chúng nhìn thấy là khác nhau.

Công cụ Phạm vi quan sát Đơn vị dữ liệu Hành động chính Ví dụ
AV (Antivirus) 1 endpoint File, signature, hash Quét/chặn malware đã biết ClamAV, Defender (chế độ AV)
EDR (Endpoint Detection & Response) 1 endpoint, hành vi Process tree, syscall, registry, network của host Phát hiện hành vi, cô lập host, kill process CrowdStrike Falcon, Defender for Endpoint, Wazuh (một phần)
NDR (Network Detection & Response) Lưu lượng mạng Packet, flow, metadata (JA3, DNS, TLS SNI) Phát hiện C2/exfil/lateral movement Zeek, Suricata, Corelight
SIEM Toàn hạ tầng (log) Event đã chuẩn hóa từ mọi nguồn Tương quan, lưu trữ, điều tra, compliance Wazuh, Splunk, Elastic SIEM
SOAR (Orchestration, Automation & Response) Quy trình phản ứng Case/playbook Tự động hóa phản ứng (block, ticket, enrich) Shuffle, TheHive+Cortex, Splunk SOAR
XDR (Extended Detection & Response) EDR + NDR + email + cloud, hợp nhất Sự kiện đa lớp đã tương quan sẵn của 1 vendor Phát hiện chéo nhiều lớp Bộ XDR của một hãng đơn

Phân biệt cốt lõi: - EDR vs SIEM: EDR là chuyên gia một endpoint (nhìn sâu telemetry process); SIEM là người tổng hợp toàn cảnh (nhìn rộng, nông hơn per-source). Wazuh thú vị vì nó vừa là agent kiểu EDR nhẹ vừa là SIEM (agent thu telemetry endpoint + manager correlation). - SIEM vs SOAR: SIEM phát hiện; SOAR phản ứng theo playbook. Wazuh có "Active Response" — một dạng SOAR mini tích hợp. - XDR vs SIEM: XDR thường khóa trong hệ sinh thái một vendor và tương quan sẵn; SIEM mở, nhận mọi nguồn, nhưng tự xây correlation.


8.4. WAZUH — Tổng quan và kiến trúc

8.4.1. Wazuh là gì

Wazuh là nền tảng bảo mật mã nguồn mở (fork lịch sử từ OSSEC HIDS, mở rộng thêm indexer/dashboard từ hệ Elastic/OpenSearch). Nó cung cấp đồng thời:

  • HIDS (Host Intrusion Detection): phân tích log, FIM, rootcheck;
  • Quản lý lỗ hổng (Vulnerability Detection);
  • SCA (Security Configuration Assessment — kiểm tra cấu hình theo CIS);
  • Active Response (phản ứng tự động);
  • Tích hợp khung MITRE ATT&CK;
  • Vai trò SIEM nhờ indexer + dashboard.

Lưu ý phiên bản: chi tiết cổng, tên service và cấu trúc một số module thay đổi theo major version (3.x → 4.x). Tài liệu này mô tả theo dòng 4.x. Các con số quan trọng (port 1514/1515/55000/1516/9200/443) nên đối chiếu lại với phiên bản cụ thể bạn triển khai.

8.4.2. Bốn thành phần chính

        ┌──────────────────────────────────────────────────────────────┐
        │                         WAZUH SERVER                          │
        │                                                               │
  Agent │   ┌───────────────┐         ┌────────────────────────────┐    │
  (host)│   │  wazuh-manager │         │   FILTER / FORWARD          │    │
  ──────┼──▶│  - analysisd   │────────▶│   Filebeat ──▶ Indexer      │    │
 1514/  │   │  - remoted     │ alerts  │                             │    │
 1515   │   │  - logcollector│ .json   └────────────┬───────────────┘    │
        │   │  - syscheckd   │                      │                    │
        │   │  - active-resp │                      ▼                    │
        │   └───────────────┘            ┌────────────────────┐          │
        └────────────────────────────────│   WAZUH INDEXER    │──────────┘
                                          │  (OpenSearch)      │
                                          └─────────┬──────────┘
                                                    │ 9200 (REST)
                                          ┌─────────▼──────────┐
                                          │  WAZUH DASHBOARD   │ 443
                                          │  (OpenSearch Dash) │
                                          └────────────────────┘
Thành phần Vai trò Daemon/process chính Cổng tiêu biểu
Wazuh agent Cài trên endpoint; thu log, FIM, SCA, gửi về manager wazuh-agentd, wazuh-logcollector, wazuh-syscheckd, wazuh-execd (client)
Wazuh manager (server) Nhận, decode, rule, alert, quản lý agent wazuh-remoted (nhận), wazuh-analysisd (phân tích), wazuh-authd (enroll), wazuh-modulesd (vuln/SCA) 1514 (data), 1515 (enroll), 1516 (cluster), 55000 (API REST)
Wazuh indexer Lưu trữ + tìm kiếm alert (OpenSearch) wazuh-indexer 9200 (REST), 9300 (transport node-to-node)
Wazuh dashboard Giao diện web, trực quan hóa, quản lý wazuh-dashboard 443/5601

8.4.3. Các daemon bên trong manager và luồng dữ liệu nội bộ

agent ─(1514)─▶ wazuh-remoted ─▶ (queue: /var/ossec/queue/sockets/queue)
                                        │
                                        ▼
                                 wazuh-analysisd
                                  ├─ PreDecoding (tách timestamp/host/program)
                                  ├─ Decoding    (decoders/*.xml → trích field)
                                  ├─ Rule matching (rules/*.xml → gán level/id)
                                  └─ nếu khớp ⇒ ghi alert
                                        │
                       ┌────────────────┼─────────────────┐
                       ▼                ▼                 ▼
            /var/ossec/logs/        active-response   archives (nếu bật)
            alerts/alerts.json      (wazuh-execd)     alerts/archives.json
                       │
                       ▼
                   Filebeat ──▶ Wazuh indexer ──▶ Dashboard

Đường đi của một event (decapsulation logic): 1. Agent đọc dòng log → đóng gói cùng metadata (agent id, location) → mã hóa → gửi qua 1514. 2. wazuh-remoted giải mã, đặt event vào hàng đợi local socket. 3. wazuh-analysisd lấy event ra, chạy PreDecoding → Decoding → Rule matching. 4. Nếu một rule khớp với level ≥ ngưỡng (<log_alert_level>, mặc định 3), event được ghi vào alerts.json. 5. Nếu rule có <active-response> liên kết, wazuh-execd chạy script phản ứng trên agent/manager. 6. Filebeat đọc alerts.json, đẩy vào indexer (9200). 7. Dashboard truy vấn indexer để hiển thị.


8.5. Luồng agent → manager: enrollment và truyền dữ liệu (byte/port-level)

8.5.1. Hai cổng và vì sao tách

Cổng Giao thức Daemon nghe Mục đích
1515/TCP TLS wazuh-authd Enrollment (đăng ký agent, cấp key) — chỉ dùng một lần khi agent gia nhập
1514/UDP hoặc TCP Mã hóa khóa chia sẻ (Blowfish/AES tùy cấu hình) wazuh-remoted Truyền dữ liệu event liên tục
1516/TCP — wazuh-clusterd Giao tiếp giữa các manager trong cluster
55000/TCP HTTPS wazuh-apid API quản trị (RBAC, JWT)

Vì sao tách enrollment (1515) khỏi data (1514): Enrollment là thao tác nhạy cảm (trao khóa). Tách cổng cho phép quản trị bật authd chỉ trong cửa sổ đăng ký rồi tắt, giảm bề mặt tấn công. Kênh data 1514 dùng khóa đối xứng đã trao, tối ưu cho throughput cao và stateless (UDP) hoặc tin cậy (TCP).

8.5.2. Quy trình enrollment (state machine, từng bước)

   AGENT                                   MANAGER (authd:1515)
     │                                            │
     │ 1. TLS ClientHello ───────────────────────▶│
     │◀── 2. TLS handshake hoàn tất (TLS 1.2/1.3) │
     │                                            │
     │ 3. Gửi yêu cầu enroll:                      │
     │    "OSSEC A:'<agent_name>'" [+ password]    │
     │    (tùy chọn host_name/ip)        ─────────▶│
     │                                            │ 4. authd:
     │                                            │    - kiểm tra password (nếu bật)
     │                                            │    - cấp agent ID (vd 001)
     │                                            │    - sinh client.key entry
     │◀── 5. "OSSEC K:'<id> <name> <ip> <key>'" ──│
     │                                            │
     │ 6. Lưu vào /var/ossec/etc/client.keys       │
     │ 7. Khởi động wazuh-agentd, kết nối 1514     │
     ▼                                            ▼

Cấu trúc một dòng client.keys:

001 web01 any 6b2c...e1f9a3...   (64 hex chars = khóa 256-bit dạng pre-shared)
└┬┘ └─┬─┘ └┬┘ └──────────┬─────┘
 │    │    │             └─ Pre-shared key (hex) dùng để mã hóa kênh 1514
 │    │    └─ IP cho phép ("any" = bất kỳ)
 │    └─ Tên agent
 └─ Agent ID (3 chữ số)
Trường Kích thước Ý nghĩa Ví dụ
Agent ID thường 3 ký tự số Định danh duy nhất agent trong manager 001
Name chuỗi Tên agent web01
Allowed IP chuỗi IP/any được phép kết nối với ID này 192.0.2.10
Key 64 hex (≈256-bit) Khóa chia sẻ để mã hóa/giải mã message 1514 6b2c...

Cảnh báo: - client.keys là bí mật cấp host — quyền 640 root:wazuh. Lộ key cho phép giả mạo agent và inject log giả. - Bật <use_password>yes</use_password> cho authd để chống đăng ký trái phép. Tốt hơn: dùng chứng chỉ (CA verification) cho cả manager-verify-agent và agent-verify-manager để chống MITM ở bước enroll. - Trùng tên/ID agent gây "agent flapping" — đặt tên duy nhất, ổn định.

8.5.3. Định dạng message data trên 1514 (mức khái niệm field)

Một message từ agent gồm các phần logic sau trước khi mã hóa:

[counters][random][MSG]
Phần Mục đích
Counter (global + local) Chống replay — manager từ chối message có counter cũ
Random padding Làm nhiễu để chống phân tích
MSG Payload thực: <msg_type>:<location>:<log line>

Phần location cho biết log đến từ đâu trên agent, ví dụ /var/log/auth.log, để analysisd biết áp decoder nào. Toàn bộ được mã hóa bằng khóa trong client.keys (Wazuh 4.x mặc định AES, có thể cấu hình Blowfish cho tương thích cũ).


8.6. File cấu hình ossec.conf — mổ xẻ từng khối

ossec.conf (đường dẫn /var/ossec/etc/ossec.conf) là cấu hình XML chính cho cả manager lẫn agent. Cấu trúc gốc nằm trong thẻ <ossec_config>. Dưới đây là các khối quan trọng kèm giải thích từng tham số.

8.6.1. Khối <global> và alert level (trên manager)

<ossec_config>
  <global>
    <jsonout_output>yes</jsonout_output>     <!-- ghi alerts.json (phục vụ Filebeat/indexer) -->
    <alerts_log>yes</alerts_log>             <!-- ghi alerts.log dạng text -->
    <logall>no</logall>                       <!-- không lưu mọi event vào archives -->
    <logall_json>no</logall_json>
    <email_notification>no</email_notification>
  </global>

  <alerts>
    <log_alert_level>3</log_alert_level>      <!-- chỉ ghi alert khi rule level >= 3 -->
    <email_alert_level>12</email_alert_level> <!-- gửi email khi level >= 12 -->
  </alerts>
Tham số Giá trị ví dụ Ý nghĩa Vì sao
jsonout_output yes Sinh alerts.json Filebeat cần JSON để đẩy indexer
logall no Không lưu toàn bộ event vào archives logall=yes sinh dữ liệu khổng lồ — chỉ bật khi cần điều tra/forensic
log_alert_level 3 Ngưỡng ghi alert Lọc nhiễu — level 0–2 là noise
email_alert_level 12 Ngưỡng gửi mail Tránh spam — chỉ sự cố nghiêm trọng

8.6.2. Khối <remote> (manager nghe agent)

  <remote>
    <connection>secure</connection>   <!-- secure = mã hóa bằng client.keys -->
    <port>1514</port>
    <protocol>tcp</protocol>          <!-- tcp đảm bảo phân phối; udp nhẹ hơn nhưng có thể rớt -->
    <queue_size>131072</queue_size>
  </remote>
Tham số Ý nghĩa Lưu ý
connection secure (mã hóa) hoặc syslog (nhận syslog thô) Dùng secure cho agent; syslog cho thiết bị không cài agent được
protocol tcp/udp TCP ưu tiên: không mất log; UDP cho quy mô cực lớn chịu được mất mát
queue_size Số message đệm Tăng nếu burst lớn để tránh drop

8.6.3. Khối <client> (trên agent)

  <client>
    <server>
      <address>10.0.0.5</address>      <!-- IP/hostname manager -->
      <port>1514</port>
      <protocol>tcp</protocol>
    </server>
    <enrollment>
      <enabled>yes</enabled>
      <manager_address>10.0.0.5</manager_address>
      <port>1515</port>
      <agent_name>web01</agent_name>
    </enrollment>
    <crypto_method>aes</crypto_method>
    <notify_time>10</notify_time>        <!-- keepalive mỗi 10s -->
    <time-reconnect>60</time-reconnect>  <!-- thử lại sau 60s nếu mất kết nối -->
  </client>

8.6.4. Khối <localfile> (logcollector — agent đọc nguồn log nào)

  <localfile>
    <log_format>syslog</log_format>
    <location>/var/log/auth.log</location>
  </localfile>

  <localfile>
    <log_format>json</log_format>
    <location>/var/log/myapp/audit.json</location>
  </localfile>

  <localfile>
    <log_format>command</log_format>
    <command>df -P</command>           <!-- chạy lệnh, lấy output làm log -->
    <frequency>360</frequency>          <!-- mỗi 360s -->
  </localfile>
log_format Dùng cho
syslog File text dòng kiểu syslog (auth.log, messages)
json Mỗi dòng là một JSON object — Wazuh tự parse field
command / full_command Lấy output lệnh làm event định kỳ
eventchannel Windows Event Log (Security, System, Application)
audit Linux auditd

8.6.5. Khối <syscheck> và <rootcheck> — xem chi tiết ở 8.9.

8.6.6. Nạp lại cấu hình

# Kiểm tra cú pháp cấu hình + rule/decoder trước khi restart (rất quan trọng)
/var/ossec/bin/wazuh-logtest        # test rule/decoder tương tác
/var/ossec/bin/wazuh-analysisd -t   # -t = test mode, báo lỗi cú pháp rồi thoát

# Khởi động lại
systemctl restart wazuh-manager
# hoặc
/var/ossec/bin/wazuh-control restart

Lưu ý: Luôn chạy wazuh-analysisd -t trước khi restart production. Một lỗi cú pháp trong local_rules.xml khiến analysisd không khởi động → mù toàn bộ.


8.7. DECODER — bóc tách field từ log thật

8.7.1. Decoder là gì

Decoder là quy tắc XML chỉ cho wazuh-analysisd cách trích các trường (srcip, srcuser, ...) ra khỏi một dòng log thô. Decoder không sinh alert — nó chỉ chuẩn bị dữ liệu cho rule.

Đường dẫn: - Decoder gốc: /var/ossec/ruleset/decoders/*.xml (không sửa — bị ghi đè khi update). - Decoder tùy biến: /var/ossec/etc/decoders/local_decoder.xml.

8.7.2. Hai loại decoder và thuộc tính

Loại Thẻ Vai trò
Parent decoder <decoder name="..."> Nhận diện chương trình/nguồn (qua program_name hoặc prematch)
Child decoder <decoder name="..."><parent>...</parent> Trích field cụ thể, chạy sau khi parent khớp
Thẻ con Ý nghĩa Thứ tự xử lý
<program_name> So khớp tên program (lấy từ PreDecoding) Lọc nhanh trước
<prematch> Regex phải khớp trước thì decoder mới chạy <regex> Bộ lọc tầng 1
<regex> Regex trích giá trị; nhóm bắt () ánh xạ sang <order> Trích field
<order> Danh sách tên field tương ứng nhóm bắt trong regex Đặt tên

8.7.3. Ví dụ thực tế — decoder cho SSH (đã có sẵn trong Wazuh, ở đây mổ xẻ)

Log thô:

Jun 19 10:22:41 web01 sshd[2931]: Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2

PreDecoding (tự động, không cần XML) tách phần header syslog:

hostname    = web01
program_name= sshd
timestamp   = Jun 19 10:22:41
log         = "Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2"

Parent decoder (nhận diện sshd):

<decoder name="sshd">
  <program_name>^sshd</program_name>
</decoder>

Child decoder (trích user + ip):

<decoder name="ssh-failed-invalid">
  <parent>sshd</parent>
  <prematch>^Failed password for invalid user</prematch>
  <regex offset="after_prematch">^ (\S+) from (\d+.\d+.\d+.\d+) port (\d+)</regex>
  <order>srcuser, srcip, srcport</order>
</decoder>

Giải thích từng phần: - <parent>sshd</parent>: chỉ chạy nếu parent sshd đã khớp. - <prematch>: yêu cầu dòng bắt đầu bằng Failed password for invalid user. Nếu không khớp → bỏ qua, tiết kiệm CPU (vì sao: regex đầy đủ tốn kém, prematch là cổng lọc rẻ). - offset="after_prematch": bắt đầu <regex> ngay sau phần đã prematch. Đây là tối ưu để regex ngắn gọn. - Nhóm bắt (\S+), (\d+.\d+.\d+.\d+), (\d+) lần lượt ánh xạ sang srcuser, srcip, srcport qua <order>.

Lưu ý cú pháp regex: Wazuh hỗ trợ cú pháp OS_Regex nội bộ (nhanh, hạn chế) và PCRE2 qua thuộc tính type="pcre2". Trong OS_Regex, \d, \S là lớp ký tự; . khớp ký tự bất kỳ. Khi cần regex phức tạp (lookahead...), dùng <regex type="pcre2">.

Kết quả sau decode (field sẵn sàng cho rule):

{
  "program_name": "sshd",
  "srcuser": "admin",
  "srcip": "203.0.113.5",
  "srcport": "51244"
}

8.7.4. Ví dụ — viết decoder tùy biến cho log ứng dụng tự định nghĩa

[DEMO] Decoder dưới đây chỉ minh hoạ cơ chế parent/child + <order>; cần kiểm thử bằng wazuh-logtest và neo regex trước khi đưa vào production.

Giả sử ứng dụng nội bộ sinh log:

Jun 19 11:05:00 app01 paywall: AUTH_FAIL user=jdoe ip=198.51.100.7 reason=bad_token txn=AB12

local_decoder.xml:

<decoder name="paywall">
  <program_name>^paywall</program_name>
</decoder>

<decoder name="paywall-authfail">
  <parent>paywall</parent>
  <prematch>^AUTH_FAIL </prematch>
  <regex>user=(\S+) ip=(\d+.\d+.\d+.\d+) reason=(\S+) txn=(\S+)</regex>
  <order>srcuser, srcip, reason, txn_id</order>
</decoder>

Kiểm thử ngay bằng wazuh-logtest:

/var/ossec/bin/wazuh-logtest
# Dán dòng log vào stdin:
Jun 19 11:05:00 app01 paywall: AUTH_FAIL user=jdoe ip=198.51.100.7 reason=bad_token txn=AB12

Output mẫu:

**Phase 1: Completed pre-decoding.
        full event: 'Jun 19 11:05:00 app01 paywall: AUTH_FAIL ...'
        timestamp: 'Jun 19 11:05:00'
        hostname: 'app01'
        program_name: 'paywall'
**Phase 2: Completed decoding.
        name: 'paywall'
        srcuser: 'jdoe'
        srcip: '198.51.100.7'
        reason: 'bad_token'
        txn_id: 'AB12'
**Phase 3: Completed filtering (rules).
        ... (chưa có rule khớp)

Cảnh báo: Decoder sai (regex tham lam, thiếu anchor ^) có thể trích nhầm field hoặc làm chậm analysisd dưới tải cao → DoS gián tiếp. Luôn neo regex và test với wazuh-logtest trước khi triển khai.


8.8. RULE — phát hiện và phân loại

8.8.1. Rule là gì

Rule quyết định event nào trở thành alert, gán level (0–16), id, nhóm, và (tùy chọn) ánh xạ MITRE. Rule chạy sau decoder, dựa trên field đã trích.

Đường dẫn: - Rule gốc: /var/ossec/ruleset/rules/*.xml (không sửa). - Rule tùy biến: /var/ossec/etc/rules/local_rules.xml.

8.8.2. Thuộc tính và thẻ của một rule

Thuộc tính/Thẻ Ý nghĩa Ví dụ
id Định danh duy nhất. Khoảng 100000–120000 dành cho rule tùy biến (không trùng rule gốc) 100100
level Mức nghiêm trọng 0–16 (0 = bỏ qua/không alert) 10
<if_sid> Chỉ xét nếu rule có ID này đã khớp trước (xâu chuỗi rule) <if_sid>5710</if_sid>
<if_matched_sid> Dùng với correlation: rule con khớp khi rule SID kia khớp đủ tần suất —
<match> So khớp chuỗi con (substring) trong log <match>AUTH_FAIL</match>
<regex> So khớp regex trong log/field <regex>reason=bad_token</regex>
<field> So khớp một field đã decode <field name="srcuser">admin</field>
<frequency> Số lần khớp cần để kích hoạt (correlation) 8
<timeframe> Cửa sổ thời gian (giây) cho frequency 120
<same_source_ip/> Yêu cầu cùng srcip mới đếm (khóa nhóm) —
<group> Nhóm phân loại (authentication_failed, attack...) authentication_failures,
<mitre><id> Mã kỹ thuật MITRE ATT&CK T1110
<description> Mô tả hiển thị trên alert —

8.8.3. Thang LEVEL của Wazuh (0–16)

Level Ý nghĩa khái quát
0 Bỏ qua hoàn toàn (không log) — dùng để giảm FP
1–3 Thông tin / ít quan trọng
4–6 Đáng chú ý (lỗi auth đơn lẻ, cấu hình)
7–9 Quan trọng (nhiều lỗi, hành vi đáng ngờ)
10–12 Tấn công khả năng cao (brute-force phát hiện)
13–16 Nghiêm trọng (xâm nhập thành công, hệ thống)

8.8.4. Ví dụ — rule stateless ánh vào decoder paywall ở 8.7.4

[DEMO] Rule mẫu cho decoder paywall ở trên — minh hoạ cấu trúc <field>, level, ánh xạ MITRE; điều chỉnh level và điều kiện theo môi trường trước khi dùng thật.

local_rules.xml:

<group name="paywall,authentication,">

  <!-- Rule cơ sở: một lần AUTH_FAIL của paywall -->
  <rule id="100100" level="5">
    <decoded_as>paywall</decoded_as>
    <field name="reason">bad_token</field>
    <description>Paywall: xác thực thất bại do token sai (user $(srcuser), ip $(srcip))</description>
    <group>authentication_failed,</group>
    <mitre>
      <id>T1078</id>   <!-- Valid Accounts (lạm dụng credential) -->
    </mitre>
  </rule>

</group>

Giải thích: - <decoded_as>paywall</decoded_as>: chỉ áp dụng cho event đã được decoder paywall xử lý. - <field name="reason">bad_token</field>: khớp field reason đã decode. - $(srcuser), $(srcip): nội suy field vào mô tả alert.

8.8.5. Ví dụ trọng tâm — correlation brute-force (frequency + timeframe)

Đây là ví dụ stateful kinh điển: nhiều lần login fail từ cùng IP trong cửa sổ thời gian → một alert brute-force.

[DEMO] Rule tương quan minh hoạ cơ chế frequency+timeframe+same_source_ip; ngưỡng cần hiệu chỉnh theo lưu lượng thật (xem quy trình tuning ở 8.14). Việc gắn mã T#### được trình bày chi tiết tại Chương 15.

<group name="paywall,authentication,attack,">

  <!-- Rule tương quan: >=8 lần rule 100100 từ CÙNG srcip trong 120 giây -->
  <rule id="100110" level="10" frequency="8" timeframe="120">
    <if_matched_sid>100100</if_matched_sid>
    <same_source_ip />
    <description>Paywall: BRUTE-FORCE token — >=8 lần thất bại từ $(srcip) trong 120s</description>
    <group>authentication_failures,brute_force,</group>
    <mitre>
      <id>T1110</id>   <!-- Brute Force -->
    </mitre>
  </rule>

</group>

Cơ chế (state machine của analysisd):

Khởi tạo cho mỗi srcip một bộ đếm + timestamp đầu cửa sổ.

  Sự kiện rule 100100 khớp, srcip=X:
    ┌─ Tìm bucket cho key=X
    │     ├─ Nếu chưa có: tạo bucket{count=1, t0=now}
    │     └─ Nếu có:
    │           ├─ Nếu (now - t0) > timeframe(120s): RESET bucket{count=1, t0=now}
    │           └─ Ngược lại: count++
    │                 └─ Nếu count >= frequency(8): KÍCH HOẠT rule 100110 (level 10) → ALERT
    │                       (sau khi kích hoạt, đặt lại để tránh spam liên tục)
    ▼

Bảng minh họa với chuỗi sự kiện (frequency=8, timeframe=120):

t (s) srcip count(X) Hành động
0 203.0.113.5 1 tạo bucket
5 203.0.113.5 2 count++
... ... ... ...
40 203.0.113.5 8 count==8 ≤ 120s → ALERT 100110 level 10
200 203.0.113.5 1 t0 cũ hết hạn (200-0>120) → reset

Vì sao thiết kế frequency+timeframe+same_source_ip: - same_source_ip là khóa nhóm: không có nó, 8 lần fail từ 8 IP khác nhau (ví dụ password spraying nhẹ phân tán) sẽ gộp nhầm. Có nó, ta tách bucket theo IP để phát hiện đúng brute-force tập trung. - timeframe định nghĩa "tốc độ" — phân biệt brute-force (8 lần/2 phút) với 8 lần fail rải rác cả ngày (người dùng quên mật khẩu).

Các khóa nhóm khác: <same_source_user/>, <same_destination_ip/>, <different_source_ip/> (cho phát hiện distributed). Ngoài ra <if_matched_group> cho phép đếm theo nhóm rule thay vì một SID.

8.8.6. Ghi đè và điều chỉnh rule gốc (overwrite / <if_sid>)

Không sửa file gốc; thay vào đó trong local_rules.xml:

[DEMO] Ví dụ ghi đè/ngoại lệ minh hoạ cơ chế overwrite và <if_sid>; dải srcip tin cậy phải khai theo mạng nội bộ thực tế.

<!-- Hạ level một rule gốc gây nhiễu trong môi trường của bạn -->
<rule id="5710" level="0" overwrite="yes">
  <description>sshd: non-existent user (bị làm câm trong subnet quản trị)</description>
</rule>

Hoặc tạo exception có điều kiện:

<rule id="100200" level="0">
  <if_sid>5710</if_sid>
  <field name="srcip">10.0.0.0/8</field>   <!-- bỏ qua nếu từ mạng nội bộ tin cậy -->
  <description>Bỏ qua sshd fail từ mạng quản trị nội bộ</description>
</rule>

8.9. FIM / Syscheck — giám sát toàn vẹn tệp

8.9.1. FIM là gì

FIM (File Integrity Monitoring) — module syscheckd — phát hiện thay đổi của file/thư mục/registry: tạo, sửa, xóa. Dùng để bắt webshell, tampering binary, sửa file cấu hình nhạy cảm.

8.9.2. Cơ chế

Syscheck duy trì một CSDL trạng thái (FIM database, SQLite trong Wazuh 4.x) lưu cho mỗi file:

Thuộc tính lưu Kích thước/kiểu Ý nghĩa
size int Kích thước byte
perm mode bits Quyền (rwx)
uid/gid int Chủ sở hữu/nhóm
inode int Số inode (Linux)
mtime timestamp Thời điểm sửa nội dung
md5 128-bit (32 hex) Hash MD5
sha1 160-bit (40 hex) Hash SHA-1
sha256 256-bit (64 hex) Hash SHA-256 (mặc định khuyến nghị)

Hai chế độ:

Chế độ Cơ chế Độ trễ phát hiện
Scheduled scan Quét định kỳ (<frequency>), so sánh hash mới với DB Theo chu kỳ (giây/giờ)
Real-time Dùng inotify (Linux) / ReadDirectoryChangesW (Windows) để nhận sự kiện kernel tức thì Gần tức thì

Vì sao dùng hash chứ không chỉ mtime: attacker có thể touch để khôi phục mtime sau khi sửa. Hash nội dung (SHA-256) bắt được thay đổi nội dung dù metadata bị làm giả. SHA-256 chọn vì kháng va chạm (collision-resistant) tốt hơn MD5/SHA-1.

8.9.3. Ví dụ — cấu hình <syscheck> trong ossec.conf

[PROD] Cấu hình dưới đây đủ chặt để dùng thật cho host web tiêu biểu (realtime đúng chỗ, loại trừ nhiễu, <nodiff> cho đường nhạy cảm); vẫn nên rà soát danh sách <directories> theo từng hệ thống.

<syscheck>
  <disabled>no</disabled>
  <frequency>43200</frequency>            <!-- quét scheduled mỗi 12 giờ -->
  <scan_on_start>yes</scan_on_start>

  <!-- Thư mục web: realtime + report nội dung thay đổi -->
  <directories check_all="yes" realtime="yes" report_changes="yes">/var/www/html</directories>

  <!-- File cấu hình nhạy cảm: theo dõi mọi thuộc tính -->
  <directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin</directories>

  <!-- Loại trừ để giảm nhiễu -->
  <ignore>/etc/mtab</ignore>
  <ignore>/var/www/html/cache</ignore>
  <ignore type="sregex">.log$</ignore>

  <!-- Không tính hash file lớn để tiết kiệm -->
  <skip_nfs>yes</skip_nfs>
  <nodiff>/etc/ssl/private</nodiff>       <!-- không lưu diff nội dung (chứa secret) -->
</syscheck>
Thuộc tính <directories> Ý nghĩa
check_all="yes" Kiểm tra size+perm+owner+mtime+inode+các hash
realtime="yes" Bật inotify cho thư mục này
report_changes="yes" Lưu diff nội dung (cho file text) để xem chính xác dòng nào đổi
whodata="yes" Dùng auditd để biết ai (uid/process) đã thay đổi (cao cấp hơn realtime)

8.9.4. Ví dụ — alert FIM mẫu (webshell)

Khi attacker drop shell.php vào /var/www/html, syscheck (realtime) sinh event, khớp rule FIM gốc (nhóm syscheck, các rule 550–554), alert.json (quy trình điều tra một alert như vậy xem Chương 10):

{
  "rule": { "id": "554", "level": 7, "description": "File added to the system." },
  "syscheck": {
    "path": "/var/www/html/shell.php",
    "event": "added",
    "sha256_after": "9f2c...e1",
    "uid_after": "33", "gname_after": "www-data",
    "mtime_after": "2026-06-19T11:30:02"
  },
  "location": "syscheck"
}

Cảnh báo: - report_changes/nodiff: không lưu diff của file chứa secret (private key, /etc/shadow) — diff được lưu trong DB Wazuh có thể rò bí mật. Dùng <nodiff> cho đường nhạy cảm. - Realtime trên thư mục cực lớn (vd /) làm cạn inotify watches kernel (fs.inotify.max_user_watches) → mất giám sát thầm lặng. Chỉ realtime nơi cần.


8.10. Active Response — phản ứng tự động

8.10.1. Active Response là gì

Active Response (AR) cho phép Wazuh tự chạy một lệnh (script) khi một rule khớp — ví dụ chặn IP tấn công bằng firewall. Đây là khả năng "SOAR mini" tích hợp, do wazuh-execd thực thi trên agent hoặc manager.

8.10.2. Cơ chế (luồng + state)

Rule X khớp (vd brute-force level >=10)
        │
        ▼
analysisd kiểm tra có <active-response> nào liên kết (qua <rules_id> hoặc <level>)
        │  có
        ▼
manager gửi lệnh AR xuống agent đích (qua kênh 1514)
        │
        ▼
wazuh-execd trên agent gọi script trong /var/ossec/active-response/bin/
        ├─ action = "add"     → chặn (vd thêm rule iptables DROP srcip)
        └─ sau <timeout> giây  → tự gọi lại action = "delete" → gỡ chặn

Script AR nhận tham số qua stdin (JSON) trong Wazuh 4.x: gồm command (add/delete), và parameters.alert (toàn bộ alert, có srcip). Vì sao có timeout: chặn vĩnh viễn có rủi ro tự DoS (chặn nhầm IP hợp lệ, hoặc IP NAT chung) — timeout cho phép gỡ tự động.

8.10.3. Ví dụ thực tế — chặn IP brute-force bằng firewall-drop

[DEMO] Cấu hình AR dưới đây minh hoạ luồng chặn/gỡ tự động; trước khi bật trên production phải có allowlist IP hạ tầng và giới hạn <location> (xem phần Cảnh báo cuối mục).

Bước 1 — định nghĩa command và active-response trong ossec.conf (trên manager):

<!-- Command: trỏ tới script tích hợp sẵn firewall-drop -->
<command>
  <name>firewall-drop</name>
  <executable>firewall-drop</executable>   <!-- /var/ossec/active-response/bin/firewall-drop -->
  <timeout_allowed>yes</timeout_allowed>
</command>

<!-- Active response: chạy command trên agent có sự kiện, chặn 600s -->
<active-response>
  <command>firewall-drop</command>
  <location>local</location>          <!-- local = chạy trên agent nơi sự kiện xảy ra -->
  <rules_id>100110</rules_id>         <!-- chính là rule brute-force ở 8.8.5 -->
  <timeout>600</timeout>              <!-- gỡ chặn sau 600 giây -->
</active-response>
<location> Chạy ở đâu
local Trên agent phát sinh sự kiện
server Trên manager
defined-agent Trên một agent chỉ định (<agent_id>)
all Mọi agent (cẩn thận!)

Bước 2 — firewall-drop (script tích hợp) trên Linux agent thực thi tương đương:

# action=add:
iptables -I INPUT -s 203.0.113.5 -j DROP
# sau 600s, action=delete:
iptables -D INPUT -s 203.0.113.5 -j DROP

Bước 3 — alert AR ghi trong active-responses.log trên agent:

2026-06-19 11:31:00 /var/ossec/active-response/bin/firewall-drop: add - 203.0.113.5 - 1718796660.123456 - 100110

Cảnh báo: - Chống self-DoS: attacker spoof srcip = IP gateway/DNS nội bộ trong log để ép Wazuh chặn hạ tầng của chính bạn. Luôn duy trì allowlist (script firewall-drop có cơ chế bỏ qua IP trong danh sách trắng); không bật AR all cho rule dễ bị giả mạo. - AR chạy bằng quyền cao (iptables cần root) → script AR là bề mặt tấn công; chỉ dùng script đã kiểm duyệt, quyền file chặt. - Ưu tiên AR local thay vì all để giới hạn tác động.


8.11. Vulnerability Detection

8.11.1. Là gì và cơ chế

Module wazuh-modulesd (Vulnerability Detector) đối chiếu danh sách package đã cài (do agent gửi qua syscollector) với CVE feed để báo host nào có lỗ hổng nào.

Luồng:

1. syscollector (trên agent) liệt kê package + version + OS  ──▶ manager
2. Vulnerability Detector tải/cập nhật feed CVE
      (nguồn: NVD, Canonical/Ubuntu OVAL, Red Hat, Debian, Microsoft MSU, ALAS...)
3. Đối chiếu: package P version V vs điều kiện "ảnh hưởng nếu V < V_fixed"
4. Sinh alert nếu khớp, kèm CVE id, CVSS, package, version vá

Lưu ý kiến trúc theo phiên bản: cách cấu hình và nguồn feed của Vulnerability Detection đã thay đổi đáng kể giữa các minor 4.x (mô hình feed cũ dựa OVAL/NVD trực tiếp vs mô hình "Vulnerability Detection" mới dựa Content Manager/CTI của Wazuh). Hãy kiểm chứng cú pháp khối cấu hình với tài liệu đúng version đang chạy. Phần dưới minh họa dạng cấu hình OVAL/NVD kiểu cũ để hiểu nguyên lý.

8.11.2. Ví dụ — cấu hình kiểu cũ (minh họa nguyên lý)

<vulnerability-detector>
  <enabled>yes</enabled>
  <interval>5m</interval>
  <run_on_start>yes</run_on_start>

  <provider name="canonical">          <!-- Ubuntu OVAL -->
    <enabled>yes</enabled>
    <os>focal</os>
    <os>jammy</os>
    <update_interval>1h</update_interval>
  </provider>

  <provider name="nvd">                <!-- NVD bổ sung CVSS/CPE -->
    <enabled>yes</enabled>
    <update_interval>1h</update_interval>
  </provider>
</vulnerability-detector>

8.11.3. Ví dụ — alert CVE mẫu

{
  "rule": { "level": 7, "description": "CVE-2024-XXXX affects openssl" },
  "data": {
    "vulnerability": {
      "cve": "CVE-2024-XXXX",
      "package": { "name": "openssl", "version": "3.0.2-0ubuntu1.10" },
      "severity": "High",
      "cvss": { "cvss3": { "base_score": "7.5" } },
      "status": "Active",
      "reference": "https://ubuntu.com/security/CVE-2024-XXXX"
    }
  }
}

Lưu ý: Vulnerability Detection báo lỗ hổng tiềm năng theo version, không xác nhận khả năng khai thác thực tế (chưa biết có patch backport của distro hay không trong mọi trường hợp). Cần đối chiếu với reachability/exposure trước khi ưu tiên vá — tránh "CVE noise".


8.12. SCA — Security Configuration Assessment

8.12.1. SCA là gì

SCA kiểm tra cấu hình hệ thống so với baseline (CIS Benchmark, ví dụ "SSH không cho phép root login", "password phải đặt độ phức tạp"). Module này chạy các policy dạng YAML trên agent và báo pass/fail từng kiểm tra.

8.12.2. Cơ chế — cấu trúc một policy check

Policy SCA (file YAML trong /var/ossec/ruleset/sca/) gồm các checks, mỗi check có rules đánh giá theo logic.

[DEMO] Check mẫu dưới đây minh hoạ cú pháp rules/condition; dùng trực tiếp policy CIS chính thức của Wazuh thay vì tự chép từng check.

policy:
  id: "cis_debian12"
  name: "CIS Debian Linux 12 Benchmark"

checks:
  - id: 5001
    title: "Ensure SSH PermitRootLogin is disabled"
    description: "PermitRootLogin nên = no để chặn đăng nhập root trực tiếp"
    rationale: "Giảm bề mặt brute-force vào tài khoản quyền cao nhất"
    remediation: "Đặt 'PermitRootLogin no' trong /etc/ssh/sshd_config rồi reload sshd"
    condition: all          # tất cả rules phải đúng thì PASS
    rules:
      - 'f:/etc/ssh/sshd_config -> r:^\s*PermitRootLogin\s+no'

Cú pháp rules (atomic checks):

Tiền tố Ý nghĩa Ví dụ
f: File tồn tại f:/etc/ssh/sshd_config
f:... -> r: File chứa regex f:/etc/ssh/sshd_config -> r:^PermitRootLogin no
c: Chạy command, so output c:sysctl net.ipv4.ip_forward -> r:= 0$
d: Thư mục tồn tại d:/etc/cron.d
p: Process đang chạy p:auditd
r: Khóa registry (Windows) —

condition: all (mọi rule đúng), any (một rule đúng), none (không rule nào đúng).

8.12.3. Ví dụ — kết quả SCA trên dashboard / alert

{
  "data": {
    "sca": {
      "type": "check",
      "policy": "CIS Debian Linux 12 Benchmark",
      "check": {
        "id": "5001",
        "title": "Ensure SSH PermitRootLogin is disabled",
        "result": "failed",
        "remediation": "Set 'PermitRootLogin no' in /etc/ssh/sshd_config"
      }
    }
  }
}

Lưu ý: SCA là configuration drift detection — chạy định kỳ. Một host "passed 90%" vẫn có thể có 10% fail là lỗ hổng nghiêm trọng; đọc theo từng check, không chỉ điểm tổng.


8.13. Tích hợp MITRE ATT&CK

8.13.1. MITRE ATT&CK là gì

MITRE ATT&CK là ma trận chuẩn hóa kỹ thuật của attacker theo Tactic (mục tiêu) → Technique (cách làm). Wazuh gắn mã technique (T####, sub-technique T####.###) vào rule, cho phép dashboard hiển thị tấn công theo ma trận và phục vụ threat hunting. Cách lập bản đồ kỹ thuật và đọc ma trận ATT&CK được trình bày ở Chương 15.

Khái niệm Ví dụ
Tactic Credential Access (TA0006)
Technique T1110 Brute Force
Sub-technique T1110.001 Password Guessing

8.13.2. Ví dụ — gắn MITRE vào rule (đã thấy ở 8.8.5)

<rule id="100110" level="10" frequency="8" timeframe="120">
  <if_matched_sid>100100</if_matched_sid>
  <same_source_ip />
  <description>Paywall brute-force</description>
  <mitre>
    <id>T1110</id>
  </mitre>
</rule>

Trên dashboard, alert này xuất hiện trong module MITRE ATT&CK, nhóm theo Tactic Credential Access, cho phép trả lời "trong tuần qua có bao nhiêu sự kiện thuộc Credential Access và trên host nào".

Lưu ý: Gắn MITRE đúng technique là phần của detection engineering — gắn sai làm sai lệch coverage report (tưởng đã phủ technique X nhưng thực ra rule bắt việc khác).


8.14. Detection Engineering — viết, tinh chỉnh rule, FP vs FN

8.14.1. FP và FN

Khái niệm Định nghĩa Hậu quả
False Positive (FP) Alert nổ nhưng không phải tấn công Mệt mỏi analyst (alert fatigue), bỏ sót alert thật
False Negative (FN) Tấn công thật nhưng không có alert Lọt lưới — nguy hiểm nhất
True Positive (TP) Alert đúng Lý tưởng
True Negative (TN) Không alert, đúng là an toàn Lý tưởng

Đánh đổi cốt lõi: Hạ ngưỡng (frequency thấp, level cao cho event đơn lẻ) → giảm FN nhưng tăng FP. Nâng ngưỡng → giảm FP nhưng tăng FN. Detection engineering là tìm điểm cân bằng theo ngữ cảnh tài sản.

Chỉ số định lượng:

Precision = TP / (TP + FP)     (alert nổ thì đáng tin tới mức nào)
Recall    = TP / (TP + FN)     (bắt được bao nhiêu phần tấn công thật)

8.14.2. Quy trình tuning (vòng lặp)

1. Viết rule giả thuyết (dựa decoder + field).
2. Test offline với wazuh-logtest trên log mẫu (cả mẫu tấn công lẫn mẫu bình thường).
3. Triển khai ở level THẤP (vd level 3) — quan sát volume, không hành động.
4. Đo FP: tỷ lệ alert là benign. Nếu cao → thêm điều kiện (field/srcip allowlist) hoặc tăng frequency.
5. Đo FN: replay mẫu tấn công đã biết — rule có nổ không?
6. Khi precision đạt, nâng level + (tùy) gắn active-response.
7. Lặp lại định kỳ (môi trường thay đổi → rule lỗi thời).

8.14.3. Ví dụ — tinh chỉnh rule brute-force để giảm FP

Vấn đề: rule 100110 nổ khi proxy/NAT làm nhiều user thật cùng srcip fail. Tinh chỉnh:

[DEMO] Cặp rule tinh chỉnh dưới đây minh hoạ kỹ thuật giảm FP (đổi khoá nhóm, loại trừ dải NAT); dải IP và ngưỡng phải thay theo môi trường thực tế.

<!-- v2: chỉ tính brute-force khi cùng IP NHƯNG khác user (dấu hiệu dò user),
         và loại trừ dải NAT nội bộ tin cậy -->
<rule id="100111" level="12" frequency="10" timeframe="120">
  <if_matched_sid>100100</if_matched_sid>
  <same_source_ip />
  <different_source_user />      <!-- nhiều user khác nhau từ 1 IP = dò tài khoản -->
  <description>Paywall: dò nhiều tài khoản từ $(srcip) (credential stuffing nghi vấn)</description>
  <mitre><id>T1110.004</id></mitre>  <!-- Credential Stuffing -->
</rule>

<!-- Triệt FP: bỏ qua dải office NAT -->
<rule id="100112" level="0">
  <if_sid>100110</if_sid>
  <field name="srcip">^10.20.0.</field>
  <description>Bỏ qua brute-force giả từ NAT văn phòng</description>
</rule>

8.14.4. Nguyên tắc viết rule tốt

Nguyên tắc Vì sao
Neo regex (^, $), khớp field thay vì substring khi có thể Tránh khớp nhầm, nhanh hơn
Đặt id trong 100000–120000 Không đụng rule gốc, không bị mất khi update
Bắt đầu ở level thấp, nâng dần Tránh active-response gây hại do FP
Tài liệu hóa <description> rõ ràng kèm field nội suy Analyst hiểu ngay khi triage
Gắn <mitre> đúng Đo coverage
Có cả test case dương tính và âm tính Đảm bảo không FN và không FP

8.14.5. Kinh nghiệm tuning từ vận hành thật

Vài điều mình rút ra sau một thời gian trực hệ thống giám sát thật (không phải lab):

  • Whitelist dải IP quản trị nội bộ trước tiên. Phần lớn alert authentication/web lặp đi lặp lại hóa ra đến từ chính đội vận hành: SSH qua VPN, healthcheck, job CI. Một rule level 0 có điều kiện srcip cho dải quản trị (đúng kiểu 8.8.6) cắt được lượng nhiễu lớn nhất với ít công nhất. Lưu ý: chỉ whitelist dải quản trị/hạ tầng cho đúng nhóm rule gây nhiễu — đừng whitelist cả mạng văn phòng cho mọi loại rule, vì máy văn phòng dính malware là chuyện hoàn toàn có thể xảy ra.
  • Chuẩn hóa nội dung alert: phải tự trả lời "máy nào, chuyện gì, từ đâu". Alert mà người trực còn phải mở dashboard chỉ để biết nó xảy ra trên host nào là alert viết chưa xong. Nội suy field vào <description> ($(srcip), agent name trong kênh notify) — thời gian triage giảm rõ rệt.
  • Thêm alert cho triệu chứng tài nguyên, không chỉ log. Mình có thêm cảnh báo CPU cao bất thường kéo dài (từ metric node_exporter, hoặc log_format command chạy lệnh định kỳ) — cryptominer thường lộ bằng CPU trước khi lộ bằng dòng log nào đó. SIEM bắt sự kiện; hệ metric bắt triệu chứng; hai nguồn bổ khuyết cho nhau (giám sát metric xem Chương 9).

8.15. Ví dụ end-to-end: SSH brute-force từ log thô tới alert dashboard

Ghép toàn bộ chuỗi: log → predecode → decode → rule stateless → rule correlation → alert → (active response) → indexer → dashboard.

Bước 0 — Cấu hình agent đọc auth.log

ossec.conf (agent):

<localfile>
  <log_format>syslog</log_format>
  <location>/var/log/auth.log</location>
</localfile>

Bước 1 — Log thô sinh ra (3 dòng minh họa)

Jun 19 10:22:41 web01 sshd[2931]: Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2
Jun 19 10:22:43 web01 sshd[2933]: Failed password for invalid user admin from 203.0.113.5 port 51290 ssh2
... (lặp 8 lần trong ~30s)

Bước 2 — PreDecoding (analysisd)

timestamp:    Jun 19 10:22:41
hostname:     web01
program_name: sshd
log:          Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2

Bước 3 — Decoding (decoder sshd gốc)

srcuser: admin
srcip:   203.0.113.5
srcport: 51244

Bước 4 — Rule stateless gốc khớp

Wazuh ship sẵn rule cho việc này:

<!-- (rule gốc, minh họa) -->
<rule id="5710" level="5">
  <if_sid>5700</if_sid>
  <match>Failed password|authentication failure|invalid user</match>
  <description>sshd: Attempt to login using a non-existent user.</description>
  <group>authentication_failed,invalid_login,</group>
</rule>

→ Mỗi dòng fail tạo một alert level 5.

Bước 5 — Rule correlation gốc khớp (brute-force)

<!-- (rule gốc, minh họa) — nhiều lần authentication_failed cùng IP -->
<rule id="5712" level="10" frequency="8" timeframe="120">
  <if_matched_sid>5710</if_matched_sid>
  <same_source_ip />
  <description>sshd: brute force trying to get access to the system. Authentication failed.</description>
  <group>authentication_failures,</group>
  <mitre><id>T1110</id></mitre>
</rule>

→ Sau lần fail thứ 8 trong 120s từ 203.0.113.5, alert level 10 nổ.

Bước 6 — Alert JSON (ghi vào /var/ossec/logs/alerts/alerts.json)

Alert này là điểm khởi đầu của quy trình điều tra/phản ứng sự cố (xem Chương 10).

{
  "timestamp": "2026-06-19T10:23:11.044+0000",
  "rule": {
    "id": "5712",
    "level": 10,
    "description": "sshd: brute force trying to get access to the system.",
    "groups": ["syslog", "sshd", "authentication_failures"],
    "mitre": { "id": ["T1110"], "tactic": ["Credential Access"], "technique": ["Brute Force"] },
    "frequency": 8
  },
  "agent": { "id": "001", "name": "web01", "ip": "192.0.2.10" },
  "data": { "srcip": "203.0.113.5", "srcuser": "admin", "srcport": "51244" },
  "decoder": { "name": "sshd" },
  "location": "/var/log/auth.log",
  "full_log": "Jun 19 10:22:41 web01 sshd[2931]: Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2"
}

Bước 7 — Active Response (tùy chọn) chặn IP

Nếu cấu hình <active-response> với <rules_id>5712</rules_id>, manager ra lệnh agent web01 chạy firewall-drop:

iptables -I INPUT -s 203.0.113.5 -j DROP   # tự gỡ sau <timeout>

Bước 8 — Filebeat → Indexer → Dashboard

  • Filebeat (cấu hình /etc/filebeat/filebeat.yml với module wazuh) đọc alerts.json, đẩy vào index wazuh-alerts-4.x-2026.06.19 trên indexer (cổng 9200).
  • Dashboard truy vấn index, hiển thị alert level 10 trong Security Events; trong module MITRE ATT&CK nó thuộc Tactic Credential Access / Technique T1110.

Bước 9 — Kiểm thử toàn chuỗi offline

# Mô phỏng nhanh: dán dòng log vào logtest để xác nhận decoder + rule
/var/ossec/bin/wazuh-logtest
# input:
Jun 19 10:22:41 web01 sshd[2931]: Failed password for invalid user admin from 203.0.113.5 port 51244 ssh2

Output xác nhận Phase 1/2/3 và rule id 5710 → (sau đủ tần suất) 5712.

Sơ đồ tổng kết end-to-end:

auth.log line ──▶ logcollector(agent) ──1514──▶ remoted ──▶ analysisd
                                                              │
                       ┌──────────────────────────────────────┘
                       ▼
            PreDecode ▶ Decode(sshd: srcip/srcuser) ▶ Rule 5710(L5) ▶ [×8/120s, same_source_ip] ▶ Rule 5712(L10)
                                                                                                        │
                                          ┌─────────────────────────────────────────────┬─────────────┘
                                          ▼                                             ▼
                                  active-response: firewall-drop              alerts.json ▶ Filebeat ▶ Indexer(9200) ▶ Dashboard(443)
                                  iptables DROP 203.0.113.5

8.16. Điều tra một cảnh báo thực tế bằng Wazuh — quy trình tái dùng được

Các mục trên mô tả pipeline theo chiều xuôi (log đi vào thành alert). Mục này đi chiều ngược lại: từ một con số bất thường trên dashboard truy về bằng chứng gốc và ra kết luận. Kịch bản dưới đây là case dựng lại để minh hoạ — mọi IP, mốc thời gian và dòng log đều là dữ liệu mẫu (dùng dải tài liệu RFC 5737), nhưng hình dạng của nó đúng với một đợt quét path traversal điển hình mà bất kỳ máy web nào phơi Internet cũng gặp. Mục tiêu cuối là trả lời được ba câu: bị cái gì, thủng chưa, làm gì tiếp — kèm bằng chứng, không phải cảm giác.

8.16.1. Bước 1 — Phát hiện từ dashboard: một nguồn chiếm áp đảo

Điểm khởi đầu là panel Top source IPs (dựng trên Grafana đọc index alert của Wazuh; module Security Events của Wazuh dashboard cũng có view tương đương). Một IP — 198.51.100.77 — chiếm 536 cảnh báo trong khung nhìn, trong khi các nguồn kế tiếp chỉ 54, 51... Nhóm rule đi kèm: web, attack. Một nguồn lạ (không phải dải văn phòng/đối tác) tạo ~90% alert nhóm tấn công → điều tra ngay.

Bài học ngay ở bước này: dashboard không trả lời câu hỏi — nó chỉ chỉ chỗ đáng để hỏi. Toàn bộ kết luận phía sau đến từ query.

8.16.2. Bước 2 — Khoanh vùng bằng Dev Tools: ba query tổng hợp

Wazuh dashboard có Dev Tools (console gửi query thẳng vào indexer — cú pháp OpenSearch/Elasticsearch DSL) trên index wazuh-alerts-*. Ba aggregation trả lời ba câu hỏi khoanh vùng.

(a) Nó đập máy nào? — terms theo agent.name:

GET wazuh-alerts-*/_search
{"size":0,"query":{"term":{"data.srcip":"198.51.100.77"}},
 "aggs":{"by_agent":{"terms":{"field":"agent.name","size":10}}}}

Kết quả: toàn bộ 536 alert nằm trên một máy dev chạy web API (nginx làm reverse proxy, phơi Internet). Phạm vi gọn lại còn đúng một host.

(b) Nó sinh ra rule gì? — terms theo rule.id:

GET wazuh-alerts-*/_search
{"size":0,"query":{"term":{"data.srcip":"198.51.100.77"}},
 "aggs":{"rules":{"terms":{"field":"rule.id","size":15}}}}

Kết quả: 31516 (URL đáng ngờ — 230), 31104 (pattern web attack — 218), 31151/31153 (nhiều lỗi 400/404) — toàn bộ thuộc nhóm "dò và bị từ chối". Quan trọng không kém là rule không xuất hiện: không có 31106 — rule "web attack trả về 200" (tức tấn công mà server đáp thành công). Chi tiết vắng mặt này là mấu chốt của bước kết luận (8.16.5). Các ID trên thuộc ruleset web access-log mặc định của Wazuh — đối chiếu lại với phiên bản ruleset đang chạy (cần kiểm chứng).

(c) Nhịp độ ra sao? — date_histogram theo timestamp:

GET wazuh-alerts-*/_search
{"size":0,"query":{"term":{"data.srcip":"198.51.100.77"}},
 "aggs":{"timeline":{"date_histogram":{"field":"timestamp","fixed_interval":"5m"}}}}

Kết quả: mở màn bằng 1 request lẻ (thăm dò), ~35 phút sau leo đỉnh 58 request/5 phút (xấp xỉ 1 request mỗi 5 giây), giữ đều ~2 giờ rồi tắt hẳn. Nhịp đều tăm tắp như vậy không phải người bấm trình duyệt — là scanner tự động. Nhận định này về sau trở thành cơ sở kỹ thuật để đề xuất rate-limit ở reverse proxy.

8.16.3. Bước 3 — Đọc bằng chứng gốc: trường full_log

Aggregation cho hình dạng của sự việc; muốn bằng chứng phải đọc document đầy đủ:

GET wazuh-alerts-*/_search
{"size":1,"query":{"bool":{"filter":[
   {"term":{"data.srcip":"198.51.100.77"}},{"term":{"rule.id":"31516"}}]}},
 "_source":["timestamp","rule.description","full_log","data.url","GeoLocation"],
 "sort":[{"timestamp":"desc"}]}

Trường full_log giữ nguyên vẹn dòng access log mà agent đã đọc:

198.51.100.77 - - [12/Mar/2025:16:50:30 +0000] "GET /....//....//....//....//....//home/admin/.ssh/id_rsa?_=a1b2c3&v=x9y8 HTTP/1.1" 301 178 "https://www.bing.com/search?q=ab12cd" "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15"

Một chi tiết đáng lưu ý trong kịch bản này: SSH vào máy grep IP đó trong /var/log/nginx/access.log thì không còn dòng nào — sự kiện xảy ra hôm trước, log đã xoay (logrotate). Nhưng SIEM tập trung vẫn giữ full_log trong index. Đây chính là chỗ log tập trung chứng minh giá trị: bằng chứng không phụ thuộc log thô trên máy còn hay mất — và attacker có xóa được log trên host cũng không xóa được bản đã nằm trong index. (Muốn đối chiếu bản gốc trên máy vẫn được: sudo zgrep 198.51.100.77 /var/log/nginx/access.log.* trên các file đã nén.)

8.16.4. Bước 4 — Diễn giải dòng log: nó đang làm gì, nhắm cái gì

Đọc từng phần của dòng log (kết hợp thêm terms theo data.url để xem toàn bộ danh sách path bị dò):

GET wazuh-alerts-*/_search
{"size":0,"query":{"term":{"data.srcip":"198.51.100.77"}},
 "aggs":{"urls":{"terms":{"field":"data.url","size":25}}}}
Thành phần quan sát được Nhận định
/....//....//....//home/admin/.ssh/id_rsa Path traversal — cố thoát web root để đọc file hệ thống
....// và (request khác) %252e%252e Double-encoding — né các bộ lọc chỉ bắt ../ dạng thô
Nhắm .ssh/id_rsa, .ssh/id_ed25519, .env, .mysql_history Săn SSH key + secret ứng dụng + lịch sử DB — trúng phát nào ăn phát đó
Thử lần lượt root/admin/ubuntu/deploy/ec2-user/git/www-data Dò theo danh sách user phổ biến — hành vi wordlist tự động
Status toàn 301/400/404 Proxy/app từ chối hết, không thấy dấu hiệu rò rỉ
Referer https://www.bing.com/search?q=... (chuỗi ngẫu nhiên) Referer giả — ngụy trang thành traffic từ search engine
User-Agent xoay vòng (Safari/Mac, Chrome/Windows, Android...) Xoay UA — công cụ tự động né chặn theo UA
GeoIP: VPS tại một hosting nước ngoài Không phải người dùng thật của hệ thống

8.16.5. Bước 5 — Kết luận dựa trên rule có / không xuất hiện

Kết luận "chưa thủng" đứng trên hai chân bằng chứng — và cần nói rõ vì sao chưa thủng: các request đó bị chính ứng dụng phía sau từ chối, chứ không phải bị một lớp phòng vệ nào chặn lại từ trước. Hai chân bằng chứng:

  1. Toàn bộ rule đã nổ đều thuộc nhóm "dò và bị từ chối" (URL đáng ngờ, 400/404); nhóm rule "web attack trả về 200" — dấu hiệu tấn công thành công — hoàn toàn vắng mặt với IP này.
  2. Status code trong các full_log mẫu đều là 301/400/404, khớp với (1).

Hai lưu ý về cách lập luận:

  • Vắng alert không đồng nghĩa vắng sự cố — kết luận chỉ có giá trị trong phạm vi telemetry đang thu (nếu decoder/rule không phủ một dạng tấn công thì SIEM im lặng dù có chuyện). Vì vậy kết luận nên phát biểu kèm phạm vi: "trong dữ liệu alert hiện có, không thấy dấu hiệu thành công".
  • Chính nhờ soi theo tiêu chí "trả 200" mà khi mở rộng ra toàn fleet (bước 6), mình phát hiện một nhóm nhỏ alert "attack trả 200" ở nơi khác — được tách thành một cuộc điều tra riêng, ưu tiên cao. Một cuộc điều tra tốt thường đẻ ra việc mới có phạm vi rõ ràng, thay vì phình vô hạn.

8.16.6. Bước 6 — Mở rộng: từ một IP ra bức tranh fleet

Bỏ filter theo IP, giữ filter theo nhóm rule — hỏi ba câu ở tầm toàn hệ thống:

GET wazuh-alerts-*/_search
{"size":0,"query":{"terms":{"rule.groups":["attack"]}},
 "aggs":{"ips":{"terms":{"field":"data.srcip","size":20}}}}

Kết quả làm thay đổi hẳn cách nhìn: IP đang điều tra chỉ đứng thứ ba về số alert; phía trên còn nguồn hàng nghìn alert, và nhiều IP đi theo cụm subnet (203.0.113.x với năm địa chỉ khác nhau, 198.51.100.x, 192.0.2.x...). Tương tự, terms theo agent.name (lọc rule.groups: ["attack","web"]) cho thấy mọi máy web-facing đều bị quét, không riêng máy ban đầu; và terms theo rule.description lộ thêm các kiểu tấn công khác (dò PHP CGI-bin, POST-flood, UA nằm blacklist) cùng một lượng lớn lỗi 500 — scan làm app phát sinh lỗi nội bộ, tức đã ảnh hưởng độ ổn định dù chưa thủng.

Hệ quả cho phản ứng: chặn từng IP là "đập chuột chũi" — scanner đổi địa chỉ liên tục theo cả dải. Biện pháp đúng tầng là kiểm soát hành vi tại reverse proxy (rate-limit, chặn tên tệp nhạy cảm, default_server ngắt Host lạ) và thu hẹp bề mặt (môi trường dev không phơi công khai); phòng thủ tầng mạng/WAF xem Chương 11.

8.16.7. Tóm tắt phương pháp — checklist tái dùng

Bước Câu hỏi Công cụ / Query
1. Phát hiện Có nguồn/nhóm nào bất thường không? Dashboard (Top source IPs, Security Events)
2. Khoanh vùng Máy nào? Rule gì? Nhịp độ nào? terms theo agent.name, rule.id + date_histogram theo timestamp, filter data.srcip
3. Bằng chứng Thực sự nó gửi cái gì? Đọc document đầy đủ, trường full_log (+ data.url, GeoLocation)
4. Diễn giải Kỹ thuật gì, nhắm cái gì? Đọc từng phần dòng log; terms theo data.url
5. Kết luận Thủng chưa? Bằng chứng đâu? Rule "thành công" có/không xuất hiện + status code; phát biểu kèm phạm vi telemetry
6. Mở rộng Chỉ IP này? Chỉ máy này? Bỏ filter srcip, aggs theo data.srcip / agent.name / rule.description trên nhóm attack/web

8.17. SOAR — tự động hóa phía trên SIEM

8.17.1. SOAR là gì, giải bài toán gì

SOAR (Security Orchestration, Automation and Response) là nền tảng nhận alert từ SIEM và chạy các playbook (luồng xử lý định nghĩa trước): làm giàu (enrich) alert bằng nguồn dữ liệu ngoài, thông báo, mở case, và — khi quy trình đủ chín — thực thi phản ứng.

Bài toán gốc: một SIEM được tuning tốt vẫn sinh alert nhiều hơn sức người đọc. Quan sát của mình khi trực: phần lớn thời gian triage một alert web không nằm ở phán đoán, mà ở thao tác lặp — copy IP nguồn → mở 2–3 tab tra reputation → nhìn kết quả → quyết định bỏ qua hay đào tiếp. Chuỗi thao tác đó máy làm được toàn bộ, và SOAR sinh ra đúng để làm việc đó: tự động hóa phần lặp lại, để dành người cho phần phán đoán.

8.17.2. Chọn công cụ: Shuffle vs TheHive vs n8n

Công cụ Bản chất Điểm mạnh Cân nhắc
Shuffle SOAR mã nguồn mở, workflow kéo-thả, kho app connector thiên về security Sinh ra cho use case SOC: có sẵn app cho Wazuh, các nguồn threat intel phổ biến; nhận webhook dễ Cộng đồng nhỏ hơn n8n; UI/docs còn thô ở vài chỗ
TheHive (+ Cortex) Nền tảng case management + engine phân tích observable (Cortex analyzer) Quản lý vòng đời case, phân công, lưu evidence — đúng nghĩa quy trình SOC Nặng so với nhu cầu "chỉ cần enrich + notify" ở giai đoạn đầu
n8n Automation tổng quát (không riêng security) Connector rất nhiều, dev nào cũng dùng được Không có khái niệm alert/case/observable sẵn — phần security phải tự chế

Với giai đoạn đầu, Shuffle là lựa chọn hợp lý: bài toán trước mắt là enrich + notify (đúng sở trường của nó), còn case management kiểu TheHive để dành cho giai đoạn quy trình đã ổn định. n8n hợp khi đội đã dùng sẵn nó cho automation chung và chỉ cần thêm vài luồng security đơn giản.

8.17.3. Kiến trúc Wazuh → SOAR: lọc trước khi đẩy

Wazuh manager ──(integration/webhook, CHỈ alert level ≥ N)──▶ SOAR (Shuffle)
                                                                 │
                                          ┌──────────────────────┤
                                          ▼                      ▼
                                [playbook enrich IP]     [playbook khác...]
                                          │
                                          ▼
                                kênh chat vận hành (kèm link về alert gốc trong SIEM)

Điểm thiết kế quan trọng nhất nằm ngay cạnh Wazuh: lọc theo severity trước khi đẩy. Chỉ những alert từ một mức level nhất định (ví dụ ≥ 7) mới sang SOAR; đẩy tất cả thì SOAR chỉ là bản sao ồn ào của SIEM và quota API threat intel cạn trong một buổi. Wazuh hỗ trợ việc này bằng khối <integration> trong ossec.conf — gọi webhook khi alert thỏa điều kiện:

<integration>
  <name>custom-soar</name>                    <!-- script custom-* trong /var/ossec/integrations/ -->
  <hook_url>https://soar.example.internal/api/v1/hooks/webhook_xxx</hook_url>
  <level>7</level>                            <!-- chỉ đẩy alert level >= 7 -->
  <alert_format>json</alert_format>
</integration>

(Ngoài <level> còn lọc được theo <rule_id> / <group>; cú pháp đối chiếu theo phiên bản đang chạy — cần kiểm chứng.)

8.17.4. Playbook đầu tiên: enrich IP + notify

Luồng enrich mình chạy thực tế, theo thứ tự:

alert JSON từ Wazuh
   │
   ▼
1. Trích data.srcip
2. Lọc: IP có phải public không?
      ├─ private (RFC 1918) / IP hạ tầng của mình → dừng (tra threat intel IP private vừa vô nghĩa vừa tốn quota)
      └─ public → tiếp
3. Tra threat intel song song hai nguồn:
      ├─ nguồn kiểu AbuseIPDB  → abuse confidence score (0–100), số report, ISP, usage type
      └─ nguồn kiểu VirusTotal → bao nhiêu engine gắn cờ malicious
4. Ghép thành message chuẩn: MÁY NÀO, rule gì (id + description), IP, score, số engine gắn cờ
5. Notify kênh chat vận hành — luôn kèm link quay về alert gốc trong SIEM

Giá trị thấy ngay trong tuần đầu: phân biệt được scanner có tiền sử với truy cập hợp lệ bị ghi nhận nhầm. Cùng một alert brute-force, IP có confidence score 100% với hàng nghìn report là chuyện khác hẳn IP score 0 hóa ra là đối tác đổi dải mạng. Người trực nhìn message là quyết định được ngay, không phải mở ba tab tra tay — alert nhiễu giảm theo đúng nghĩa "giảm chi phí xử lý một alert", chứ không phải giấu bớt alert đi.

8.17.5. Nguyên tắc: SOAR là lớp bổ sung, không thay thế SIEM

  • Alert gốc và bằng chứng vẫn nằm trong SIEM. SOAR chỉ giữ bản sao phục vụ playbook. SOAR chết thì mất tiện nghi, không mất dữ liệu; mọi notify đều có đường dẫn quay về SIEM để điều tra sâu (quy trình ở 8.16).
  • Chưa vội tự động hóa phản ứng. Lộ trình mình theo: (1) enrich + notify — máy chỉ đọc, chưa làm; (2) case management (kiểu TheHive) khi số vụ việc cần theo dấu tăng; (3) phản ứng có human approval — playbook chuẩn bị sẵn hành động (chặn IP, cô lập host), người bấm duyệt; (4) cuối cùng mới là tự động hoàn toàn cho một nhóm hẹp tình huống rất chắc chắn — với tinh thần y như Active Response (8.10): allowlist hạ tầng + timeout tự gỡ.

8.18. Tổng kết vận hành & checklist bảo mật Wazuh

Hạng mục Khuyến nghị
Truyền dữ liệu TCP 1514 + crypto_method aes; enroll qua 1515 có password/CA
client.keys Quyền chặt, không lộ; tên agent ổn định, duy nhất
Rule/decoder tùy biến Chỉ trong local_*.xml, id 100000+, test bằng wazuh-logtest & analysisd -t trước restart
FIM Realtime đúng chỗ; <nodiff> cho đường chứa secret; canh inotify watches
Active Response Allowlist IP hạ tầng; ưu tiên local; timeout để tự gỡ; script quyền chặt
Vuln/SCA Đối chiếu version đúng feed; đọc theo từng CVE/check, không chỉ điểm tổng
Lưu trữ logall=no trừ khi forensic; chính sách retention rõ ràng (hot/warm/cold)
Tuning Vòng lặp đo FP/FN; nâng level dần; review định kỳ; whitelist dải quản trị đúng nhóm rule
SOAR Lọc theo level trước khi đẩy; alert gốc luôn ở SIEM; phản ứng tự động đi sau human approval
API 55000 Đổi credential mặc định, bật RBAC, giới hạn network

Hết Chương 8.


Ghi chú của mình

Khu vực ghi chú cá nhân: những điểm từng hiểu sai, phần còn đang tìm hiểu, hoặc kinh nghiệm rút ra khi thực hành — cập nhật dần.

  • Hồi mới học SIEM, mình nghĩ giá trị của nó nằm ở dashboard đẹp và alert realtime. Sau lần đầu điều tra một đợt quét thật (mục 8.16), mình mới thấy giá trị lớn nhất nằm ở chỗ khác: khả năng đặt câu hỏi lên dữ liệu đã tập trung. Dashboard chỉ chỉ chỗ bất thường; toàn bộ kết luận đến từ vài query aggregation trên wazuh-alerts-*.
  • Bài học đắt nhất về log tập trung: khi mình SSH vào máy để grep access.log thì log đã xoay, không còn dòng nào — nhưng full_log trong index vẫn giữ nguyên vẹn từng dòng bằng chứng. Nếu chỉ giám sát bằng cách "có chuyện thì SSH vào xem log", mình đã tay trắng. Log tập trung không phải để cho sang — nó là nơi duy nhất bằng chứng sống sót qua logrotate (và qua cả attacker xóa log trên host).
  • Mình từng mặc định phản ứng đúng với IP tấn công là chặn nó. Đến khi bỏ filter IP và aggs theo data.srcip toàn fleet, thấy hàng chục IP đi theo cả cụm subnet, mình mới hiểu vì sao chặn từng IP là "đập chuột chũi". Giờ mình xếp thứ tự khác: thu hẹp bề mặt (dev không phơi công khai) → kiểm soát hành vi ở proxy (rate-limit, chặn tên tệp nhạy cảm) → cuối cùng mới đến chặn IP như biện pháp tạm.
  • Điều bất ngờ khi nối SIEM với luồng enrich threat intel (8.17): giá trị đầu tiên không phải "bắt thêm được attacker" mà là phân biệt nhanh scanner có tiền sử với false positive — IP confidence score 100% với hàng nghìn report khác hẳn IP score 0 hóa ra là truy cập hợp lệ. Đỡ hẳn những cuộc tranh luận "IP này là của ai" trong kênh vận hành.
  • Một kết luận "chưa thủng" mà mình dám viết vào báo cáo là kết luận có hai chân bằng chứng: rule "tấn công trả 200" vắng mặt và status code trong full_log khớp. Mình cũng học được cách phát biểu kèm phạm vi ("trong telemetry hiện có...") — vắng alert không đồng nghĩa vắng sự cố.
  • Đang tìm hiểu tiếp: tự chặn IP theo ngưỡng bằng fail2ban kết hợp Wazuh (mới ở mức nghiên cứu, chưa dám bật vì sợ self-DoS — xem cảnh báo 8.10); đưa case management vào quy trình khi số vụ việc cần theo dấu tăng; và WAF (ModSecurity + OWASP CRS) làm lớp chặn traversal triệt để mà nginx thuần không làm được.