Guilgo Blog

Notas de mi quehacer diario con las técnologias.

Lectura: ~9 minutos

Cuando una alerta de AdGuard llega a Wazuh sabes qué dominio se ha bloqueado, pero no si realmente es phishing, malware o simplemente publicidad.

¿Qué conseguirás al terminar este artículo?

  • Enriquecer automáticamente alertas de AdGuard con urlscan.io.
  • Mostrar únicamente amenazas relevantes en Telegram.
  • Evitar enviar URLs privadas a servicios externos.
  • Integrar todo en Wazuh-lite sin depender del Dashboard oficial.

En este artículo integramos urlscan.io con Wazuh-lite para enriquecer automáticamente las alertas con URL reputation, sin abandonar Grafana ni el flujo habitual de Telegram. AdGuard bloquea la conexión; urlscan aporta el contexto necesario para decidir si la amenaza requiere atención.

La inspiración viene del artículo de Moiz Ud Din Rafay en Medium (Urlscan.io Integration with Wazuh SIEM for Automated URL Reputation Enrichment, julio 2026).

En nuestro caso lo adaptamos a:

  • Wazuh 4.14.5
  • Docker (manager en raam)
  • Grafana + alerts.json (sin Indexer Dashboard clásico)
  • custom-telegram.py

Documentación útil: urlscan.io, API de urlscan y Wazuh Integrator. Encaja con el control parental y el dashboard Grafana sin Elasticsearch.

Antes y después: el valor en una mirada

Antes (solo AdGuard → Wazuh):

AdGuard bloquea evil-domain.com
         ↓
Wazuh alerta level ≥7
         ↓
Telegram: “dominio bloqueado” + equipo

Sabes quién y qué dominio. No sabes qué es.

Después (+ enriquecimiento urlscan):

AdGuard bloquea evil-domain.com
         ↓
Wazuh alerta level ≥7 ──→ Telegram parental (como siempre)
         ↓
custom-urlscan → urlscan.io (search / result)
         ↓
alerts.json → Grafana
         ↓
Si malicious/suspicious + API key → Telegram URLSCAN REPUTATION
  · Título: Microsoft Login
  · Verdict: Malicious
  · IP: 104.x.x.x
  · Enlace al scan

El operador decide en segundos si merece atención o es ruido.

¿Por qué urlscan y no VirusTotal?

Una pregunta que suele aparecer es: ¿por qué urlscan y no VirusTotal? Elegimos urlscan porque:

  • Permite buscar escaneos existentes (/search) sin publicar tu URL.
  • No hace falta hacer submit de dominios privados del hogar (privacidad).
  • Devuelve metadatos útiles: título de página, IP, enlace al resultado, y con API key el veredicto.
  • Tiene API gratuita suficiente para un SOC lite.
  • Encaja bien como threat intelligence ligera / enriquecimiento de IOCs sobre alertas ya filtradas.

VirusTotal sería otra opción válida (más motor de firmas, más ecosistema). En nuestro caso el cuello de botella no era “¿está en VT?”, sino “¿qué pinta tiene esta URL que AdGuard acaba de cortar?” sin salir del SIEM y sin filtrar URLs internas a un portal de submit.

Para un homelab, urlscan ofrece un equilibrio muy bueno entre privacidad, simplicidad y contexto.

Problemas reales durante la implementación

Esta sección diferencia el post de una documentación oficial. Lo que falló de verdad:

  1. Directorio logs/ del host no escribible → crear ficheros de log/state con docker exec.
  2. API /result/ → 403 sin login → sin key no hay verdict_malicious; sí metadatos + enlace + regla 126109.
  3. Falsos positivos de regex (timestamps ISO tipo 57.000Z como “dominio”) → TLD alfabético + prioridad a data.domain.
  4. Integraciones custom no viven en el volumen → tras recreate hay que volver a ejecutar install-urlscan-integration.sh (docker cp a /var/ossec/integrations).
  5. Nombre sin prefijo custom-integratord rechaza el script; colateral: k8s-audit-telegram.pycustom-k8s-audit-telegram.py.
  6. Telegram urlscan sin “quién/equipo” → el enricher no copiaba client_*; corregido en enricher + formatter.

Arquitectura: enriquecimiento asíncrono

AdGuard / parental / web / AUR
              │
              ▼
         Wazuh-lite
         (level ≥ 7)
         ┌────┴────┐
         ▼         ▼
   Telegram     custom-urlscan
   “de siempre”      │
   (equipo)          ▼
                urlscan.io
              search (+ /result)
                     │
                     ▼
              enrichment.log
                     │
                     ▼
              reglas 12610x
                     │
              ┌──────┴──────┐
              ▼             ▼
         alerts.json    Telegram urlscan
         + Grafana      (solo malicious /
                         suspicious + key)

Decisiones de diseño

  • Solo usamos /search y /resultnunca submit de URLs privadas.
  • Integración únicamente para grupos concretos + level ≥ 7 (no todo el DNS).
  • Caché 24 h para no martillar la API de reputación.
  • IDs propios 126100–126109 (evitar choque con AdGuard 111xxx / AUR 125xxx; no los 111100 del artículo Medium).
  • Python embebido de Wazuh: /var/ossec/framework/python/bin/python3.

Qué mensajes de Telegram recibirás

Evento Rule ID Level ¿Telegram?
AdGuard / control parental 1110xx / 1100xx ≥7 — Quién, dominio, categoría
urlscan genérico / cache / not_found 126100, 104, 106–108 3 No
urlscan sin veredicto (sin API key) 126109 3 No
urlscan API failure 126105 5 No
urlscan malicious / suspicious / critical 126101 / 102 / 103 14 / 12 / 14 *

*Solo con veredicto real de /result/. Sin API key no se generan 126101/102/103 desde la API.

En resumen:

  • Telegram cuando AdGuard/parental detecta algo (level ≥ 7), con el equipo.
  • Telegram cuando urlscan confirma malware/suspicious (y hay API key).
  • Ningún aviso si simplemente falta la API key o el enriquecimiento no sacó veredicto.

El enricher propaga client_name, client_ip y domain de la alerta origen. Ejemplo de formato verificado (regla 126101):

🦠 *URLSCAN REPUTATION*

👤 *Quién:* Marcelo
📋 *Alerta:* urlscan.io malicious URL reputation detected
💻 *Equipo/IP:* `10.5.5.124`
🌐 *Dominio:* `evil.example`
🔗 *URL:* https://evil.example/phish
🧾 *urlscan:* https://urlscan.io/result/abc/
🦠 *Veredicto:* MALICIOSO
📊 *Risk:* critical

Mensaje Telegram URLSCAN REPUTATION: Quién, equipo/IP, dominio, enlace al scan y veredicto MALICIOSO (formato verificado en runtime, regla 126101).

API key: metadatos sí, veredicto solo con key

Importante. Sin API key el enriquecimiento funciona (título, IP, enlace al scan), pero no obtienes el veredicto de reputación (malicious / suspicious). La regla 126109 es level 3: no spamea Telegram. Si “no me llega el veredicto”, primero comprueba que existe etc/urlscan_api_key — no asumas que la integración está rota.

Sin etc/urlscan_api_key Con API key
Search público OK Search + /result/
Título, IP, enlace al scan + veredicto / score
Evento urlscan_verdict_unavailable (level 3) Posible Telegram 126101/102/103
Sin Telegram de reputación urlscan Telegram si malicious/suspicious

La key va en etc/urlscan_api_key (gitignored). Cuenta gratis: urlscan.io → Settings → API. En el momento de este post aún no estaba en producción: el circuito Telegram de reputación queda pendiente de esa pieza.

Adaptaciones frente al artículo Medium

Artículo de referencia Nuestro wazuh-lite
Integración amplia por level Grupos concretos + level ≥ 7
IDs tipo 111100 126100–126109
Stack Indexer / Dashboard Grafana + proxy alerts.json
Submit en algunos flujos Nunca submit; solo search/result
Telegram genérico Dos capas: parental vs urlscan con veredicto

Verificación de la integración (ejecutado 2026-07-25)

ssh raam 'cd /home/dguillermo/kubernetes/wazuh-lite && bash scripts/install-urlscan-integration.sh'
ssh raam 'cd /home/dguillermo/kubernetes/wazuh-lite && bash scripts/test-urlscan-integration.sh'

Salida real: RESULTADO: OK — urlscan integration verificada.

Comprobado: enricher sobre example.com; sin key → urlscan_verdict_unavailable; wazuh-logtest126101 level 14; evento en alerts.json; Enabling integration for: 'custom-urlscan'; formato Telegram con Quién + Equipo/IP.

No verificado aún en producción: Telegram real disparado por veredicto urlscan con API key.

Cómo integrar urlscan.io en tu Wazuh-lite

Requisitos

  • Wazuh 4.14+ (probado en 4.14.5)
  • Docker (manager en contenedor)
  • AdGuard (u otra fuente) ya integrado en Wazuh, con alertas level ≥ 7
  • Python embebido de Wazuh (/var/ossec/framework/python/bin/python3)
  • API key de urlscan (opcional para metadatos; recomendada para veredictos y Telegram de reputación)

Pasos

  1. Cuenta en urlscan.io → API key (docs API).
  2. Escribir la key en wazuh-lite/etc/urlscan_api_key (chmod 640).
  3. bash scripts/install-urlscan-integration.sh
  4. bash scripts/test-urlscan-integration.sh
  5. Esperar una alerta AdGuard level ≥ 7 o invocar el enricher con JSON de prueba.

Piezas: scripts/custom-urlscan.py, etc/rules/urlscan_rules.xml, integración en ossec.conf (Wazuh Integrator) y formatter Telegram para el grupo urlscan.

Tras docker compose … up --force-recreate: /var/ossec/integrations no está en volumen → reinstalar la integración.

Conclusión

La integración no pretende sustituir a AdGuard ni convertir urlscan en un antivirus. El objetivo es práctico: cuando una alerta importante aparece en Wazuh, disponer de inmediato del contexto suficiente para decidir si merece atención o puede ignorarse.

En un homelab eso supone menos tiempo investigando y menos falsas alarmas, sin modificar el flujo habitual del SIEM ni spamear el móvil cuando no hay veredicto.

En un SOC doméstico el tiempo también importa. Si una alerta ya llega enriquecida con contexto y reputación, la decisión deja de ser «¿qué hago ahora?» para convertirse en «¿requiere actuación o puedo descartarla?».

Próximos pasos: poner la API key en producción, un panel Grafana filtrando rule.groups con urlscan, y una prueba E2E con un dominio malicioso conocido.

Más contexto en el blog: DNS tunneling vía AdGuard, SOC proactivo del homelab y Wazuh 5 en producción.