ht">+| `WAZUH_AUTO_SYNC_ENABLED` | `true` | Enable/disable background sync loop | 53
+| `WAZUH_AUTO_SYNC_INTERVAL_SECONDS` | `60` | How often to run sync (seconds) |
54
+| `WAZUH_AUTO_SYNC_QUERY` | `*` | OpenSearch query string filter |
55
+| `WAZUH_AUTO_SYNC_LIMIT` | `50` | Max alerts per sync cycle |
56
+| `WAZUH_AUTO_SYNC_MINUTES` | `120` | Lookback window per sync cycle (minutes) |
57
+
58
+### Shuffle
59
+
60
+| Variable | Default | Description |
61
+|---|---|---|
62
+| `SHUFFLE_BASE_URL` | `http://shuffle-backend:5001` | Shuffle API base URL |
63
+| `SHUFFLE_API_KEY` | _(empty)_ | Shuffle API key |
64
+| `SHUFFLE_USERNAME` | _(empty)_ | Shuffle username (if using basic auth) |
65
+| `SHUFFLE_PASSWORD` | _(empty)_ | Shuffle password |
66
+
67
+### PagerDuty
68
+
69
+| Variable | Default | Description |
70
+|---|---|---|
71
+| `PAGERDUTY_BASE_URL` | `http://pagerduty-stub` | PagerDuty API base URL (stub in MVP) |
72
+| `PAGERDUTY_API_KEY` | _(empty)_ | PagerDuty integration key |
73
+
74
+### IRIS
75
+
76
+| Variable | Default | Description |
77
+|---|---|---|
78
+| `IRIS_BASE_URL` | `https://iriswebapp_nginx:8443` | IRIS web app base URL |
79
+| `IRIS_API_KEY` | _(empty)_ | IRIS API key (from IRIS user settings) |
80
+| `IRIS_DEFAULT_CUSTOMER_ID` | `1` | Customer ID used when creating IRIS alerts/cases |
81
+| `IRIS_DEFAULT_SOC_ID` | _(empty)_ | Default SOC user ID for assignment |
82
+
83
+### Threat Intelligence (IOC Enrichment)
84
+
85
+| Variable | Default | Description |
86
+|---|---|---|
87
+| `VIRUSTOTAL_BASE_URL` | `https://www.virustotal.com/api/v3` | VirusTotal API base URL |
88
+| `VIRUSTOTAL_API_KEY` | _(empty)_ | VirusTotal API key |
89
+| `ABUSEIPDB_BASE_URL` | `https://api.abuseipdb.com/api/v2` | AbuseIPDB API base URL |
90
+| `ABUSEIPDB_API_KEY` | _(empty)_ | AbuseIPDB API key |
91
+
92
+### IOC CDB List Refresh
93
+
94
+Background loop that fetches public threat feeds, merges with locally confirmed IOCs, writes Wazuh CDB list files, and restarts the Wazuh manager to recompile them.
95
+
96
+**Feeds:** Feodo Tracker (C2 IPs), URLhaus (malware domains), ThreatFox (IPs/domains/hashes), MalwareBazaar (SHA256 hashes), plus high-confidence hits from the local `ioc_trace` table.
97
+
98
+**Shared volume:** `wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/` is bind-mounted into both containers:
99
+- Wazuh manager → `/var/ossec/etc/lists/malicious-ioc`
100
+- soc-integrator → `/ioc-lists`
101
+
102
+| Variable | Default | Description |
103
+|---|---|---|
104
+| `IOC_REFRESH_ENABLED` | `false` | Enable/disable background refresh loop |
105
+| `IOC_REFRESH_INTERVAL_SECONDS` | `14400` | Refresh interval (seconds). Default = 4 hours. Minimum enforced: 300s |
106
+| `IOC_REFRESH_CONFIDENCE_THRESHOLD` | `0.7` | Minimum confidence (0–1) for a local `ioc_trace` hit to be included in CDB lists |
107
+| `IOC_REFRESH_LOOKBACK_DAYS` | `30` | How many days back to query `ioc_trace` for confirmed local hits |
108
+| `WAZUH_LISTS_PATH` | `/ioc-lists` | Container path where CDB list files are written (must match bind-mount destination) |
109
+
110
+**Manual trigger:**
111
+```bash
112
+curl -X POST http://localhost:8088/wazuh/ioc-lists/refresh \
113
+  -H 'X-Internal-API-Key: dev-internal-key'
114
+```
115
+
116
+**Status:**
117
+```bash
118
+curl http://localhost:8088/wazuh/ioc-lists/status
119
+```
120
+
121
+### Log Loss Monitor
122
+
123
+Background loop that checks Wazuh alert ingestion rate and optionally creates an IRIS ticket when no logs arrive within the window.
124
+
125
+| Variable | Default | Description |
126
+|---|---|---|
127
+| `LOG_LOSS_MONITOR_ENABLED` | `false` | Enable/disable background monitor loop |
128
+| `LOG_LOSS_MONITOR_INTERVAL_SECONDS` | `60` | How often to check (seconds) |
129
+| `LOG_LOSS_MONITOR_WINDOW_MINUTES` | `5` | Sliding window to count alerts (minutes) |
130
+| `LOG_LOSS_MONITOR_CREATE_IRIS_TICKET` | `false` | Create IRIS alert when log loss detected |
131
+| `LOG_LOSS_MONITOR_TICKET_COOLDOWN_SECONDS` | `900` | Minimum time between consecutive IRIS tickets (seconds) |
132
+
133
+### GeoIP
134
+
135
+| Variable | Default | Description |
136
+|---|---|---|
137
+| `GEOIP_PROVIDER` | `ipwhois` | GeoIP backend (`ipwhois` uses free ipwhois.app API) |
138
+| `GEOIP_CACHE_TTL_SECONDS` | `21600` | How long to cache GeoIP results (seconds). Default = 6 hours |
139
+
140
+### Correlation / C-Detection
141
+
142
+| Variable | Default | Description |
143
+|---|---|---|
144
+| `C_DETECTION_ENABLED` | `true` | Enable/disable correlation engine |
145
+| `C_DETECTION_WINDOW_MINUTES` | `30` | Sliding time window for correlation checks (minutes) |
146
+| `C_DETECTION_CREATE_IRIS_TICKET` | `true` | Create IRIS alert on correlation hit |
147
+| `C_DETECTION_TICKET_COOLDOWN_SECONDS` | `900` | Minimum time between IRIS tickets for the same entity (seconds) |
148
+| `C1_MAX_TRAVEL_SPEED_KMPH` | `900` | C1 impossible travel: max plausible speed (km/h) |
149
+| `C2_OFFHOURS_START_UTC` | `20` | C2 off-hours login: day end hour (UTC, 0–23) |
150
+| `C2_OFFHOURS_END_UTC` | `6` | C2 off-hours login: day start hour (UTC, 0–23) |
151
+| `C3_HOST_SPREAD_THRESHOLD` | `5` | C3 lateral movement: unique destination hosts threshold |
152
+| `C3_SCAN_PORT_THRESHOLD` | `20` | C3 lateral movement: unique destination ports threshold |

+ 149 - 0
soc-integrator/app/adapters/ioc_feed.py

@@ -0,0 +1,149 @@
1
+"""
2
+ioc_feed.py — Public threat-intel feed fetcher.
3
+
4
+Sources:
5
+  Feodo Tracker  — botnet C2 IPs (Emotet, TrickBot, QakBot, …)
6
+  ThreatFox      — IPs, domains, and hashes from live malware C2 (Abuse.ch)
7
+  URLhaus        — malware distribution domains (Abuse.ch)
8
+  MalwareBazaar  — SHA256 malware hashes (Abuse.ch)
9
+"""
10
+from __future__ import annotations
11
+
12
+import logging
13
+from typing import Any
14
+
15
+import httpx
16
+
17
+logger = logging.getLogger(__name__)
18
+
19
+FEODO_IP_URL = "https://feodotracker.abuse.ch/downloads/ipblocklist.txt"
20
+THREATFOX_API = "https://threatfox-api.abuse.ch/api/v1/"
21
+URLHAUS_DOMAINS_URL = "https://urlhaus.abuse.ch/downloads/text_online/"
22
+BAZAAR_HASHES_URL = "https://bazaar.abuse.ch/export/txt/sha256/full/"
23
+
24
+_TIMEOUT = 30.0
25
+
26
+
27
+class IocFeedAdapter:
28
+
29
+    async def fetch_feodo_ips(self) -> dict[str, str]:
30
+        """Feodo Tracker IP blocklist — plain text, one IP per line, comments start with #."""
31
+        result: dict[str, str] = {}
32
+        try:
33
+            async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
34
+                r = await client.get(FEODO_IP_URL)
35
+                r.raise_for_status()
36
+            for line in r.text.splitlines():
37
+                line = line.strip()
38
+                if not line or line.startswith("#"):
39
+                    continue
40
+                # Format: "1.2.3.4" or "1.2.3.4 # comment"
41
+                ip = line.split()[0]
42
+                if _is_valid_ip(ip):
43
+                    result[ip] = "feodo_c2"
44
+            logger.info("feodo_tracker ips=%d", len(result))
45
+        except Exception as exc:
46
+            logger.warning("feodo_tracker fetch failed: %s", exc)
47
+        return result
48
+
49
+    async def fetch_threatfox(self, days: int = 3) -> tuple[dict[str, str], dict[str, str], dict[str, str]]:
50
+        """
51
+        ThreatFox — returns (ips, domains, hashes) dicts.
52
+        ioc_type values: "ip:port", "domain", "url", "md5_hash", "sha256_hash"
53
+        """
54
+        ips: dict[str, str] = {}
55
+        domains: dict[str, str] = {}
56
+        hashes: dict[str, str] = {}
57
+        try:
58
+            async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
59
+                r = await client.post(
60
+                    THREATFOX_API,
61
+                    json={"query": "get_iocs", "days": days},
62
+                    headers={"Content-Type": "application/json"},
63
+                )
64
+                r.raise_for_status()
65
+            payload: dict[str, Any] = r.json()
66
+            for entry in payload.get("data") or []:
67
+                ioc_type = str(entry.get("ioc_type") or "")
68
+                ioc_value = str(entry.get("ioc") or "").strip()
69
+                malware = str(entry.get("malware_printable") or "threatfox").replace(" ", "_")[:32]
70
+                tag = f"threatfox_{malware.lower()}"
71
+                if ioc_type == "ip:port":
72
+                    ip = ioc_value.split(":")[0]
73
+                    if _is_valid_ip(ip):
74
+                        ips[ip] = tag
75
+                elif ioc_type == "domain":
76
+                    if ioc_value:
77
+                        domains[ioc_value] = tag
78
+                elif ioc_type == "url":
79
+                    # extract hostname from URL
80
+                    host = _hostname_from_url(ioc_value)
81
+                    if host:
82
+                        domains[host] = tag
83
+                elif ioc_type in {"sha256_hash"}:
84
+                    if len(ioc_value) == 64:
85
+                        hashes[ioc_value] = tag
86
+            logger.info("threatfox ips=%d domains=%d hashes=%d", len(ips), len(domains), len(hashes))
87
+        except Exception as exc:
88
+            logger.warning("threatfox fetch failed: %s", exc)
89
+        return ips, domains, hashes
90
+
91
+    async def fetch_urlhaus_domains(self) -> dict[str, str]:
92
+        """URLhaus — online malware distribution domains (plain text list)."""
93
+        result: dict[str, str] = {}
94
+        try:
95
+            async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
96
+                r = await client.get(URLHAUS_DOMAINS_URL)
97
+                r.raise_for_status()
98
+            for line in r.text.splitlines():
99
+                line = line.strip()
100
+                if not line or line.startswith("#"):
101
+                    continue
102
+                # Lines are full URLs like "http://evil.com/path"
103
+                host = _hostname_from_url(line)
104
+                if host and not _is_valid_ip(host):
105
+                    result[host] = "urlhaus_malware"
106
+            logger.info("urlhaus domains=%d", len(result))
107
+        except Exception as exc:
108
+            logger.warning("urlhaus fetch failed: %s", exc)
109
+        return result
110
+
111
+    async def fetch_bazaar_hashes(self) -> dict[str, str]:
112
+        """MalwareBazaar SHA256 full export (plain text, one hash per line)."""
113
+        result: dict[str, str] = {}
114
+        try:
115
+            async with httpx.AsyncClient(timeout=_TIMEOUT) as client:
116
+                r = await client.get(BAZAAR_HASHES_URL)
117
+                r.raise_for_status()
118
+            for line in r.text.splitlines():
119
+                line = line.strip()
120
+                if not line or line.startswith("#"):
121
+                    continue
122
+                if len(line) == 64 and all(c in "0123456789abcdefABCDEF" for c in line):
123
+                    result[line.lower()] = "bazaar_malware"
124
+            logger.info("bazaar hashes=%d", len(result))
125
+        except Exception as exc:
126
+            logger.warning("bazaar fetch failed: %s", exc)
127
+        return result
128
+
129
+
130
+# ---------------------------------------------------------------------------
131
+# Helpers
132
+# ---------------------------------------------------------------------------
133
+
134
+def _is_valid_ip(value: str) -> bool:
135
+    parts = value.split(".")
136
+    if len(parts) != 4:
137
+        return False
138
+    try:
139
+        return all(0 <= int(p) <= 255 for p in parts)
140
+    except ValueError:
141
+        return False
142
+
143
+
144
+def _hostname_from_url(url: str) -> str:
145
+    """Extract hostname from a URL-like string, stripping scheme and path."""
146
+    url = url.strip()
147
+    if "://" in url:
148
+        url = url.split("://", 1)[1]
149
+    return url.split("/")[0].split(":")[0].lower()

+ 12 - 0
soc-integrator/app/adapters/wazuh.py

@@ -130,6 +130,18 @@ class WazuhAdapter:
130 130
             response.raise_for_status()
131 131
             return response.json()
132 132
 
133
+    async def restart_manager(self) -> dict[str, Any]:
134
+        """Restart wazuh-analysisd to recompile CDB lists and reload rules."""
135
+        async with httpx.AsyncClient(verify=False, timeout=60.0) as client:
136
+            token = await self._authenticate(client)
137
+            url = f"{self.base_url}/manager/restart"
138
+            response = await client.put(
139
+                url,
140
+                headers={"Authorization": f"Bearer {token}"},
141
+            )
142
+            response.raise_for_status()
143
+            return response.json() if response.content else {"status": "restarting"}
144
+
133 145
     async def count_alerts(
134 146
         self,
135 147
         query: str,

+ 6 - 0
soc-integrator/app/config.py

@@ -61,5 +61,11 @@ class Settings(BaseSettings):
61 61
     abuseipdb_base_url: str = "https://api.abuseipdb.com/api/v2"
62 62
     abuseipdb_api_key: str = ""
63 63
 
64
+    ioc_refresh_enabled: bool = False
65
+    ioc_refresh_interval_seconds: int = 14400   # 4 hours
66
+    ioc_refresh_confidence_threshold: float = 0.7
67
+    ioc_refresh_lookback_days: int = 30
68
+    wazuh_lists_path: str = "/ioc-lists"
69
+
64 70
 
65 71
 settings = Settings()

+ 119 - 0
soc-integrator/app/main.py

@@ -18,6 +18,7 @@ from fastapi.staticfiles import StaticFiles
18 18
 
19 19
 from app.adapters.abuseipdb import AbuseIpdbAdapter
20 20
 from app.adapters.geoip import GeoIpAdapter
21
+from app.adapters.ioc_feed import IocFeedAdapter
21 22
 from app.adapters.iris import IrisAdapter
22 23
 from app.adapters.pagerduty import PagerDutyAdapter
23 24
 from app.adapters.shuffle import ShuffleAdapter
@@ -45,6 +46,7 @@ from app.models import (
45 46
 from app.repositories.mvp_repo import MvpRepository
46 47
 from app.routes.mvp import build_mvp_router
47 48
 from app.security import require_internal_api_key
49
+from app.services.ioc_list_service import IocListService
48 50
 from app.services.mvp_service import MvpService
49 51
 from app.services.c_detection_service import CDetectionService
50 52
 
@@ -95,11 +97,21 @@ mvp_service = MvpService(
95 97
     shuffle_adapter=shuffle_adapter,
96 98
     iris_adapter=iris_adapter,
97 99
     pagerduty_adapter=pagerduty_adapter,
100
+    virustotal_adapter=virustotal_adapter,
101
+    abuseipdb_adapter=abuseipdb_adapter,
98 102
 )
99 103
 c_detection_service = CDetectionService(
100 104
     repo=repo,
101 105
     geoip_adapter=geoip_adapter,
102 106
 )
107
+ioc_list_service = IocListService(
108
+    repo=repo,
109
+    wazuh_adapter=wazuh_adapter,
110
+    feed_adapter=IocFeedAdapter(),
111
+    lists_path=settings.wazuh_lists_path,
112
+    confidence_threshold=settings.ioc_refresh_confidence_threshold,
113
+    lookback_days=settings.ioc_refresh_lookback_days,
114
+)
103 115
 app.include_router(build_mvp_router(mvp_service, require_internal_api_key))
104 116
 app.mount("/ui/assets", StaticFiles(directory=str(UI_ASSETS_DIR)), name="ui-assets")
105 117
 
@@ -299,6 +311,36 @@ async def _wazuh_auto_sync_loop() -> None:
299 311
         await asyncio.sleep(interval)
300 312
 
301 313
 
314
+async def _ioc_refresh_loop() -> None:
315
+    interval = max(300, int(settings.ioc_refresh_interval_seconds))
316
+    while True:
317
+        started_at = datetime.now(timezone.utc).isoformat()
318
+        try:
319
+            app.state.ioc_refresh_state["running"] = True
320
+            app.state.ioc_refresh_state["last_started_at"] = started_at
321
+            result = await ioc_list_service.refresh()
322
+            app.state.ioc_refresh_state["last_status"] = "ok"
323
+            app.state.ioc_refresh_state["last_result"] = result
324
+            app.state.ioc_refresh_state["last_finished_at"] = datetime.now(timezone.utc).isoformat()
325
+            lists = result.get("lists", {})
326
+            logger.info(
327
+                "ioc-refresh complete ips=%s domains=%s hashes=%s wazuh_restarted=%s errors=%s",
328
+                lists.get("malicious_ips", 0),
329
+                lists.get("malicious_domains", 0),
330
+                lists.get("malware_hashes", 0),
331
+                result.get("wazuh_restarted"),
332
+                len(result.get("errors", [])),
333
+            )
334
+        except Exception as exc:
335
+            app.state.ioc_refresh_state["last_status"] = "error"
336
+            app.state.ioc_refresh_state["last_error"] = str(exc)
337
+            app.state.ioc_refresh_state["last_finished_at"] = datetime.now(timezone.utc).isoformat()
338
+            logger.exception("ioc-refresh failed: %s", exc)
339
+        finally:
340
+            app.state.ioc_refresh_state["running"] = False
341
+        await asyncio.sleep(interval)
342
+
343
+
302 344
 def _c_match_to_incident_event(match: dict[str, object]) -> dict[str, object]:
303 345
     event = dict(match.get("event") or {})
304 346
     usecase_id = str(match.get("usecase_id") or "C-unknown")
@@ -374,8 +416,25 @@ async def startup() -> None:
374 416
     app.state.systems_monitor_state = {
375 417
         "last_ok_at": {},
376 418
     }
419
+    app.state.ioc_refresh_state = {
420
+        "running": False,
421
+        "last_status": None,
422
+        "last_started_at": None,
423
+        "last_finished_at": None,
424
+        "last_error": None,
425
+        "last_result": None,
426
+    }
377 427
     app.state.sim_runs = {}
378 428
     SIM_RUN_LOGS_DIR.mkdir(parents=True, exist_ok=True)
429
+    if settings.ioc_refresh_enabled:
430
+        app.state.ioc_refresh_task = asyncio.create_task(_ioc_refresh_loop())
431
+        logger.info(
432
+            "ioc-refresh enabled interval=%ss confidence=%.2f lookback_days=%s path=%s",
433
+            settings.ioc_refresh_interval_seconds,
434
+            settings.ioc_refresh_confidence_threshold,
435
+            settings.ioc_refresh_lookback_days,
436
+            settings.wazuh_lists_path,
437
+        )
379 438
     if settings.wazuh_auto_sync_enabled:
380 439
         app.state.wazuh_auto_sync_task = asyncio.create_task(_wazuh_auto_sync_loop())
381 440
         logger.info(
@@ -412,6 +471,13 @@ async def shutdown() -> None:
412 471
             await ll_task
413 472
         except asyncio.CancelledError:
414 473
             pass
474
+    ioc_task = getattr(app.state, "ioc_refresh_task", None)
475
+    if ioc_task:
476
+        ioc_task.cancel()
477
+        try:
478
+            await ioc_task
479
+        except asyncio.CancelledError:
480
+            pass
415 481
     sim_runs = getattr(app.state, "sim_runs", {})
416 482
     for run in sim_runs.values():
417 483
         process = run.get("process")
@@ -1644,6 +1710,59 @@ async def wazuh_auto_sync_status() -> ApiResponse:
1644 1710
     )
1645 1711
 
1646 1712
 
1713
+@app.post(
1714
+    "/wazuh/ioc-lists/refresh",
1715
+    response_model=ApiResponse,
1716
+    dependencies=[Depends(require_internal_api_key)],
1717
+    summary="Trigger IOC list refresh",
1718
+    description="Fetch public IOC feeds and local confirmed hits, write CDB list files, and restart Wazuh analysisd.",
1719
+)
1720
+async def ioc_lists_refresh() -> ApiResponse:
1721
+    state = getattr(app.state, "ioc_refresh_state", {})
1722
+    state["running"] = True
1723
+    state["last_started_at"] = datetime.now(timezone.utc).isoformat()
1724
+    try:
1725
+        result = await ioc_list_service.refresh()
1726
+        state["last_status"] = "ok"
1727
+        state["last_result"] = result
1728
+        state["last_error"] = None
1729
+        state["last_finished_at"] = datetime.now(timezone.utc).isoformat()
1730
+        return ApiResponse(data=result)
1731
+    except Exception as exc:
1732
+        state["last_status"] = "error"
1733
+        state["last_error"] = str(exc)
1734
+        state["last_finished_at"] = datetime.now(timezone.utc).isoformat()
1735
+        raise HTTPException(status_code=500, detail=str(exc)) from exc
1736
+    finally:
1737
+        state["running"] = False
1738
+
1739
+
1740
+@app.get(
1741
+    "/wazuh/ioc-lists/status",
1742
+    response_model=ApiResponse,
1743
+    summary="IOC list status",
1744
+    description="Return the last refresh result and current entry counts for each CDB list file.",
1745
+)
1746
+async def ioc_lists_status() -> ApiResponse:
1747
+    state = getattr(app.state, "ioc_refresh_state", {})
1748
+    task = getattr(app.state, "ioc_refresh_task", None)
1749
+    disk_status = ioc_list_service.list_status()
1750
+    return ApiResponse(
1751
+        data={
1752
+            "enabled": settings.ioc_refresh_enabled,
1753
+            "task_running": bool(task and not task.done()),
1754
+            "settings": {
1755
+                "interval_seconds": settings.ioc_refresh_interval_seconds,
1756
+                "confidence_threshold": settings.ioc_refresh_confidence_threshold,
1757
+                "lookback_days": settings.ioc_refresh_lookback_days,
1758
+                "lists_path": settings.wazuh_lists_path,
1759
+            },
1760
+            "disk": disk_status,
1761
+            "state": state,
1762
+        }
1763
+    )
1764
+
1765
+
1647 1766
 @app.get(
1648 1767
     "/wazuh/sync-policy",
1649 1768
     response_model=ApiResponse,

+ 21 - 0
soc-integrator/app/repositories/mvp_repo.py

@@ -280,6 +280,27 @@ class MvpRepository:
280 280
                 ),
281 281
             )
282 282
 
283
+    def get_confirmed_iocs(
284
+        self,
285
+        ioc_type: str,
286
+        confidence_threshold: float = 0.7,
287
+        lookback_days: int = 30,
288
+    ) -> list[str]:
289
+        """Return distinct IOC values of the given type that were matched with sufficient confidence."""
290
+        with get_conn() as conn, conn.cursor() as cur:
291
+            cur.execute(
292
+                """
293
+                SELECT DISTINCT ioc_value
294
+                FROM ioc_trace
295
+                WHERE ioc_type = %s
296
+                  AND matched = TRUE
297
+                  AND confidence >= %s
298
+                  AND created_at >= NOW() - INTERVAL '%s days'
299
+                """,
300
+                (ioc_type, confidence_threshold, lookback_days),
301
+            )
302
+            return [row["ioc_value"] for row in cur.fetchall()]
303
+
283 304
     def list_ioc_trace(self, limit: int = 50, offset: int = 0) -> list[dict[str, Any]]:
284 305
         with get_conn() as conn, conn.cursor() as cur:
285 306
             cur.execute(

+ 193 - 0
soc-integrator/app/services/ioc_list_service.py

@@ -0,0 +1,193 @@
1
+"""
2
+ioc_list_service.py — Build and push Wazuh CDB threat-intel lists.
3
+
4
+Sources merged per refresh cycle:
5
+  1. Feodo Tracker      — botnet C2 IPs
6
+  2. ThreatFox          — IPs, domains, hashes from live C2
7
+  3. URLhaus            — malware distribution domains
8
+  4. MalwareBazaar      — SHA256 hashes
9
+  5. ioc_trace (local)  — high-confidence VT/AbuseIPDB matches from this pipeline
10
+
11
+After writing the list files to disk (bind-mounted into the Wazuh container),
12
+the service calls PUT /manager/restart via the Wazuh API so analysisd
13
+recompiles the CDB binary files and reloads rules.
14
+"""
15
+from __future__ import annotations
16
+
17
+import logging
18
+from datetime import datetime, timezone
19
+from pathlib import Path
20
+from typing import Any
21
+
22
+from app.adapters.ioc_feed import IocFeedAdapter
23
+from app.adapters.wazuh import WazuhAdapter
24
+from app.repositories.mvp_repo import MvpRepository
25
+
26
+logger = logging.getLogger(__name__)
27
+
28
+_CDB_COMMENT = (
29
+    "# Auto-generated by soc-integrator ioc_list_service\n"
30
+    "# DO NOT EDIT MANUALLY — changes will be overwritten on next refresh\n"
31
+    "# Format: key:source_tag\n"
32
+)
33
+
34
+
35
+class IocListService:
36
+    def __init__(
37
+        self,
38
+        repo: MvpRepository,
39
+        wazuh_adapter: WazuhAdapter,
40
+        feed_adapter: IocFeedAdapter,
41
+        lists_path: str = "/ioc-lists",
42
+        confidence_threshold: float = 0.7,
43
+        lookback_days: int = 30,
44
+    ) -> None:
45
+        self.repo = repo
46
+        self.wazuh_adapter = wazuh_adapter
47
+        self.feed_adapter = feed_adapter
48
+        self.lists_path = Path(lists_path)
49
+        self.confidence_threshold = confidence_threshold
50
+        self.lookback_days = lookback_days
51
+
52
+    async def refresh(self) -> dict[str, Any]:
53
+        """
54
+        Full refresh cycle:
55
+          1. Fetch public feeds
56
+          2. Merge with local ioc_trace confirmed hits
57
+          3. Write CDB list files to disk
58
+          4. Restart Wazuh analysisd so lists are recompiled
59
+
60
+        Returns a summary dict with counts per list and timing.
61
+        """
62
+        started_at = datetime.now(timezone.utc).isoformat()
63
+        errors: list[str] = []
64
+
65
+        # ------------------------------------------------------------------
66
+        # 1. Fetch public feeds
67
+        # ------------------------------------------------------------------
68
+        feodo_ips = await self.feed_adapter.fetch_feodo_ips()
69
+        tf_ips, tf_domains, tf_hashes = await self.feed_adapter.fetch_threatfox(days=3)
70
+        uh_domains = await self.feed_adapter.fetch_urlhaus_domains()
71
+        bz_hashes = await self.feed_adapter.fetch_bazaar_hashes()
72
+
73
+        # ------------------------------------------------------------------
74
+        # 2. Merge with local confirmed hits from ioc_trace
75
+        # ------------------------------------------------------------------
76
+        local_ips = {
77
+            v: "local_vt_abuseipdb"
78
+            for v in self.repo.get_confirmed_iocs(
79
+                "ip", self.confidence_threshold, self.lookback_days
80
+            )
81
+        }
82
+        local_domains = {
83
+            v: "local_vt"
84
+            for v in self.repo.get_confirmed_iocs(
85
+                "domain", self.confidence_threshold, self.lookback_days
86
+            )
87
+        }
88
+        local_hashes = {
89
+            v: "local_vt"
90
+            for v in self.repo.get_confirmed_iocs(
91
+                "hash", self.confidence_threshold, self.lookback_days
92
+            )
93
+        }
94
+
95
+        # Public feeds take precedence (more authoritative tag), local fills gaps
96
+        all_ips: dict[str, str] = {**local_ips, **feodo_ips, **tf_ips}
97
+        all_domains: dict[str, str] = {**local_domains, **uh_domains, **tf_domains}
98
+        all_hashes: dict[str, str] = {**local_hashes, **bz_hashes, **tf_hashes}
99
+
100
+        # ------------------------------------------------------------------
101
+        # 3. Write CDB files
102
+        # ------------------------------------------------------------------
103
+        self.lists_path.mkdir(parents=True, exist_ok=True)
104
+
105
+        try:
106
+            _write_cdb(self.lists_path / "malicious-ip", all_ips)
107
+        except Exception as exc:
108
+            errors.append(f"write malicious-ip: {exc}")
109
+            logger.error("failed to write malicious-ip: %s", exc)
110
+
111
+        try:
112
+            _write_cdb(self.lists_path / "malicious-domains", all_domains)
113
+        except Exception as exc:
114
+            errors.append(f"write malicious-domains: {exc}")
115
+            logger.error("failed to write malicious-domains: %s", exc)
116
+
117
+        try:
118
+            _write_cdb(self.lists_path / "malware-hashes", all_hashes)
119
+        except Exception as exc:
120
+            errors.append(f"write malware-hashes: {exc}")
121
+            logger.error("failed to write malware-hashes: %s", exc)
122
+
123
+        # ------------------------------------------------------------------
124
+        # 4. Restart Wazuh analysisd to recompile CDB binaries
125
+        # ------------------------------------------------------------------
126
+        wazuh_restarted = False
127
+        wazuh_error: str | None = None
128
+        if not errors:
129
+            try:
130
+                await self.wazuh_adapter.restart_manager()
131
+                wazuh_restarted = True
132
+                logger.info("wazuh manager restart triggered — CDB lists reloaded")
133
+            except Exception as exc:
134
+                wazuh_error = str(exc)
135
+                errors.append(f"wazuh_restart: {exc}")
136
+                logger.error("wazuh restart failed: %s", exc)
137
+        else:
138
+            wazuh_error = "skipped due to write errors"
139
+
140
+        finished_at = datetime.now(timezone.utc).isoformat()
141
+        return {
142
+            "started_at": started_at,
143
+            "finished_at": finished_at,
144
+            "lists": {
145
+                "malicious_ips": len(all_ips),
146
+                "malicious_domains": len(all_domains),
147
+                "malware_hashes": len(all_hashes),
148
+            },
149
+            "sources": {
150
+                "feodo_ips": len(feodo_ips),
151
+                "threatfox_ips": len(tf_ips),
152
+                "threatfox_domains": len(tf_domains),
153
+                "threatfox_hashes": len(tf_hashes),
154
+                "urlhaus_domains": len(uh_domains),
155
+                "bazaar_hashes": len(bz_hashes),
156
+                "local_confirmed_ips": len(local_ips),
157
+                "local_confirmed_domains": len(local_domains),
158
+                "local_confirmed_hashes": len(local_hashes),
159
+            },
160
+            "wazuh_restarted": wazuh_restarted,
161
+            "wazuh_error": wazuh_error,
162
+            "errors": errors,
163
+        }
164
+
165
+    def list_status(self) -> dict[str, Any]:
166
+        """Return current entry counts for each CDB file (reads from disk)."""
167
+        out: dict[str, Any] = {}
168
+        for name in ("malicious-ip", "malicious-domains", "malware-hashes"):
169
+            path = self.lists_path / name
170
+            if path.exists():
171
+                lines = [
172
+                    l for l in path.read_text().splitlines()
173
+                    if l.strip() and not l.startswith("#")
174
+                ]
175
+                out[name] = {"entries": len(lines), "exists": True}
176
+            else:
177
+                out[name] = {"entries": 0, "exists": False}
178
+        return out
179
+
180
+
181
+# ---------------------------------------------------------------------------
182
+# Helpers
183
+# ---------------------------------------------------------------------------
184
+
185
+def _write_cdb(path: Path, entries: dict[str, str]) -> None:
186
+    """Write a Wazuh CDB list file. Format: key:tag per line."""
187
+    lines = [_CDB_COMMENT]
188
+    for key, tag in sorted(entries.items()):
189
+        # CDB keys must not contain colons (except ip:port which we already strip)
190
+        safe_key = str(key).replace(":", "_")
191
+        lines.append(f"{safe_key}:{tag}\n")
192
+    path.write_text("".join(lines), encoding="utf-8")
193
+    logger.info("wrote %s entries → %s", len(entries), path)

+ 237 - 88
soc-integrator/app/services/mvp_service.py

@@ -10,9 +10,11 @@ from typing import Any
10 10
 
11 11
 import httpx
12 12
 
13
+from app.adapters.abuseipdb import AbuseIpdbAdapter
13 14
 from app.adapters.iris import IrisAdapter
14 15
 from app.adapters.pagerduty import PagerDutyAdapter
15 16
 from app.adapters.shuffle import ShuffleAdapter
17
+from app.adapters.virustotal import VirusTotalAdapter
16 18
 from app.adapters.wazuh import WazuhAdapter
17 19
 from app.config import settings
18 20
 from app.repositories.mvp_repo import MvpRepository
@@ -36,6 +38,85 @@ _SEVERITY_ORDER: dict[str, int] = {
36 38
 }
37 39
 
38 40
 
41
+def _build_vt_ioc_result(
42
+    vt: dict[str, object],
43
+    ioc_type: str,
44
+    ioc_value: str,
45
+    malicious_threshold: int,
46
+    suspicious_threshold: int,
47
+) -> tuple[dict[str, object], bool, str, float]:
48
+    stats = (
49
+        (((vt.get("data") or {}).get("attributes") or {}).get("last_analysis_stats"))
50
+        if isinstance(vt, dict)
51
+        else None
52
+    ) or {}
53
+
54
+    malicious = int(stats.get("malicious", 0) or 0)
55
+    suspicious = int(stats.get("suspicious", 0) or 0)
56
+    harmless = int(stats.get("harmless", 0) or 0)
57
+    undetected = int(stats.get("undetected", 0) or 0)
58
+    total = malicious + suspicious + harmless + undetected
59
+    confidence = 0.0 if total == 0 else round(((malicious + (0.5 * suspicious)) / total), 4)
60
+
61
+    matched = (malicious >= malicious_threshold) or (suspicious >= suspicious_threshold)
62
+    severity = "low"
63
+    if malicious >= 5 or suspicious >= 10:
64
+        severity = "critical"
65
+    elif malicious >= 2 or suspicious >= 5:
66
+        severity = "high"
67
+    elif malicious >= 1 or suspicious >= 1:
68
+        severity = "medium"
69
+
70
+    reason = (
71
+        f"virustotal_stats malicious={malicious} suspicious={suspicious} "
72
+        f"thresholds(malicious>={malicious_threshold}, suspicious>={suspicious_threshold})"
73
+    )
74
+
75
+    result: dict[str, object] = {
76
+        "ioc_type": ioc_type,
77
+        "ioc_value": ioc_value,
78
+        "matched": matched,
79
+        "severity": severity,
80
+        "confidence": confidence,
81
+        "reason": reason,
82
+        "providers": {"virustotal": {"stats": stats}},
83
+        "raw": {"virustotal": vt},
84
+    }
85
+    return result, matched, severity, confidence
86
+
87
+
88
+def _build_abuseipdb_ioc_result(
89
+    abuse: dict[str, object],
90
+    ioc_value: str,
91
+    confidence_threshold: int = 50,
92
+) -> tuple[dict[str, object], bool, str, float]:
93
+    data = ((abuse.get("data") if isinstance(abuse, dict) else None) or {}) if isinstance(abuse, dict) else {}
94
+    score = int(data.get("abuseConfidenceScore", 0) or 0)
95
+    total_reports = int(data.get("totalReports", 0) or 0)
96
+    matched = score >= confidence_threshold
97
+
98
+    severity = "low"
99
+    if score >= 90:
100
+        severity = "critical"
101
+    elif score >= 70:
102
+        severity = "high"
103
+    elif score >= 30:
104
+        severity = "medium"
105
+
106
+    confidence = round(score / 100.0, 4)
107
+    reason = f"abuseipdb score={score} totalReports={total_reports} threshold>={confidence_threshold}"
108
+    result: dict[str, object] = {
109
+        "ioc_type": "ip",
110
+        "ioc_value": ioc_value,
111
+        "matched": matched,
112
+        "severity": severity,
113
+        "confidence": confidence,
114
+        "reason": reason,
115
+        "providers": {"abuseipdb": {"score": score, "totalReports": total_reports, "raw": abuse}},
116
+    }
117
+    return result, matched, severity, confidence
118
+
119
+
39 120
 class MvpService:
40 121
     def __init__(
41 122
         self,
@@ -44,12 +125,16 @@ class MvpService:
44 125
         shuffle_adapter: ShuffleAdapter,
45 126
         iris_adapter: IrisAdapter,
46 127
         pagerduty_adapter: PagerDutyAdapter,
128
+        virustotal_adapter: VirusTotalAdapter | None = None,
129
+        abuseipdb_adapter: AbuseIpdbAdapter | None = None,
47 130
     ) -> None:
48 131
         self.repo = repo
49 132
         self.wazuh_adapter = wazuh_adapter
50 133
         self.shuffle_adapter = shuffle_adapter
51 134
         self.iris_adapter = iris_adapter
52 135
         self.pagerduty_adapter = pagerduty_adapter
136
+        self.virustotal_adapter = virustotal_adapter
137
+        self.abuseipdb_adapter = abuseipdb_adapter
53 138
 
54 139
     def _is_off_hours(self, ts: datetime) -> bool:
55 140
         hour = ts.astimezone(timezone.utc).hour
@@ -300,46 +385,6 @@ class MvpService:
300 385
             return "medium"
301 386
         return "low"
302 387
 
303
-    def _extract_shuffle_verdict(self, shuffle_result: dict[str, Any] | None) -> dict[str, Any]:
304
-        if not isinstance(shuffle_result, dict):
305
-            return {
306
-                "matched": False,
307
-                "confidence": 0.0,
308
-                "severity": "low",
309
-                "evidence": "",
310
-                "iocs": [],
311
-                "reason": "no_shuffle_result",
312
-            }
313
-
314
-        flat = dict(shuffle_result)
315
-        nested = shuffle_result.get("result")
316
-        if isinstance(nested, dict):
317
-            merged = dict(nested)
318
-            merged.update(flat)
319
-            flat = merged
320
-
321
-        confidence = self._to_float(flat.get("confidence"), 0.0)
322
-        matched_raw = flat.get("matched")
323
-        if isinstance(matched_raw, bool):
324
-            matched = matched_raw
325
-            reason = "shuffle_explicit"
326
-        else:
327
-            matched = confidence >= 0.7
328
-            reason = "confidence_threshold_fallback"
329
-
330
-        severity_raw = str(flat.get("severity", "")).lower()
331
-        severity = severity_raw if severity_raw in {"low", "medium", "high", "critical"} else self._severity_from_confidence(confidence)
332
-
333
-        return {
334
-            "matched": matched,
335
-            "confidence": confidence,
336
-            "severity": severity,
337
-            "evidence": str(flat.get("evidence", "")),
338
-            "iocs": flat.get("iocs", []),
339
-            "reason": reason,
340
-            "raw": shuffle_result,
341
-        }
342
-
343 388
     async def ingest_incident(self, event: dict[str, Any]) -> dict[str, Any]:
344 389
         policy = self.repo.get_policy()
345 390
         incident_key = self._incident_key(event)
@@ -458,65 +503,107 @@ class MvpService:
458 503
         }
459 504
 
460 505
     async def evaluate_ioc(self, payload: dict[str, Any]) -> dict[str, Any]:
461
-        policy = self.repo.get_policy()
462
-        workflow_id = str(policy.get("shuffle", {}).get("ioc_workflow_id", "")).strip()
506
+        ioc_type = str(payload.get("ioc_type") or "ip").strip()
507
+        ioc_value = str(payload.get("ioc_value") or "").strip()
508
+        if not ioc_value or ioc_value == "unknown":
509
+            return {
510
+                "matched": False,
511
+                "confidence": 0.0,
512
+                "severity": "low",
513
+                "evidence": "no_ioc_value",
514
+                "iocs": [],
515
+                "decision_source": "skipped",
516
+                "result": {"action_taken": "rejected"},
517
+            }
463 518
 
464
-        shuffle_result: dict[str, Any] | None = None
519
+        verdicts: list[dict[str, Any]] = []
465 520
 
466
-        if workflow_id:
467
-            shuffle_result = await self.shuffle_adapter.trigger_workflow(workflow_id, payload)
468
-        verdict = self._extract_shuffle_verdict(shuffle_result)
469
-        matched = bool(verdict["matched"])
470
-        confidence = self._to_float(verdict["confidence"], 0.0)
521
+        if self.virustotal_adapter is not None:
522
+            try:
523
+                vt_raw = await self.virustotal_adapter.enrich_ioc(ioc_type, ioc_value)
524
+                vt_result, vt_matched, vt_severity, vt_conf = _build_vt_ioc_result(
525
+                    vt_raw, ioc_type, ioc_value,
526
+                    malicious_threshold=1, suspicious_threshold=3,
527
+                )
528
+                verdicts.append({
529
+                    "matched": vt_matched, "severity": vt_severity,
530
+                    "confidence": vt_conf, "result": vt_result, "provider": "virustotal",
531
+                })
532
+            except Exception as exc:
533
+                logger.warning("VT IOC eval failed for %s: %s", ioc_value, exc)
471 534
 
472
-        logger.info(
473
-            "ioc evaluation workflow_id=%s matched=%s confidence=%.2f",
474
-            workflow_id or "<none>",
475
-            matched,
476
-            confidence,
535
+        if ioc_type == "ip" and self.abuseipdb_adapter is not None:
536
+            try:
537
+                abuse_raw = await self.abuseipdb_adapter.check_ip(ioc_value)
538
+                abuse_result, abuse_matched, abuse_severity, abuse_conf = _build_abuseipdb_ioc_result(
539
+                    abuse_raw, ioc_value, confidence_threshold=50,
540
+                )
541
+                verdicts.append({
542
+                    "matched": abuse_matched, "severity": abuse_severity,
543
+                    "confidence": abuse_conf, "result": abuse_result, "provider": "abuseipdb",
544
+                })
545
+            except Exception as exc:
546
+                logger.warning("AbuseIPDB IOC eval failed for %s: %s", ioc_value, exc)
547
+
548
+        matched = any(v["matched"] for v in verdicts)
549
+        confidence = max((v["confidence"] for v in verdicts), default=0.0)
550
+        severity = (
551
+            max(
552
+                (v["severity"] for v in verdicts if v["matched"]),
553
+                key=lambda s: _SEVERITY_ORDER.get(s, 1),
554
+                default="low",
555
+            )
556
+            if matched
557
+            else "low"
477 558
         )
559
+        evidence = "; ".join(v["result"]["reason"] for v in verdicts if v["matched"])
560
+        providers_used = [v["provider"] for v in verdicts]
478 561
 
479
-        if matched:
480
-            src_event = payload.get("source_event", {})
481
-            event_id = src_event.get("event_id") or f"ioc-{int(time.time())}"
482
-            if not isinstance(event_id, str):
483
-                event_id = str(event_id)
562
+        logger.info(
563
+            "ioc evaluation ioc=%s type=%s matched=%s confidence=%.2f providers=%s",
564
+            ioc_value, ioc_type, matched, confidence, providers_used,
565
+        )
484 566
 
485
-            description = f"IOC evaluation result confidence={confidence:.2f}"
486
-            evidence = str(verdict.get("evidence", "")).strip()
487
-            if evidence:
488
-                description = f"{description} evidence={evidence[:180]}"
567
+        self.repo.add_ioc_trace(
568
+            action="evaluate",
569
+            ioc_type=ioc_type,
570
+            ioc_value=ioc_value,
571
+            providers=providers_used,
572
+            request_payload=payload,
573
+            response_payload={"verdicts": verdicts, "matched": matched},
574
+            matched=matched,
575
+            severity=severity if matched else None,
576
+            confidence=confidence,
577
+        )
489 578
 
579
+        ingest_result: dict[str, Any] = {"action_taken": "rejected"}
580
+        if matched:
581
+            src_event = payload.get("source_event", {}) or {}
582
+            event_id = str(src_event.get("event_id") or f"ioc-{int(time.time())}")
490 583
             event = {
491
-                "source": "shuffle",
492
-                "event_type": "ioc_dns" if payload.get("ioc_type") == "domain" else "ioc_ips",
584
+                "source": providers_used[0] if providers_used else "ioc",
585
+                "event_type": "ioc_dns" if ioc_type == "domain" else "ioc_ips",
493 586
                 "event_id": event_id,
494 587
                 "timestamp": datetime.now(timezone.utc).isoformat(),
495
-                "severity": verdict["severity"],
496
-                "title": f"IOC match: {payload.get('ioc_value', 'unknown')}",
497
-                "description": description,
588
+                "severity": severity,
589
+                "title": f"IOC match: {ioc_value}",
590
+                "description": f"IOC evaluation confidence={confidence:.2f} evidence={evidence[:180]}",
498 591
                 "asset": src_event.get("asset", {}),
499 592
                 "network": src_event.get("network", {}),
500
-                "tags": ["ioc", str(payload.get("ioc_type", "unknown"))],
593
+                "tags": ["ioc", ioc_type],
501 594
                 "risk_context": {},
502
-                "raw": {
503
-                    "payload": payload,
504
-                    "shuffle": verdict.get("raw"),
505
-                },
595
+                "raw": {"payload": payload, "verdicts": verdicts},
506 596
                 "payload": {},
507 597
             }
508
-            ingest_result = await self.ingest_incident(event)
509
-        else:
510
-            ingest_result = {"action_taken": "rejected"}
598
+            ingest_result = await self.ingest_wazuh_alert_to_iris(event)
511 599
 
512 600
         return {
513 601
             "matched": matched,
514 602
             "confidence": confidence,
515
-            "severity": verdict["severity"],
516
-            "evidence": verdict["evidence"],
517
-            "iocs": verdict["iocs"],
518
-            "decision_source": verdict["reason"],
519
-            "shuffle": shuffle_result,
603
+            "severity": severity,
604
+            "evidence": evidence,
605
+            "iocs": [v["result"] for v in verdicts if v["matched"]],
606
+            "decision_source": "direct_api",
520 607
             "result": ingest_result,
521 608
         }
522 609
 
@@ -569,6 +656,72 @@ class MvpService:
569 656
         severity_str = (event.get("severity") or "medium").lower()
570 657
         severity_id = _IRIS_SEVERITY_ID.get(severity_str, 3)
571 658
 
659
+        note_lines: list[str] = []
660
+
661
+        # Asset
662
+        asset = event.get("asset") or {}
663
+        if any(asset.get(k) for k in ("hostname", "user", "agent_id")):
664
+            note_lines.append("## Asset")
665
+            if asset.get("hostname"):
666
+                note_lines.append(f"  Hostname : {asset['hostname']}")
667
+            if asset.get("user"):
668
+                note_lines.append(f"  User     : {asset['user']}")
669
+            if asset.get("agent_id"):
670
+                note_lines.append(f"  Agent ID : {asset['agent_id']}")
671
+
672
+        # Network
673
+        network = event.get("network") or {}
674
+        net_fields = {k: network.get(k) for k in ("src_ip", "dst_ip", "dst_port", "dst_host", "proto") if network.get(k)}
675
+        if net_fields:
676
+            note_lines.append("\n## Network")
677
+            for k, v in net_fields.items():
678
+                note_lines.append(f"  {k:<12}: {v}")
679
+
680
+        # IOC verdicts
681
+        raw = event.get("raw") or {}
682
+        verdicts: list[dict[str, Any]] = raw.get("verdicts") or []
683
+        if verdicts:
684
+            note_lines.append("\n## IOC Verdicts")
685
+            ioc_payload = raw.get("payload") or {}
686
+            if ioc_payload.get("ioc_value"):
687
+                note_lines.append(f"  IOC      : {ioc_payload.get('ioc_type','?')} / {ioc_payload['ioc_value']}")
688
+            for v in verdicts:
689
+                provider = v.get("provider", "?")
690
+                matched = v.get("matched", False)
691
+                conf = v.get("confidence", 0.0)
692
+                sev = v.get("severity", "?")
693
+                result = v.get("result") or {}
694
+                note_lines.append(f"\n  [{provider.upper()}]")
695
+                note_lines.append(f"    Matched    : {'YES' if matched else 'no'}")
696
+                note_lines.append(f"    Confidence : {conf:.0%}")
697
+                note_lines.append(f"    Severity   : {sev}")
698
+                # Provider-specific detail
699
+                providers_detail = (result.get("providers") or {})
700
+                vt = providers_detail.get("virustotal", {})
701
+                if vt.get("stats"):
702
+                    s = vt["stats"]
703
+                    note_lines.append(
704
+                        f"    VT Stats   : malicious={s.get('malicious',0)} "
705
+                        f"suspicious={s.get('suspicious',0)} "
706
+                        f"harmless={s.get('harmless',0)} "
707
+                        f"undetected={s.get('undetected',0)}"
708
+                    )
709
+                ab = providers_detail.get("abuseipdb", {})
710
+                if ab:
711
+                    note_lines.append(
712
+                        f"    AbuseIPDB  : score={ab.get('score',0)} "
713
+                        f"reports={ab.get('totalReports',0)}"
714
+                    )
715
+                if result.get("reason"):
716
+                    note_lines.append(f"    Reason     : {result['reason']}")
717
+
718
+        # Tags
719
+        tags = event.get("tags") or []
720
+        if tags:
721
+            note_lines.append(f"\n## Tags\n  {', '.join(str(t) for t in tags)}")
722
+
723
+        alert_note = "\n".join(note_lines).strip()
724
+
572 725
         payload: dict[str, Any] = {
573 726
             "alert_title": event.get("title") or f"Wazuh alert {event_id}",
574 727
             "alert_description": event.get("description") or "",
@@ -578,11 +731,7 @@ class MvpService:
578 731
             "alert_source_ref": event_id,
579 732
             "alert_source_event_time": event.get("timestamp") or datetime.now(timezone.utc).isoformat(),
580 733
             "alert_customer_id": settings.iris_default_customer_id or 1,
581
-            "alert_note": json.dumps({
582
-                "asset": event.get("asset", {}),
583
-                "network": event.get("network", {}),
584
-                "tags": event.get("tags", []),
585
-            }),
734
+            "alert_note": alert_note,
586 735
         }
587 736
         result = await self.iris_adapter.create_alert(payload)
588 737
         iris_alert_id = (result.get("data") or {}).get("alert_id")
@@ -664,9 +813,9 @@ class MvpService:
664 813
                     if ioc_result.get("matched"):
665 814
                         ioc_matched += 1
666 815
                         ingested += 1
667
-                        incident_key = str((ioc_result.get("result", {}) or {}).get("incident_key", ""))
668
-                        if incident_key:
669
-                            created_incidents.append(incident_key)
816
+                        iris_alert_id = (ioc_result.get("result", {}) or {}).get("iris_alert_id")
817
+                        if iris_alert_id:
818
+                            created_incidents.append(str(iris_alert_id))
670 819
                     else:
671 820
                         ioc_rejected += 1
672 821
                 else:

+ 653 - 0
wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/malicious-domains

@@ -0,0 +1,653 @@
1
+# Auto-generated by soc-integrator ioc_list_service
2
+# DO NOT EDIT MANUALLY — changes will be overwritten on next refresh
3
+# Format: key:source_tag
4
+0022a601.pphost.net:urlhaus_malware
5
+1.off3.ru:urlhaus_malware
6
+111101111.ru:urlhaus_malware
7
+123.ywxww.net:urlhaus_malware
8
+132.red-81-42-249.staticip.rima-tde.net:urlhaus_malware
9
+14-0-204-188.static.pccw-hkt.com:urlhaus_malware
10
+1717.1000uc.com:urlhaus_malware
11
+176.190.153.160.host.secureserver.net:urlhaus_malware
12
+178.248.3.202.ll.sta.mana.pf:urlhaus_malware
13
+179.248.3.202.ll.sta.mana.pf:urlhaus_malware
14
+1827897262.v.123pan.cn:urlhaus_malware
15
+185-55-196-13.cprapid.com:urlhaus_malware
16
+2.off3.ru:urlhaus_malware
17
+2cfc0222.salamanderprocessing.pages.dev:urlhaus_malware
18
+60aaf9c6.salamanderprocessing.pages.dev:urlhaus_malware
19
+7070-ppxcx-a1-3gg5ufwp666ee644-1300076834.tcb.qcloud.la:urlhaus_malware
20
+94fae730-597f-4442-813c-86263972a8f0.usrfiles.com:urlhaus_malware
21
+99-118-215-24.lightspeed.irvnca.sbcglobal.net:urlhaus_malware
22
+99194034-96-20180108171507.webstarterz.com:urlhaus_malware
23
+a-gwo.pages.dev:urlhaus_malware
24
+aaronart.com:urlhaus_malware
25
+abissnet.net:urlhaus_malware
26
+acaviationsupplies.com:urlhaus_malware
27
+acc.jiangsujiaxue.com:urlhaus_malware
28
+access.skaparade.com:urlhaus_malware
29
+acms.saleseos.com:urlhaus_malware
30
+adclick.g.doubleclick.net:urlhaus_malware
31
+admin.byte.in.ua:urlhaus_malware
32
+admin.gestroom.it:urlhaus_malware
33
+agence-immobiliere-lyon.com:urlhaus_malware
34
+airlife.bget.ru:urlhaus_malware
35
+alarmline.com.br:urlhaus_malware
36
+alineeleuterio.com.br:urlhaus_malware
37
+allendostmen.com:urlhaus_malware
38
+allister.ee:urlhaus_malware
39
+allsydevs.com:urlhaus_malware
40
+alternativas.ru:urlhaus_malware
41
+anch0-bridge.withregw.in.net:urlhaus_malware
42
+api.baimless.com:urlhaus_malware
43
+api.ezilax.com:urlhaus_malware
44
+apn-87-251-249-41.static.gprs.plus.pl:urlhaus_malware
45
+app.appzcvb.com:urlhaus_malware
46
+apple-service93.ru:urlhaus_malware
47
+ardena.pro:urlhaus_malware
48
+areyouready.co.za:urlhaus_malware
49
+artacom.com.br:urlhaus_malware
50
+asl-company.ru:urlhaus_malware
51
+atasapka.com.tr:urlhaus_malware
52
+atkcgnew.evgeni7e.beget.tech:urlhaus_malware
53
+attach.mail.daum.net:urlhaus_malware
54
+audicontadores.com:urlhaus_malware
55
+augsburg-auto.com:urlhaus_malware
56
+avbackup.acionline.de:urlhaus_malware
57
+backupso.com:urlhaus_malware
58
+bafybeias4uzwo3l336d5ewygv2dd3oqbnlvrer5ndf5wyhjcwkm4igaafa.ipfs.w3s.link:urlhaus_malware
59
+bafybeibfoyi7ruuyoncarf4xr55qa3lthsjjjgrktk4ia4z3upesawb4ry.ipfs.w3s.link:urlhaus_malware
60
+bafybeibqcivjhwg2msil5g62did64uhtptlf7epidbrat4gexerzfv5mmq.ipfs.dweb.link:urlhaus_malware
61
+bafybeibwz6lzwo6u5gkhp3ydl4te3hl3plfkypox6mnejssqwfrpdsmqoy.ipfs.dweb.link:urlhaus_malware
62
+bafybeiccl6irsru52xsyiuy4pqlitflw4f57xovkfpk5w2wnhtmeaqpjuy.ipfs.dweb.link:urlhaus_malware
63
+bafybeidp7zdy2lu6yxvbgoev4b6xokuaa6jljr34vkflxzel2ya2gc3plm.ipfs.dweb.link:urlhaus_malware
64
+bafybeidv6v7pezugmfpzwl2k2ni56nhvlyv5vaibriswtsthae5loxskpi.ipfs.dweb.link:urlhaus_malware
65
+bafybeidvf6tytrspkd4wnvxzs23m3kjr6bfvgszbfwybmmcosl4rrhvuo4.ipfs.dweb.link:urlhaus_malware
66
+bafybeidvgy76m4r347tpqg6plr3ac2p7o5bpcluicawc25nuh7mowtkssy.ipfs.dweb.link:urlhaus_malware
67
+bafybeiedkdwsp77zcvi6477lovtfde7rwsjdz7654kdnrgmciqg5mfhwh4.ipfs.dweb.link:urlhaus_malware
68
+bafybeieq7tctzxkqidqpq4fjvtznbupqrpo2w4n4lfmzksehei4dinilii.ipfs.w3s.link:urlhaus_malware
69
+bafybeiffpkay6l7heq55epccneb563p5chjzclxnso3vkozyorphlz6ana.ipfs.w3s.link:urlhaus_malware
70
+bafybeig5e7vfagk6xs4b2kk6s2bgaqm4trr56whisnhzirxutlovqkcnli.ipfs.dweb.link:urlhaus_malware
71
+bafybeihamvbzrm2tsifa4s7xruhfnsgnkzgtk2jqwj6cwgmdxj4wqe5lm4.ipfs.dweb.link:urlhaus_malware
72
+bafybeihmvo5nbtacxb7bx6bzla7adpg7ldm2ud3fqbom6724ajlki42urq.ipfs.dweb.link:urlhaus_malware
73
+bakhtov.com.ua:urlhaus_malware
74
+bankgarantia.ru:urlhaus_malware
75
+bbb.xfrwu.cn:urlhaus_malware
76
+bblissmassage.com:urlhaus_malware
77
+bbos.minet.vn:urlhaus_malware
78
+best.obs.cn-sz1.ctyun.cn:urlhaus_malware
79
+best10cdn.blob.core.windows.net:urlhaus_malware
80
+bf-chromefdghd.oss-cn-hongkong.aliyuncs.com:urlhaus_malware
81
+biducaconfeitos.com.br:urlhaus_malware
82
+bitbucket.org:urlhaus_malware
83
+bj5y6-0f-9h4-9fgg4-1324992141.cos.ap-bangkok.myqcloud.com:urlhaus_malware
84
+blankeyeo.com:urlhaus_malware
85
+blessdayservices.org:urlhaus_malware
86
+blogs.sokun.jp:urlhaus_malware
87
+bmdcompany.com:urlhaus_malware
88
+bmh-global.myfirewall.org:urlhaus_malware
89
+bnet-api.playm8ru.win:urlhaus_malware
90
+bnet.playm8ru.win:urlhaus_malware
91
+bombayonline.in:urlhaus_malware
92
+book.rollingvideogames.com:urlhaus_malware
93
+bracell.latitude.net.br:urlhaus_malware
94
+bridgeroad.maverickpreviews.com:urlhaus_malware
95
+brightworks.cz:urlhaus_malware
96
+bruplong.oss-accelerate.aliyuncs.com:urlhaus_malware
97
+bvaco.com:urlhaus_malware
98
+c.fi3.me:urlhaus_malware
99
+c0e5b87c.solaraweb-alj.pages.dev:urlhaus_malware
100
+c3436037.salamanderprocessing.pages.dev:urlhaus_malware
101
+c9791c08-f1e4-4402-9510-d04c13c50ea3.selstorage.ru:urlhaus_malware
102
+cad.659t.cn:urlhaus_malware
103
+cambodiatouristservice.com:urlhaus_malware
104
+cat.xiaoshabi.nl:urlhaus_malware
105
+cd.textfiles.com:urlhaus_malware
106
+cdaonline.com.ar:urlhaus_malware
107
+cdn-10049480.file.myqcloud.com:urlhaus_malware
108
+cdn.file6.goodid.com:urlhaus_malware
109
+cdn.filestackcontent.com:urlhaus_malware
110
+cdn.gomlab.com:urlhaus_malware
111
+cdn.ly.9377.com:urlhaus_malware
112
+cdn.novoline.top:urlhaus_malware
113
+cdn.pixelbin.io:urlhaus_malware
114
+cdn.xiaoduoai.com:urlhaus_malware
115
+celebratingseniors.net:urlhaus_malware
116
+cfs10.blog.daum.net:urlhaus_malware
117
+cfs13.tistory.com:urlhaus_malware
118
+cfs5.tistory.com:urlhaus_malware
119
+cfs7.blog.daum.net:urlhaus_malware
120
+cfs9.blog.daum.net:urlhaus_malware
121
+cfu.twr.mybluehost.me:urlhaus_malware
122
+chat-server.maverickpreviews.com:urlhaus_malware
123
+checkfivem.com:urlhaus_malware
124
+checkinetverifk.com:urlhaus_malware
125
+chelpus.com:urlhaus_malware
126
+chinaapper.com:urlhaus_malware
127
+chiptune.com:urlhaus_malware
128
+chuguadventures.co.tz:urlhaus_malware
129
+chungminhtaichinhsaigon.net:urlhaus_malware
130
+cl3dev.chrysalisbuffer.in.net:urlhaus_malware
131
+class1004.dothome.co.kr:urlhaus_malware
132
+classroomseven.com:urlhaus_malware
133
+clipaid.app:urlhaus_malware
134
+clisi.digifors.de:urlhaus_malware
135
+cloudstay168.com:urlhaus_malware
136
+clubdetiroelpicarcho.com:urlhaus_malware
137
+cn.unionlever.com:urlhaus_malware
138
+co-emas.com:urlhaus_malware
139
+codeload.github.com:urlhaus_malware
140
+compimento.ba:urlhaus_malware
141
+contentmentfairnesspesky.com:urlhaus_malware
142
+continentalgroup.net.in:urlhaus_malware
143
+controliumbt.com:urlhaus_malware
144
+coolcams.duckdns.org:urlhaus_malware
145
+coralasargetia.ro:urlhaus_malware
146
+corporacioncrf.com:urlhaus_malware
147
+cpe90-146-57-238.liwest.at:urlhaus_malware
148
+creativevoltage.com:urlhaus_malware
149
+crimefreesoftware.com:urlhaus_malware
150
+crixup.com:urlhaus_malware
151
+crm.razatelefonia.pro:urlhaus_malware
152
+crystalpvp.ru:urlhaus_malware
153
+cs.go.kg:urlhaus_malware
154
+csg-app.com:urlhaus_malware
155
+ct3-24.ru:urlhaus_malware
156
+d.14yaa.com:urlhaus_malware
157
+d.kpzip.com:urlhaus_malware
158
+d.wanyouxi7.com:urlhaus_malware
159
+d2314eac.solaraweb-alj.pages.dev:urlhaus_malware
160
+d3cciiowg5l3jx.cloudfront.net:urlhaus_malware
161
+data.yhydl.com:urlhaus_malware
162
+dcrun.co.uk:urlhaus_malware
163
+def163.keenetic.pro:urlhaus_malware
164
+definitely-not.gay:urlhaus_malware
165
+delp-heizungsbau.de:urlhaus_malware
166
+derekludlow.com:urlhaus_malware
167
+dev1proc.elytrapointnode.in.net:urlhaus_malware
168
+dev2power.elytrapointnode.in.net:urlhaus_malware
169
+dev3local.elytrapointnode.in.net:urlhaus_malware
170
+dev4work.elytrapointnode.in.net:urlhaus_malware
171
+devilnet.xyz:urlhaus_malware
172
+dexios.co.za:urlhaus_malware
173
+dezcom.com:urlhaus_malware
174
+dhnconstrucciones.com.ar:urlhaus_malware
175
+di5pat-ring.prowinserv.in.net:urlhaus_malware
176
+dialkwik.in:urlhaus_malware
177
+disk.accord1key.cn:urlhaus_malware
178
+divvanews.com:urlhaus_malware
179
+dl.1003b.56a.com:urlhaus_malware
180
+dl.2345.com:urlhaus_malware
181
+dl.360safe.com:urlhaus_malware
182
+dl.aginjector.com:urlhaus_malware
183
+dl.armour-inc-down.net:urlhaus_malware
184
+dl.dzqzd.com:urlhaus_malware
185
+dl.ijinshan.com:urlhaus_malware
186
+dl.natgo.cn:urlhaus_malware
187
+dla.zhuayoukong.com:urlhaus_malware
188
+dlied6.bytes.tcdnos.com:urlhaus_malware
189
+dlied6.yz.tcdnos.com:urlhaus_malware
190
+do-dear.com:urlhaus_malware
191
+docs.google.com:urlhaus_malware
192
+donkeytourscroatia.com:urlhaus_malware
193
+down.54nb.com:urlhaus_malware
194
+down.ftp21.cc:urlhaus_malware
195
+down.fwqlt.com:urlhaus_malware
196
+down.mvip8.ru:urlhaus_malware
197
+down.pcclear.com:urlhaus_malware
198
+down.qqfarmer.com.cn:urlhaus_malware
199
+down10d.zol.com.cn:urlhaus_malware
200
+downali.game.uc.cn:urlhaus_malware
201
+download.caihong.com:urlhaus_malware
202
+download.doumaibiji.cn:urlhaus_malware
203
+download.haozip.com:urlhaus_malware
204
+download.kaobeitu.com:urlhaus_malware
205
+download.kbcard.com:urlhaus_malware
206
+download.pdf00.cn:urlhaus_malware
207
+download.pdf00.com:urlhaus_malware
208
+download.suxiazai.com:urlhaus_malware
209
+download2.huduntech.com:urlhaus_malware
210
+dowonline.ru:urlhaus_malware
211
+dreamwatchevent.com:urlhaus_malware
212
+drevos.ro:urlhaus_malware
213
+drive.google.com:urlhaus_malware
214
+dubapkg.cmcmcdn.com:urlhaus_malware
215
+dusttv.com:urlhaus_malware
216
+dweixin.cn:urlhaus_malware
217
+easybrand.vn:urlhaus_malware
218
+electri.billregulator.com:urlhaus_malware
219
+elisans.novayonetim.com:urlhaus_malware
220
+embedone.com:urlhaus_malware
221
+en.taichuan.com:urlhaus_malware
222
+energy63.ru:urlhaus_malware
223
+epanpano.com:urlhaus_malware
224
+estudio.ythan.com.br:urlhaus_malware
225
+euob.youstarsbuilding.com:urlhaus_malware
226
+evangroup.ru:urlhaus_malware
227
+eventourarte.cl:urlhaus_malware
228
+f24-zfcloud.zdn.vn:urlhaus_malware
229
+f3i5-0g49bgn-3h95-1324992141.cos.ap-jakarta.myqcloud.com:urlhaus_malware
230
+fakers.co.jp:urlhaus_malware
231
+farschid.de:urlhaus_malware
232
+fb6390d5.infinityindians.pages.dev:urlhaus_malware
233
+fenbushijujuefuwu.com:urlhaus_malware
234
+fertas.com.tr:urlhaus_malware
235
+file.blackint3.com:urlhaus_malware
236
+filerit.com:urlhaus_malware
237
+files.constantcontact.com:urlhaus_malware
238
+filezilla.cc:urlhaus_malware
239
+firebasestorage.googleapis.com:urlhaus_malware
240
+first-security-verden.de:urlhaus_malware
241
+fitforevercavan.ie:urlhaus_malware
242
+flyingmutts.com:urlhaus_malware
243
+fnvimoyvwkbxbmczlqus.supabase.co:urlhaus_malware
244
+forms.saurashtrauniversity.edu:urlhaus_malware
245
+forspeed.onlinedown.net:urlhaus_malware
246
+fromthetrenchesworldreport.com:urlhaus_malware
247
+frvrefrigeracao.com.br:urlhaus_malware
248
+frygzjyhtiunvhvnacif.supabase.co:urlhaus_malware
249
+ftp.ywxww.net:urlhaus_malware
250
+fz.tiansys.cn:urlhaus_malware
251
+gabyagozetim.com:urlhaus_malware
252
+galeri3.arkitera.com:urlhaus_malware
253
+galerisenimutiara.com:urlhaus_malware
254
+gateway.lighthouse.storage:urlhaus_malware
255
+gharnt.com:urlhaus_malware
256
+gist.githubusercontent.com:urlhaus_malware
257
+gitee.com:urlhaus_malware
258
+github.com:urlhaus_malware
259
+github.guru:urlhaus_malware
260
+gitlab.com:urlhaus_malware
261
+globaltechbilling.com:urlhaus_malware
262
+googmeetinginvitation.com:urlhaus_malware
263
+goto.stnts.com:urlhaus_malware
264
+gutando.com:urlhaus_malware
265
+haeum.nfile.net:urlhaus_malware
266
+hasalltalent.com:urlhaus_malware
267
+hcsnet.com.br:urlhaus_malware
268
+heavyvaultpanel.top:urlhaus_malware
269
+hhbs.hhu.edu.cn:urlhaus_malware
270
+hitman-pro.ru:urlhaus_malware
271
+hitstation.nl:urlhaus_malware
272
+hnjgdl.geps.glodon.com:urlhaus_malware
273
+hobobot.net:urlhaus_malware
274
+horion-static.pages.dev:urlhaus_malware
275
+host-195-103-203-106.business.telecomitalia.it:urlhaus_malware
276
+host-95-230-215-65.business.telecomitalia.it:urlhaus_malware
277
+host-95-255-114-11.business.telecomitalia.it:urlhaus_malware
278
+hotelembuguacu.blob.core.windows.net:urlhaus_malware
279
+hotellacastellana.com.uy:urlhaus_malware
280
+hotelsep.blogspot.com:urlhaus_malware
281
+hqweb.id.vn:urlhaus_malware
282
+hseda.com:urlhaus_malware
283
+hzxcaq-github-io.pages.dev:urlhaus_malware
284
+i.404.pm:urlhaus_malware
285
+ia802801.us.archive.org:urlhaus_malware
286
+ibnbatutta.pk:urlhaus_malware
287
+icoffeecloud.com:urlhaus_malware
288
+id3702579photo-image-docs.com:urlhaus_malware
289
+id3basketball.com:urlhaus_malware
290
+igra123.com:urlhaus_malware
291
+igw.myfirewall.org:urlhaus_malware
292
+ihmmkvkaiwnilneauhfn.supabase.co:urlhaus_malware
293
+imagefiles-backup.oss-ap-southeast-7.aliyuncs.com:urlhaus_malware
294
+img.ipxxxx.com:urlhaus_malware
295
+img1.wsimg.com:urlhaus_malware
296
+injectroblox.ru:urlhaus_malware
297
+inmbau.com:urlhaus_malware
298
+innlive.in:urlhaus_malware
299
+inomailerhe.net:urlhaus_malware
300
+ircftp.net:urlhaus_malware
301
+isiore.com.co:urlhaus_malware
302
+itssprout.com:urlhaus_malware
303
+izocab.com:urlhaus_malware
304
+izogard.com:urlhaus_malware
305
+j-0-09g-9bh-h-ggf-1324992141.cos.ap-bangkok.myqcloud.com:urlhaus_malware
306
+jawaratekno.com:urlhaus_malware
307
+jlwz.cn:urlhaus_malware
308
+joyeriatauro.com:urlhaus_malware
309
+jyothishmathi.in:urlhaus_malware
310
+kamin-premium.ru:urlhaus_malware
311
+kavacanada.ca:urlhaus_malware
312
+khoancatbetong89.vn:urlhaus_malware
313
+kimyen.net:urlhaus_malware
314
+km.tradeforexcopier.com:urlhaus_malware
315
+konsor.ru:urlhaus_malware
316
+kotogadang-pusako.com:urlhaus_malware
317
+kramersmarionnettes.com:urlhaus_malware
318
+krisidev.com:urlhaus_malware
319
+kuakuawenjian.oss-cn-hangzhou.aliyuncs.com:urlhaus_malware
320
+kuzina-teatr.ru:urlhaus_malware
321
+la-pan-adventures.com:urlhaus_malware
322
+landonirwin.com:urlhaus_malware
323
+lcportal.kbinsure.co.kr:urlhaus_malware
324
+leindisncieamrocea-1341831283.cos.sa-saopaulo.myqcloud.com:urlhaus_malware
325
+libretv-16e.pages.dev:urlhaus_malware
326
+libss.0x504.com:urlhaus_malware
327
+lindenappliances.co.za:urlhaus_malware
328
+lindnerelektroanlagen.de:urlhaus_malware
329
+local-update.com:urlhaus_malware
330
+localtonet.com:urlhaus_malware
331
+lomejordesalamanca.es:urlhaus_malware
332
+lon-01.dlo4d.com:urlhaus_malware
333
+luizmatoso.com.br:urlhaus_malware
334
+m.jkoa.co.kr:urlhaus_malware
335
+m.meta-dm.com:urlhaus_malware
336
+maidforyou1985.com:urlhaus_malware
337
+makotoko.com:urlhaus_malware
338
+malupieng.com.br:urlhaus_malware
339
+mapshdi.wildscreeen.shop:urlhaus_malware
340
+marsalek.cy:urlhaus_malware
341
+matthewsigmondv5.pages.dev:urlhaus_malware
342
+media.githubusercontent.com:urlhaus_malware
343
+meetvideogoogle.com:urlhaus_malware
344
+mersped.mycpanel.rs:urlhaus_malware
345
+metrics.gocloudmaps.com:urlhaus_malware
346
+mgtms.cc:urlhaus_malware
347
+miner.pages.dev:urlhaus_malware
348
+mininews.kpzip.com:urlhaus_malware
349
+mistralkorea.ru:urlhaus_malware
350
+mitreart.com:urlhaus_malware
351
+mobshah.com:urlhaus_malware
352
+modulowinapp.com:urlhaus_malware
353
+mogimall.com:urlhaus_malware
354
+movseek.pages.dev:urlhaus_malware
355
+movtime76.shop:urlhaus_malware
356
+msoftdatastore.z22.web.core.windows.net:urlhaus_malware
357
+muledo.com:urlhaus_malware
358
+mundocarnes.cl:urlhaus_malware
359
+muzzumilruheel.com:urlhaus_malware
360
+n.vs108.com:urlhaus_malware
361
+ncxps.com:urlhaus_malware
362
+ndown2.ra2ol.com:urlhaus_malware
363
+nelees.com:urlhaus_malware
364
+neo-rnount.withregw.in.net:urlhaus_malware
365
+nerve.untergrund.net:urlhaus_malware
366
+nlcygd.withregw.in.net:urlhaus_malware
367
+noithaticon.vn:urlhaus_malware
368
+nusatoyota.co.id:urlhaus_malware
369
+ny.lshdw.cc:urlhaus_malware
370
+o24o.ru:urlhaus_malware
371
+ob.youstarsbuilding.com:urlhaus_malware
372
+od.lk:urlhaus_malware
373
+ofice365.github.io:urlhaus_malware
374
+ojang.pe.kr:urlhaus_malware
375
+okhan.net:urlhaus_malware
376
+oknoplastik.sk:urlhaus_malware
377
+old.bullydog.com:urlhaus_malware
378
+onedrive.live.com:urlhaus_malware
379
+onfiltre.com.tr:urlhaus_malware
380
+onyxarmorcrypt.de:urlhaus_malware
381
+onyxcyberedge.de:urlhaus_malware
382
+onyxfortifypro.de:urlhaus_malware
383
+onyxguardify.de:urlhaus_malware
384
+onyxguardwave.de:urlhaus_malware
385
+onyxironvault.de:urlhaus_malware
386
+onyxphantomlock.de:urlhaus_malware
387
+onyxprotectech.de:urlhaus_malware
388
+onyxsafecrypt.de:urlhaus_malware
389
+onyxsecuregate.de:urlhaus_malware
390
+onyxsentinelx.de:urlhaus_malware
391
+onyxstealthnet.de:urlhaus_malware
392
+opticsval.withregw.in.net:urlhaus_malware
393
+owlcity.ru:urlhaus_malware
394
+oys0ro.static.otenet.gr:urlhaus_malware
395
+p3.zbjimg.com:urlhaus_malware
396
+paccbet.pages.dev:urlhaus_malware
397
+palharesinformatica.com.br:urlhaus_malware
398
+pamellioty.com:urlhaus_malware
399
+paradox924x.pages.dev:urlhaus_malware
400
+pardu.pages.dev:urlhaus_malware
401
+pastebin.com:urlhaus_malware
402
+pay.aqiu6.com:urlhaus_malware
403
+pcupd.com:urlhaus_malware
404
+photo-id5631894.com:urlhaus_malware
405
+pid.fly160.com:urlhaus_malware
406
+pinaview.com:urlhaus_malware
407
+pizzatang.net:urlhaus_malware
408
+pjsn.hi2.ro:urlhaus_malware
409
+planetariumobil.ro:urlhaus_malware
410
+pns.org.pk:urlhaus_malware
411
+pobedastaff.ru:urlhaus_malware
412
+pole-rt-inger.com:urlhaus_malware
413
+polonyauniversiteleri.com.tr:urlhaus_malware
414
+pornily.ai:urlhaus_malware
415
+post-host.screenconnect.com:urlhaus_malware
416
+prepstarcenter.com:urlhaus_malware
417
+programandojuntos.us.tempcloudsite.com:urlhaus_malware
418
+proto-h4ul.withregw.in.net:urlhaus_malware
419
+pub-0478b308b8cf46709a73d0eed5afd633.r2.dev:urlhaus_malware
420
+pub-1445de8c8aa84761aac5200e0036237d.r2.dev:urlhaus_malware
421
+pub-bbbdebc2599c4d74b04c5d53e439f7a7.r2.dev:urlhaus_malware
422
+pub-bfc34934a91a4893817098f73415917a.r2.dev:urlhaus_malware
423
+pub-cdd0dd27ae6a4aee9841d397e0496374.r2.dev:urlhaus_malware
424
+pub-ce02802067934e0eb072f69bf6427bf6.r2.dev:urlhaus_malware
425
+public.demo.securecloudsandbox.com:urlhaus_malware
426
+pvsa.gxfugy.cn:urlhaus_malware
427
+qiniuyunxz.yxflzs.com:urlhaus_malware
428
+r34fa352.duckdns.org:urlhaus_malware
429
+ramirex.ro:urlhaus_malware
430
+raw.flameblox.com:urlhaus_malware
431
+raw.githubusercontent.com:urlhaus_malware
432
+rdm.91yunma.cn:urlhaus_malware
433
+reauthenticator.com:urlhaus_malware
434
+records.dennisign.se:urlhaus_malware
435
+redlk.com:urlhaus_malware
436
+refaccionesalma.com.mx:urlhaus_malware
437
+refrigeracion.delmondexpress.com:urlhaus_malware
438
+reifenquick.de:urlhaus_malware
439
+resourceedge.org:urlhaus_malware
440
+reusable-flex.com:urlhaus_malware
441
+rheddh.com:urlhaus_malware
442
+riderspin.com:urlhaus_malware
443
+robertrowe.com:urlhaus_malware
444
+roverlink.io:urlhaus_malware
445
+royalindiancurryclub.com:urlhaus_malware
446
+rrhh.intelsolut.com:urlhaus_malware
447
+rxquickpay.com:urlhaus_malware
448
+sabungkingbet189.com:urlhaus_malware
449
+safewatertech.com:urlhaus_malware
450
+sanghyun.nfile.net:urlhaus_malware
451
+save.jnrsmcu.com:urlhaus_malware
452
+sbstorage.club:urlhaus_malware
453
+scainseto.com.br:urlhaus_malware
454
+schenckel.com.br:urlhaus_malware
455
+sdvgpro.ru:urlhaus_malware
456
+sec.xiaoshabi.nl:urlhaus_malware
457
+separadordecc.com:urlhaus_malware
458
+serasoo.direct.quickconnect.to:urlhaus_malware
459
+server.toeicswt.co.kr:urlhaus_malware
460
+sg123.net:urlhaus_malware
461
+sgnfyn.oss-cn-shenzhen.aliyuncs.com:urlhaus_malware
462
+shaderm.com:urlhaus_malware
463
+shadowbot-dih.pages.dev:urlhaus_malware
464
+shahamanatme.com:urlhaus_malware
465
+shangmei-test.oss-cn-beijing.aliyuncs.com:urlhaus_malware
466
+shfug.org:urlhaus_malware
467
+shop.mediasova.ru:urlhaus_malware
468
+shqdown.ggzuhao.com:urlhaus_malware
469
+simbhaolisugars.in:urlhaus_malware
470
+sites.google.com:urlhaus_malware
471
+sk-comtel.com:urlhaus_malware
472
+skillnorequired.cc:urlhaus_malware
473
+smartermail.host:urlhaus_malware
474
+soft.110route.com:urlhaus_malware
475
+softbank126023203236.bbtec.net:urlhaus_malware
476
+softcatalog.ru:urlhaus_malware
477
+softdl.360tpcdn.com:urlhaus_malware
478
+softdl2.360tpcdn.com:urlhaus_malware
479
+softdl4.360.cn:urlhaus_malware
480
+solutelco.com:urlhaus_malware
481
+spacemanslot88.games:urlhaus_malware
482
+spoar.org.in:urlhaus_malware
483
+ssagntroplexa.com:urlhaus_malware
484
+ssl.ftp21.cc:urlhaus_malware
485
+sta.qinxue.com:urlhaus_malware
486
+static.3001.net:urlhaus_malware
487
+static.ilclock.com:urlhaus_malware
488
+static.topxgun.com:urlhaus_malware
489
+static.youdm.cn:urlhaus_malware
490
+static.zongheng.com:urlhaus_malware
491
+stdown.dinju.com:urlhaus_malware
492
+steam66.cn:urlhaus_malware
493
+stonecradle.com:urlhaus_malware
494
+storage.googleapis.com:urlhaus_malware
495
+studiogioeli.it:urlhaus_malware
496
+supercleanspb.ru:urlhaus_malware
497
+support.clz.kr:urlhaus_malware
498
+sutterpoint.com:urlhaus_malware
499
+svirtual.sanviatorperu.edu.pe:urlhaus_malware
500
+swiftfusion.tech:urlhaus_malware
501
+tajalrayhan.com:urlhaus_malware
502
+talentrecruitments.com:urlhaus_malware
503
+tanscarattorneys.co.tz:urlhaus_malware
504
+tapestryoftruth.com:urlhaus_malware
505
+teak.gen.tr:urlhaus_malware
506
+tecunonline.com:urlhaus_malware
507
+tehnomag.rs:urlhaus_malware
508
+temirtau-adm.ru:urlhaus_malware
509
+tengfeidn.cn:urlhaus_malware
510
+teslasuit.to:urlhaus_malware
511
+tesllamacapp.com:urlhaus_malware
512
+test.aionclassic.pro:urlhaus_malware
513
+test.peperoncinochepassione.it:urlhaus_malware
514
+tests.yjzj.org:urlhaus_malware
515
+thairefaruq.com:urlhaus_malware
516
+thebrandmantra.in:urlhaus_malware
517
+theholidayroads.com:urlhaus_malware
518
+theoremaoliveoil.com:urlhaus_malware
519
+thtp2.volamngayxua.net:urlhaus_malware
520
+tiwanlinm.duckdns.org:urlhaus_malware
521
+tobecation.github.io:urlhaus_malware
522
+toolshare.com.tr:urlhaus_malware
523
+top8onlinegame.com:urlhaus_malware
524
+transfer.weepee.io:urlhaus_malware
525
+transparenciacanaa.com.br:urlhaus_malware
526
+trtmyanmar.com:urlhaus_malware
527
+twitch.ist:urlhaus_malware
528
+tyahelp.top:urlhaus_malware
529
+ukguk71.ru:urlhaus_malware
530
+un1rw11q4u.com:urlhaus_malware
531
+uniform-factory.ae:urlhaus_malware
532
+union.macoms.la:urlhaus_malware
533
+unruffled-chaum.185-36-205-153.plesk.page:urlhaus_malware
534
+upaicdn.xinmei365.com:urlhaus_malware
535
+upchemicals.co.in:urlhaus_malware
536
+update.aegis.aliyun.com:urlhaus_malware
537
+update.bruss.org.ru:urlhaus_malware
538
+update.cg100iii.com:urlhaus_malware
539
+update.volam2005pk.com:urlhaus_malware
540
+users.atw.hu:urlhaus_malware
541
+vaamsmgfreocmroe-1342087530.cos.sa-saopaulo.myqcloud.com:urlhaus_malware
542
+vagler.ru:urlhaus_malware
543
+vcc-library.uk:urlhaus_malware
544
+versaclean.com.br:urlhaus_malware
545
+veryboys.com:urlhaus_malware
546
+vgd.vg:urlhaus_malware
547
+videomeetgoogle.com:urlhaus_malware
548
+vincentdemiero.com:urlhaus_malware
549
+visam.info:urlhaus_malware
550
+visualwikicloud.com:urlhaus_malware
551
+vizyonuniversitesi.com.tr:urlhaus_malware
552
+vizyonuniversitesi.web.tr:urlhaus_malware
553
+vrajras.com:urlhaus_malware
554
+web.archive.org:urlhaus_malware
555
+webcstore.pw:urlhaus_malware
556
+website.mypetapp.co.za:urlhaus_malware
557
+weco2.oss-me-east-1.aliyuncs.com:urlhaus_malware
558
+whrc.ru:urlhaus_malware
559
+widexenmexico.com.mx:urlhaus_malware
560
+win.down.55kantu.com:urlhaus_malware
561
+windomstatetheater.com:urlhaus_malware
562
+windriversfiles.imeitools.com:urlhaus_malware
563
+wire2spell.com:urlhaus_malware
564
+wittenhorst.eu:urlhaus_malware
565
+wpgbf1zg-5500.euw.devtunnels.ms:urlhaus_malware
566
+www.150.co.il:urlhaus_malware
567
+www.amyuni.com:urlhaus_malware
568
+www.ardguisser.com:urlhaus_malware
569
+www.astenterprises.com.pk:urlhaus_malware
570
+www.automobile-bk.de:urlhaus_malware
571
+www.backupallfresh2030.com:urlhaus_malware
572
+www.benshamcentre.co.uk:urlhaus_malware
573
+www.blackhattoolz.com:urlhaus_malware
574
+www.blackhost.xyz:urlhaus_malware
575
+www.bratusferramentas.grupomoltz.com.br:urlhaus_malware
576
+www.cc9.ne.jp:urlhaus_malware
577
+www.chenwangqiao.com:urlhaus_malware
578
+www.cippe.com.cn:urlhaus_malware
579
+www.coolcams.duckdns.org:urlhaus_malware
580
+www.crazywickedaddiction.com:urlhaus_malware
581
+www.cutting-edge.in:urlhaus_malware
582
+www.drgenov.com:urlhaus_malware
583
+www.dropbox.com:urlhaus_malware
584
+www.first-security-verden.de:urlhaus_malware
585
+www.flybirdexpbd.com:urlhaus_malware
586
+www.hcsnet.com.br:urlhaus_malware
587
+www.hostingcloud.science:urlhaus_malware
588
+www.hseda.com:urlhaus_malware
589
+www.hwgeneralins.com:urlhaus_malware
590
+www.intelligradeeducation.vicentecisnerospub.com:urlhaus_malware
591
+www.jozefinskiatelje.si:urlhaus_malware
592
+www.konsor.ru:urlhaus_malware
593
+www.kotojuki.com:urlhaus_malware
594
+www.longfeng188.com:urlhaus_malware
595
+www.medises.co.kr:urlhaus_malware
596
+www.mevetlab.cl:urlhaus_malware
597
+www.mixturro.com:urlhaus_malware
598
+www.mobimpex.ro:urlhaus_malware
599
+www.myvcart.com:urlhaus_malware
600
+www.namuvpn.com:urlhaus_malware
601
+www.newkey.co.kr:urlhaus_malware
602
+www.notbak.com:urlhaus_malware
603
+www.ojang.pe.kr:urlhaus_malware
604
+www.okhan.net:urlhaus_malware
605
+www.r-tt.com:urlhaus_malware
606
+www.r34fa352.duckdns.org:urlhaus_malware
607
+www.reifenquick.de:urlhaus_malware
608
+www.saf-oil.ru:urlhaus_malware
609
+www.salonmarketing.ca:urlhaus_malware
610
+www.sendspace.com:urlhaus_malware
611
+www.sgeseducation.com:urlhaus_malware
612
+www.silver-hubdachwohnwagen.de:urlhaus_malware
613
+www.simbhaolisugars.in:urlhaus_malware
614
+www.ss-01.com:urlhaus_malware
615
+www.starcountry.net:urlhaus_malware
616
+www.steamrub.com:urlhaus_malware
617
+www.support-data.com:urlhaus_malware
618
+www.tdejb.com:urlhaus_malware
619
+www.tecunonline.com:urlhaus_malware
620
+www.teknoarge.com:urlhaus_malware
621
+www.teslasuit.to:urlhaus_malware
622
+www.test.peperoncinochepassione.it:urlhaus_malware
623
+www.tmcksa.com:urlhaus_malware
624
+www.udobrit.ru:urlhaus_malware
625
+www.uralmetalloprokat.ru:urlhaus_malware
626
+www.vuelaviajero.com:urlhaus_malware
627
+www.website.mypetapp.co.za:urlhaus_malware
628
+www.websitedesigningindia.biz:urlhaus_malware
629
+www.xn--on3b15m2lco2u.com:urlhaus_malware
630
+www.zamilgroups.com:urlhaus_malware
631
+www.zhikey.com:urlhaus_malware
632
+www999999asgasg-1327129302.cos.ap-chengdu.myqcloud.com:urlhaus_malware
633
+www999999safagqwhg-1327129302.cos.ap-chengdu.myqcloud.com:urlhaus_malware
634
+xanax.enzostress.st:urlhaus_malware
635
+xdcq3.com:urlhaus_malware
636
+xenon.studio:urlhaus_malware
637
+xiaoma-10021647.file.myqcloud.com:urlhaus_malware
638
+xn--90abegbttpjb3bzb2j.xn--p1ai:urlhaus_malware
639
+xn--b1afiqif6c.xn--p1ai:urlhaus_malware
640
+xn--h6qpop2cq9nl9c.pages.dev:urlhaus_malware
641
+xn--yh4bx88a.com:urlhaus_malware
642
+xshop.com.tr:urlhaus_malware
643
+xuezha.net:urlhaus_malware
644
+xww.bucea.edu.cn:urlhaus_malware
645
+xyfsd.com:urlhaus_malware
646
+ybgctdtbzvgpdxjivafy.supabase.co:urlhaus_malware
647
+yongtai.cn:urlhaus_malware
648
+youtransfer.net:urlhaus_malware
649
+zamilgroups.com:urlhaus_malware
650
+zenglobalenerji.com:urlhaus_malware
651
+zhigao5191.com:urlhaus_malware
652
+zontiz.com:urlhaus_malware
653
+zycdjz.com:urlhaus_malware

BIN
wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/malicious-domains.cdb


+ 8 - 0
wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/malicious-ip

@@ -0,0 +1,8 @@
1
+# Auto-generated by soc-integrator ioc_list_service
2
+# DO NOT EDIT MANUALLY — changes will be overwritten on next refresh
3
+# Format: key:source_tag
4
+162.243.103.246:feodo_c2
5
+178.62.3.223:feodo_c2
6
+27.133.154.218:feodo_c2
7
+34.204.119.63:feodo_c2
8
+50.16.16.211:feodo_c2

BIN
wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/malicious-ip.cdb


+ 3 - 0
wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/malware-hashes

@@ -0,0 +1,3 @@
1
+# Auto-generated by soc-integrator ioc_list_service
2
+# DO NOT EDIT MANUALLY — changes will be overwritten on next refresh
3
+# Format: key:source_tag

BIN
wazuh-docker/single-node/config/wazuh_cluster/lists/malicious-ioc/malware-hashes.cdb


+ 2 - 2
wazuh-docker/single-node/config/wazuh_cluster/local_decoder.xml

@@ -34,8 +34,8 @@
34 34
 
35 35
 <decoder name="soc-prod-dns">
36 36
   <prematch>soc_event=dns_ioc</prematch>
37
-  <regex type="pcre2">event_type=(\S+)(?:.*?src_ip=([\d.]+))?</regex>
38
-  <order>status, srcip</order>
37
+  <regex type="pcre2">event_type=(\S+)(?:.*?src_ip=([\d.]+))?(?:.*?\bquery=(\S+))?</regex>
38
+  <order>status, srcip, url</order>
39 39
 </decoder>
40 40
 
41 41
 <decoder name="soc-prod-integrator">

+ 52 - 0
wazuh-docker/single-node/config/wazuh_cluster/rules/soc-ioc-cdb-rules.xml

@@ -0,0 +1,52 @@
1
+<!--
2
+  SOC IOC CDB Lookup Rules
3
+  ========================
4
+  These rules fire when a network field matches an entry in the threat-intel CDB lists
5
+  maintained by soc-integrator (/wazuh/ioc-lists/refresh).
6
+
7
+  Lists (compiled by wazuh-analysisd at startup/restart):
8
+    etc/lists/malicious-ioc/malicious-ip      — known-bad IPs  (feodo, threatfox, local hits)
9
+    etc/lists/malicious-ioc/malicious-domains — known-bad domains (threatfox, urlhaus, local hits)
10
+    etc/lists/malicious-ioc/malware-hashes    — malware SHA256 hashes (bazaar, threatfox, local hits)
11
+
12
+  Rule IDs: 110600–110605
13
+  Level    : 10  (above log_alert_level=3, below critical=12)
14
+-->
15
+
16
+<group name="soc_mvp,threat_intel,ioc,cdb,">
17
+
18
+  <!-- ── IP: FortiGate source IP matched threat-intel list ── -->
19
+  <rule id="110600" level="10">
20
+    <if_sid>81603</if_sid>
21
+    <list field="srcip" lookup="match_key">etc/lists/malicious-ioc/malicious-ip</list>
22
+    <description>CDB: FortiGate source IP matched threat-intel list</description>
23
+    <group>soc_prod,a2,ioc,threat_intel,cdb,</group>
24
+    <mitre>
25
+      <id>T1071</id>
26
+    </mitre>
27
+  </rule>
28
+
29
+  <!-- ── IP: FortiGate destination IP matched threat-intel list ── -->
30
+  <rule id="110601" level="10">
31
+    <if_sid>81603</if_sid>
32
+    <list field="dstip" lookup="match_key">etc/lists/malicious-ioc/malicious-ip</list>
33
+    <description>CDB: FortiGate destination IP matched threat-intel list</description>
34
+    <group>soc_prod,a2,ioc,threat_intel,cdb,</group>
35
+    <mitre>
36
+      <id>T1071</id>
37
+    </mitre>
38
+  </rule>
39
+
40
+  <!-- ── Domain: DNS query matched malicious-domains list ── -->
41
+  <!-- Parent: 100250 (soc-prod-dns decoder, extracts url from query= field) -->
42
+  <rule id="110602" level="10">
43
+    <if_sid>100250</if_sid>
44
+    <list field="url" lookup="match_key">etc/lists/malicious-ioc/malicious-domains</list>
45
+    <description>CDB: DNS query matched malicious-domains threat-intel list</description>
46
+    <group>soc_prod,a1,ioc,threat_intel,cdb,dns,</group>
47
+    <mitre>
48
+      <id>T1071.004</id>
49
+    </mitre>
50
+  </rule>
51
+
52
+</group>

+ 2 - 0
wazuh-docker/single-node/docker-compose.yml

@@ -53,6 +53,8 @@ services:
53 53
       - ./config/wazuh_cluster/rules/soc-b2-logmon-rules.xml:/var/ossec/etc/rules/soc-b2-logmon-rules.xml
54 54
       - ./config/wazuh_cluster/rules/soc-b3-sysmon-rules.xml:/var/ossec/etc/rules/soc-b3-sysmon-rules.xml
55 55
       - ./config/wazuh_cluster/rules/soc-c1-c3-rules.xml:/var/ossec/etc/rules/soc-c1-c3-rules.xml
56
+      - ./config/wazuh_cluster/rules/soc-ioc-cdb-rules.xml:/var/ossec/etc/rules/soc-ioc-cdb-rules.xml
57
+      - ./config/wazuh_cluster/lists/malicious-ioc:/var/ossec/etc/lists/malicious-ioc:z
56 58
       - ./config/wazuh_cluster/local_internal_options.conf:/var/ossec/etc/local_internal_options.conf
57 59
 
58 60
   wazuh.indexer:

tum/tmt_learning - Gogs: Simplico Git Service

暫無描述

.prettierrc.json 90B

1234567
  1. {
  2. "printWidth": 120,
  3. "semi": false,
  4. "singleQuote": true,
  5. "trailingComma": "es5"
  6. }