Módulo 15 de 16

Módulo 15: Detection-as-code, tuning y operación diaria

Módulo 15: Detection-as-code, tuning y operación diaria

Casi todo SOC tiene, en algún rincón de la consola de su SIEM, una regla que nadie recuerda haber escrito. Dispara desde hace dos años, alguien la ajustó una vez a mano un viernes por la tarde y el motivo de ese ajuste vive, si vive en algún sitio, en la memoria de una persona que puede que ya ni trabaje allí. Nadie sabe qué técnica cubre exactamente, si la telemetría que necesita sigue llegando, ni si el «ajuste» de aquella tarde tapó un falso positivo real o simplemente apagó la regla a medias. Eso es deuda técnica con forma de alerta, y se acumula igual que cualquier otra deuda: en silencio, hasta que hace falta tocarla y nadie se atreve.

Los módulos anteriores de este curso enseñaron a fabricar una detección buena: con telemetría verificada, escrita en Sigma, probada contra un test atómico. Este módulo cambia de pregunta. No es «¿cómo escribo una regla que funcione?» sino «¿cómo hago que esa regla siga funcionando dentro de un año, que cualquiera de mi equipo entienda por qué existe, y que un cambio malo se pueda deshacer en minutos?». La respuesta corta es tratar las reglas como se trata el código: en un repositorio, con revisión, con pruebas automáticas y con un procedimiento de despliegue que no dependa de que alguien recuerde los pasos de memoria.

Qué aprenderás

  • Por qué una regla escrita a mano en la consola del SIEM es deuda técnica desde el momento en que se guarda, aunque funcione perfectamente ese primer día.
  • Cómo estructurar un repositorio de detecciones: carpetas, convención de nombres y los metadatos que tiene que llevar cada regla, incluidos campos que Sigma no define por defecto.
  • Qué mira de verdad quien revisa una detección antes de aprobarla, más allá de leer si el YAML tiene buena pinta.
  • Qué se puede comprobar de una regla sin tener un SIEM delante, y qué exige datos reales, con ejemplos tomados de tres proyectos públicos distintos.
  • Cómo montar un pipeline de despliegue con entornos separados, versionado de lo que está activo en producción y una forma de deshacer un cambio que sale mal.
  • Por qué la respuesta correcta a un falso positivo casi nunca es tocar la lógica original de la regla, y cómo lo resuelve el mecanismo de filtros de Sigma.
  • El ciclo de vida completo de una regla, de experimental a retirada, y por qué retirar una regla es tan trabajo como escribirla.
  • Qué métricas de operación nuevas merece la pena llevar, sin repetir las que ya viste en el módulo 1, y cómo se documenta lo que queda abierto al cambiar de turno.

La regla en la consola es deuda desde el primer día

El problema de escribir una regla directamente en la consola de un Splunk, un Elastic o un Wazuh no es que la regla vaya a funcionar mal. Puede funcionar perfectamente. El problema es todo lo que esa forma de trabajar no deja detrás: no hay historial de quién la escribió ni cuándo, no hay forma de ver qué decía la regla la semana pasada frente a hoy, no hay un paso obligatorio en el que otra persona revise la lógica antes de que empiece a generar alertas, y no hay ninguna prueba automática que impida que un cambio de un carácter (una comilla mal cerrada, un operador invertido) rompa la regla en silencio. Cuando algo va mal, la única forma de averiguar qué pasó es preguntar a la persona que la tocó por última vez, si es que alguien lo recuerda.

Guardar esa misma regla como un fichero de texto en un repositorio git no cambia la lógica de detección ni un carácter. Cambia todo lo demás: cada modificación queda firmada con autor y fecha, cualquiera puede ver la diferencia exacta entre dos versiones, y una batería de pruebas puede rechazar automáticamente una regla con la sintaxis rota antes de que llegue a producción. A esto se le llama detection-as-code: la misma disciplina que la ingeniería de software adoptó hace décadas para el mismo problema, aplicada a un tipo de código que decide, entre otras cosas, cuándo suena una alerta a las tres de la madrugada.

El repositorio de detecciones

Un repositorio de reglas necesita una estructura previsible antes que ninguna otra cosa. Si cada persona del equipo guarda sus reglas donde le parece, el repositorio se convierte en el mismo caos que la consola del SIEM, solo que ahora versionado.

Carpetas y convención de nombres

No hace falta inventar una convención desde cero: los tres proyectos públicos que se citan más abajo en este módulo ya resolvieron este problema y conviene copiarles la parte que funciona. SigmaHQ separa sus reglas en carpetas de primer nivel por propósito (rules/ para detección genérica organizada por producto, rules-emerging-threats/ para campañas concretas, deprecated/ para lo retirado, como ya viste en el módulo 7) y, dentro de rules/, usa un prefijo de fichero por categoría (proc_creation_win_, registry_set_) que permite encontrar algo con un grep sin abrir carpeta por carpeta. Un repositorio propio, aunque sea de dos reglas, se beneficia de la misma idea a pequeña escala:

detecciones/
  rules/
    windows/
      win_security_user_creation.yml
    linux/
      lnx_auditd_susp_exe_folders.yml
  docs/
    ads/
      t1136-001-local-user-creation.md
      t1587-suspicious-exec-folders.md
    DESPLIEGUE.md
  filters/
  exceptions.yml
  .github/
    workflows/
      ci-detecciones.yml

La carpeta rules/ separa por producto igual que hace SigmaHQ, porque el pipeline de conversión y el propio despliegue van a diferir entre Windows y Linux, como viste en los módulos 7 y 11. docs/ads/ guarda la documentación de estrategia de cada regla, un fichero por regla, con un nombre que combina la técnica ATT&CK y una descripción corta. filters/ guarda los ficheros de filtro de Sigma que verás más abajo, separados de las reglas para que quede claro qué documento define comportamiento nuevo y cuál solo lo recorta. exceptions.yml es un registro propio (no forma parte de la especificación de Sigma) que se explica en la sección de tuning.

Metadatos obligatorios: lo que Sigma ya exige y lo que le añades tú

El módulo 7 ya explicó qué campos son obligatorios según la especificación de Sigma (title, logsource, detection) y cuáles exige, por convención propia, el propio repositorio de SigmaHQ (status, description, author, date, tags, falsepositives, level). Para un repositorio de un SOC interno hace falta al menos un campo más que ninguno de los dos exige: quién es el dueño de la regla hoy, no quién la escribió hace tres años. author documenta autoría histórica; owner documenta responsabilidad presente, y son cosas distintas en cuanto alguien cambia de equipo.

La buena noticia es que añadir este campo no rompe nada. La propia especificación de Sigma, en la plantilla de estructura de fichero de su documento de reglas, termina con la línea [arbitrary custom fields] tras los atributos estándar: cualquier campo adicional a nivel raíz es válido por diseño, no un truco que funcione «por ahora». La implementación de referencia, pySigma, lo confirma en su propio código: la clase SigmaRule guarda cualquier atributo que no reconozca en un diccionario llamado custom_attributes, y trae incluso un validador dedicado, CustomAttributesValidator, cuyo propósito no es rechazar campos personalizados sino avisar si el nombre que usaste se parece demasiado a uno de los oficiales (para pillar un autor mal escrito en vez de author, por ejemplo). Lo comprobé instalando sigma-cli en esta misma sesión: una regla con owner: equipo-deteccion-soc y ads_doc: docs/ads/t1136-001-local-user-creation.md añadidos al final pasa sigma check sin ningún aviso y se convierte exactamente igual que sin ellos.

La estrategia documentada junto a la regla

El módulo 1 de este curso presentó la plantilla ADS (Alerting and Detection Strategy) de Palantir con sus nueve secciones. La pieza que faltaba entonces era dónde vive ese documento en la práctica, y la respuesta es: en el mismo repositorio que la regla, versionado igual que ella, no en un wiki aparte que nadie actualiza el mismo día que cambia la lógica. Cuando alguien modifica la condición de una regla y no toca el ADS correspondiente en el mismo cambio, la revisión por pares (siguiente sección) tiene que rechazarlo por ese motivo, igual que rechazaría código sin su prueba asociada.

El campo ads_doc que viste arriba apunta exactamente a ese fichero. No hace falta repetir aquí las nueve secciones completas, ya están explicadas en el módulo 1; el ejercicio de este módulo instancia el documento para las dos reglas concretas del repositorio de ejemplo.

Revisión por pares de una detección

Revisar una detección no es lo mismo que revisar una función de software, aunque el mecanismo (una solicitud de cambio que otra persona aprueba antes de fusionar) sea idéntico. Quien revisa tiene que responder a preguntas concretas, no simplemente comprobar que el YAML tiene buena forma:

  • ¿La lógica hace lo que el title y la description dicen que hace? Es fácil escribir una condición que coincide con más, o con menos, de lo que el autor pensaba.
  • ¿Existe de verdad la telemetría que la regla necesita? Esto se responde mirando el logsource contra lo que el equipo sabe, con certeza, que llega al SIEM (módulos 3, 4, 5 y 6 de este curso), no contra lo que «debería» llegar.
  • ¿Qué falsos positivos son previsibles en este entorno concreto, y están anotados en falsepositives y en la sección correspondiente del ADS?
  • ¿Está documentada la respuesta? Una regla que dispara y no dice qué hacer con la coincidencia traslada el trabajo de pensar al analista de guardia, en el peor momento posible para pensar con calma.
  • ¿Cambia el ADS en el mismo cambio que la regla, cuando la lógica cambia lo suficiente como para que el ADS anterior ya no describa lo que la regla hace hoy?

Un mecanismo simple ayuda a que esta revisión no dependa de que alguien se acuerde de pedirla: un fichero CODEOWNERS, que GitHub reconoce si vive en la carpeta .github/, en la raíz o en docs/ del repositorio, asigna automáticamente un revisor a cualquier cambio dentro de una ruta determinada. Una línea como rules/linux/ @equipo-linux hace que cualquier solicitud de cambio que toque una regla de esa carpeta pida revisión a ese equipo sin que nadie tenga que acordarse de añadirlo a mano.

Pruebas automáticas: lo que se puede comprobar sin datos y lo que los exige

No todas las pruebas de una detección son iguales, y conviene distinguir con claridad qué capa cubre cada una porque tres proyectos públicos distintos, cada uno con su propio ecosistema, han resuelto esta división de forma distinta.

Lo que se comprueba sin ningún SIEM delante

Sintaxis, esquema y que la regla convierte sin error al backend se verifican con la regla sola, sin telemetría real de ningún tipo. Para Sigma, la herramienta es sigma check, la misma que ya usaste en el módulo 7. Merece una advertencia: su propia ayuda de línea de comandos, la que se obtiene con sigma check --help, todavía describe el comando como «(not yet implemented)». Lo comprobé en esta sesión instalando sigma-cli 3.1.0 (la misma versión ya citada en el módulo 7) y ejecutando el comando de verdad, y funciona: valida sintaxis, condiciones y aplica una batería de validadores con nombre propio en pySigma, entre ellos IdentifierExistenceValidator (avisa si falta el id), DuplicateTitleValidator, DanglingDetectionValidator (un identificador de búsqueda que la condition nunca referencia) y el ya mencionado CustomAttributesValidator. La etiqueta de la ayuda está simplemente desactualizada, y es el tipo de discrepancia entre documentación y comportamiento real que conviene comprobar uno mismo antes de construir un pipeline sobre ella.

Esta es la salida real de comprobar una regla con la condition mal escrita (referencia a un identificador de búsqueda, selection, que no existe porque el autor escribió seleccion por error), generada en esta misma sesión:

Parsing Sigma rules
Condition error in rules/windows/regla_rota_ejemplo.yml:Detection 'selection' not defined in detections in rules/windows/regla_rota_ejemplo.yml
Checking Sigma rules

=== Summary ===
Found 0 errors, 1 condition errors and 0 issues.
No rule errors found.

Condition error summary:
+-------+--------------------------------------------------------------+
| Count | Condition Error                                              |
+-------+--------------------------------------------------------------+
| 1     | Detection 'selection' not defined in detections in           |
|       | rules/windows/regla_rota_ejemplo.yml                         |
+-------+--------------------------------------------------------------+
No validation issues found.
Check failure

(He acortado la ruta absoluta del fichero, que en esta sesión apuntaba a un directorio temporal de trabajo, dejando intacto el texto del error, que es literal.) Ese «Check failure» final es justo lo que un paso de integración continua necesita: un código de salida distinto de cero que impide fusionar el cambio.

Elastic resuelve esta misma capa con su propio módulo Python, detection-rules (licencia Elastic License v2, así que aquí se cita y se enlaza, no se reempaqueta ningún contenido suyo). El comando python -m detection_rules test ejecuta una batería en la carpeta tests/ del propio repositorio (con ficheros como test_all_rules.py) que, según describe su propia guía de contribución, comprueba que las reglas «son sintácticamente correctas, usan bien el esquema de ECS o de Beats, y que los metadatos también quedan validados». Las reglas de Elastic se escriben en TOML, no en YAML como Sigma, y esta batería de pruebas corre entera sin conexión a ningún Elasticsearch ni Kibana: es la misma capa que sigma check, aplicada a otro formato de regla.

Lo que exige datos reales

Comprobar que una regla convierte sin error no dice si esa consulta encontraría de verdad el evento que busca. Para eso hacen falta datos, y aquí los tres proyectos toman caminos distintos.

Splunk resuelve esta capa con contentctl (licencia Apache-2.0, versión 5.6.0 publicada el 28 de abril de 2026 según la propia API de releases del repositorio), la herramienta que usa el equipo de Splunk Threat Research para gestionar el repositorio público security_content. El comando contentctl validate cubre la capa sin datos (esquema y consistencia de los YAML). El comando contentctl test es la capa con datos: levanta contenedores Docker efímeros con una instancia de Splunk, carga datos de ataque de verdad y comprueba que la búsqueda de la detección encuentra lo que debería, con modos como --mode changes para probar solo el contenido que cambió en una rama. Es una prueba mucho más cara de ejecutar que sigma check, y mucho más honesta sobre si la regla funciona de verdad.

Sobre contentctl hay una precisión necesaria antes de recomendarlo sin más: su propio repositorio declara, en el README que leí en esta sesión, que Splunk «está trasladando la inversión futura de contentctl a Detection Studio» y que ya no acepta nuevas solicitudes de cambio ni de funcionalidad. Detection Studio es una función propia de Splunk Enterprise Security, anunciada en RSAC 2026 y disponible de forma general desde ese mismo mes para clientes de ES Essentials y ES Premier: sustituye a contentctl solo si la organización paga esa capa comercial. contentctl sigue siendo software libre, instalable con pipx install contentctl, y el repositorio security_content que empaqueta seguía publicando versiones nuevas hasta hace dos semanas de esta sesión (la 6.2.0, del 13 de julio de 2026); solo el desarrollo de la herramienta en sí entró en modo de mantenimiento.

SigmaHQ resuelve esta misma capa de otra forma, sin depender de un motor de SIEM completo: su propio flujo de integración continua incluye un trabajo de pruebas de regresión que descarga un binario llamado evtx-sigma-checker, de Nextron Systems, y lo ejecuta contra evtx-baseline, un repositorio propio (licencia Apache-2.0) de ficheros EVTX de actividad legítima («goodware»): instalaciones de software normales, navegación web, interacción de usuario habitual, en Windows 7, 10, 11 y Server 2022. La idea es sencilla y barata de ejecutar comparada con levantar un Splunk entero: si una regla nueva coincide con actividad de ese conjunto de referencia, algo en su lógica es demasiado amplio, y el propio pipeline (python tests/regression_tests_runner.py, confirmado leyendo el fichero de flujo de trabajo en crudo) lo señala antes de que el cambio se fusione.

Capa Sigma / SigmaHQ Elastic detection-rules Splunk contentctl
Sintaxis y esquema, sin datos sigma check (pySigma) python -m detection_rules test contentctl validate
Conversión al backend sigma convert no aplica igual (el propio motor de Elastic ejecuta la consulta nativa) empaquetado dentro de contentctl build
Contra datos reales evtx-sigma-checker contra EVTX de referencia (repositorio evtx-baseline) no forma parte del framework de pruebas del propio repositorio contentctl test, contenedor Docker con Splunk y datos de ataque
Licencia del contenido DRL 1.1 Elastic License v2 Apache-2.0

Despliegue: del repositorio a la cola de alertas

Entornos separados y qué convierte el pipeline

El pipeline de despliegue hace, en orden, lo que hasta ahora hiciste a mano: revisa que sigma check pase, convierte cada regla con sigma convert al backend y al pipeline que corresponda (como en el módulo 7), y publica el resultado en el SIEM. La pieza que falta cuando se automatiza es la separación entre un entorno de pruebas y el de producción: una rama o un directorio de staging donde una regla nueva vive un tiempo, con una carga de trabajo real pero sin que sus alertas lleguen todavía a la cola que atiende el analista de guardia, antes de promocionarla. Es el mismo principio que ya viste en el ciclo de vida de una regla Sigma (experimental, test, stable, en el módulo 7): aquí se aplica a dónde vive la regla, no solo a la etiqueta que lleva dentro del YAML.

Esta es una extensión, con conversión incluida, del ejemplo de integración continua que ya se adelantó en el módulo 1 de este curso:

name: ci-detecciones
on: [push, pull_request]
jobs:
  validar:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - run: pip install sigma-cli pysigma-backend-opensearch pysigma-pipeline-windows
      - run: sigma check --fail-on-error --fail-on-issues rules/
      - run: |
          duplicados=$(grep -rh "^id: " rules/ | sort | uniq -d)
          if [ -n "$duplicados" ]; then
            echo "IDs duplicados encontrados:"; echo "$duplicados"; exit 1
          fi
      - run: sigma convert -t opensearch_lucene -p windows-logsources rules/windows/win_security_user_creation.yml
      - run: sigma convert -t opensearch_lucene --without-pipeline rules/linux/lnx_auditd_susp_exe_folders.yml

La versión actions/checkout@v5 no es arbitraria: es la misma que usa hoy el propio flujo de trabajo de pruebas de SigmaHQ, comprobado leyendo su fichero de configuración en crudo en esta sesión. Y la comprobación de identificadores duplicados con grep, sort y uniq reproduce, a menor escala, el mismo trabajo que hace el flujo sigma-test.yml real del repositorio de SigmaHQ sobre miles de reglas.

Versionado de lo desplegado

Convertir y publicar no basta si, seis meses después, nadie puede responder «¿qué versión de esta regla está activa en el SIEM ahora mismo?». La forma más simple de responder es que el propio paso de publicación escriba el hash del commit desplegado como comentario o metadato de la búsqueda guardada (Splunk) o de la regla de alerta (la mayoría de SIEM aceptan un campo de descripción libre). Ante cualquier duda, se compara ese hash contra el historial de git y se ve qué versión del fichero generó la consulta que está corriendo, sin depender de que alguien recuerde haberla tocado.

Vuelta atrás cuando algo sale mal

Una regla recién desplegada puede desbordar la infraestructura del SIEM, no solo la bandeja del analista. En Splunk, por ejemplo, una búsqueda programada que tarda más de lo que dura su propio intervalo, o que consume el cupo de concurrencia asignado, provoca lo que la documentación oficial llama una «skipped scheduled search»: esa búsqueda, o peor, otras de distintos equipos, se saltan directamente esa ejecución. Es un comportamiento específico de Splunk, no algo que se pueda dar por hecho en cualquier SIEM, pero ilustra el problema general: una regla mal escrita no solo genera ruido, puede degradar el rendimiento de todo el sistema de detección.

La primera respuesta ante esto no es reescribir la regla con prisa; es deshabilitarla, algo que debería tardar segundos porque ya está automatizado, y solo después investigar con calma. La segunda respuesta, si el propio despliegue automático introdujo el problema (por ejemplo, un cambio de pipeline que amplió sin querer el alcance de una condición), es volver atrás al commit anterior y redesplegar exactamente esa versión anterior, no intentar arreglar el problema a mano sobre lo que ya está en producción. Que el pipeline de despliegue sepa redesplegar cualquier commit anterior sin trabajo extra es, en la práctica, la razón de fondo por la que merece la pena montar todo esto en vez de seguir editando la consola directamente.

Tuning con criterio: filtrar, no parchear

Cuando una regla genera un falso positivo real, la tentación inmediata es abrir el fichero y añadir una exclusión dentro de su propia condition. Es la forma más rápida de acallar la alerta, y es también la que peor envejece: la lógica original y la excepción del entorno quedan mezcladas en el mismo bloque, así que dentro de un año nadie puede saber, mirando el YAML, cuál de las dos partes era la detección real y cuál un parche puesto para callar el ruido de un servidor concreto. Si esa regla se comparte entre varios equipos o se actualiza más adelante desde el repositorio de origen (SigmaHQ, por ejemplo), la exclusión casera se pierde en el primer merge.

El mecanismo de filtros de Sigma

La especificación de filtros de Sigma, ya presentada en el módulo 7, resuelve exactamente este problema con un documento YAML separado. Lo comprobé de nuevo en esta sesión contra la propia especificación (versión 2.1.0): un filtro es obligatoriamente title, logsource y una sección filter con rules (la lista de id de las reglas Sigma afectadas, obligatoria), selection y condition; y, algo que conviene tener claro, un filtro no lleva ni level ni status propios porque, en palabras literales de la especificación, «su propósito es enriquecer una regla Sigma ya existente», no describir una alerta nueva.

Aplicado al ejemplo de este módulo, si la regla «Program Executions in Suspicious Folders» (id a39d7fa7-3fbd-4dc2-97e1-d87f546b1bbc, la del ejercicio) empieza a disparar por un script de despliegue legítimo que corre habitualmente desde /srv/www/deploy/, la respuesta no es tocar la lista exe|startswith de la regla original. Es un fichero nuevo, con prefijo mf_ por convención:

title: Filtro para script de despliegue interno en srv-www
logsource:
    product: linux
    service: auditd
filter:
    rules:
        - a39d7fa7-3fbd-4dc2-97e1-d87f546b1bbc
    selection:
        exe: '/srv/www/deploy/publicar.sh'
    condition: not selection

El not delante de selection no es un detalle menor: sin él, el filtro haría justo lo contrario, restringiría la regla a disparar solo cuando el ejecutable fuera ese script, en vez de excluirlo. Lo confirmé leyendo el código fuente de pySigma en esta sesión: internamente, el motor une la condición del filtro con la de la regla original mediante un AND literal, sin negar nada por defecto, así que la negación depende por completo de lo que escriba quien redacta el filtro.

La regla original no se toca. Su historial de git sigue siendo el historial de la detección en sí, sin ruido de entorno mezclado. El filtro se puede revisar, fechar y retirar de forma independiente, y si algún día se sustituye la regla original por una versión más nueva del repositorio de origen, el filtro sigue aplicándose sin conflicto porque referencia el id, no el contenido del fichero.

Excepciones con dueño y fecha de caducidad

Un filtro documenta el cómo (qué se excluye), pero no siempre documenta el porqué ni, sobre todo, hasta cuándo. Esa parte no forma parte de la especificación de Sigma: es una convención propia del repositorio, y conviene llevarla en un registro aparte, tan simple como una tabla o un fichero exceptions.yml con una entrada por excepción:

- filtro: mf_srv_www_deploy_script.yml
  regla_afectada: a39d7fa7-3fbd-4dc2-97e1-d87f546b1bbc
  motivo: "Script de despliegue interno ejecuta desde /srv/www/deploy, confirmado con el equipo de plataforma"
  aprobado_por: "responsable-deteccion"
  fecha_alta: 2026-07-27
  caduca: 2026-10-27

El campo caduca es el que de verdad importa. Una excepción sin fecha de caducidad no es una excepción, es un agujero permanente que nadie va a revisar jamás porque no hay ningún disparador que obligue a hacerlo. Cuando llega esa fecha, alguien (idealmente quien la aprobó) tiene que decidir de forma activa si la excepción sigue siendo necesaria, se renueva con una fecha nueva, o se retira porque el script de despliegue ya no existe o cambió de ruta. Sin ese mecanismo, el número de filtros acumulados solo crece, y con él, silenciosamente, el hueco real de cobertura de cada regla que llevan puesta encima.

Ciclo de vida: de experimental a retirada

El módulo 7 ya explicó los cinco valores del campo status de una regla Sigma y por qué toda regla nueva en el repositorio de SigmaHQ empieza obligatoriamente en experimental. Lo que no se cubrió allí es cómo se gestiona ese avance a escala, y aquí SigmaHQ ofrece un ejemplo real y automatizado que vale la pena copiar. Su propio flujo de trabajo sigma-rule-promoter.yml (lo leí en crudo en esta sesión) corre una vez al mes, instala pySigma y ejecuta un script propio, tests/promote_rules_status.py, que promociona automáticamente de experimental a test cualquier regla que lleve más de 300 días sin cambios, y abre una solicitud de cambio con ese ajuste para que un revisor humano la confirme. No es una cifra universal ni parte de la especificación, es un criterio operativo de ese proyecto en concreto, pero demuestra algo útil: la promoción de estado no tiene por qué depender de que alguien se acuerde de revisarlo, se puede automatizar y dejar solo la confirmación final a una persona.

La retirada es la parte del ciclo que casi nadie automatiza y que más falta hace. Una regla candidata a retirarse entra en dos categorías, y conviene no tratarlas igual. La primera es la que lleva mucho tiempo sin disparar en absoluto: puede significar que la técnica que cubre simplemente no ha ocurrido (la situación deseable, ya explicada en el módulo 14), o que la telemetría que necesitaba desapareció hace meses sin que nadie lo notara. La segunda es la que sí dispara con regularidad, pero cuyo historial de cierres muestra que el cien por cien de esos disparos se cerró como ruido: falla por exceso de falsos positivos que nadie ha corregido con un filtro, no por falta de uso. Las dos merecen una revisión activa, nunca una retirada automática por antigüedad sin mirar primero por qué, y las dos exigen la misma pregunta antes de borrar el fichero: ¿la ausencia de disparos es buena señal o hueco de telemetría? Eso no se responde desde el repositorio solo, hace falta volver a la matriz de validación del módulo 14 y comprobarlo contra un test atómico real antes de dar la regla por muerta.

Métricas de operación, sin repetir el módulo 1

El módulo 1 ya definió la proporción de alertas accionables y la cobertura de técnicas ATT&CK con telemetría real comprobada como las dos métricas centrales de un SOC, con la advertencia de que ninguna sustituye a la otra. Aquí interesan dos métricas más, específicas de cómo se opera un repositorio de detección-as-code, que no tienen sentido sin el historial de git que este módulo acaba de construir.

La antigüedad media de las reglas activas se calcula sobre la fecha de modified (o, a falta de ese campo, la fecha del último commit real que tocó la lógica de detection, no un cambio cosmético de formato). Un repositorio donde la antigüedad media crece sin freno, mes tras mes, es un repositorio donde nadie está revisando nada: ni se ajustan reglas ruidosas, ni se retiran las que ya no aportan, ni se documentan excepciones nuevas. No hace falta un número objetivo universal aquí tampoco; lo que hace falta es que esa cifra se mida y se enseñe en la reunión de operación, no que se asuma buena porque nadie se queja.

El tiempo desde que se publica una técnica nueva de MITRE ATT&CK hasta que existe una regla con esa etiqueta en el repositorio es la métrica que conecta este módulo con el módulo 8 (de la técnica al caso de uso). Se mide comparando la fecha de publicación de la técnica, que consta en las propias notas de versión de ATT&CK, contra la fecha del primer commit que introdujo un fichero con attack.tXXXX en sus tags. Una organización que tarda un año en cubrir una técnica publicada tiene un problema de priorización distinto al de una que tarda una semana pero cubre mal (la primera métrica del módulo 1 lo delata). Las dos cifras juntas, velocidad y calidad, dicen mucho más que cualquiera de las dos por separado.

Turno y traspaso

La ingeniería de detección no vive solo en solicitudes de cambio que se revisan con calma durante el día. Cuando cambia el turno de guardia, lo que queda a medias (una regla en revisión, un filtro pendiente de aprobación, una excepción a punto de caducar, una regla en staging esperando confirmación) tiene que quedar por escrito en algún sitio que la persona entrante pueda leer, sin depender de una conversación verbal de cinco minutos al final del turno. La guía sobre cómo trabajar en un SOC ya cubre el traspaso operativo general entre analistas; aquí interesa solo la parte de ingeniería de detección, que tiene su propio ritmo (medido en días, no en horas) y su propio soporte natural: el tablero de solicitudes de cambio abiertas del repositorio, con una nota breve de estado en cada una, es casi siempre mejor traspaso que un documento aparte que hay que acordarse de mantener al día.

Ejercicio: monta tu repositorio de detecciones

Vas a construir un repositorio mínimo con dos reglas reales, ya vistas en este curso, su documentación de estrategia, una comprobación automática y un procedimiento de despliegue escrito. No hace falta ningún SIEM conectado para completarlo entero: todo lo que se pide corre en local con sigma-cli.

  1. Crea la estructura de carpetas del repositorio, como en el ejemplo de este módulo, e instala las herramientas:

    mkdir -p detecciones/rules/windows detecciones/rules/linux detecciones/docs/ads
    python -m pip install sigma-cli pysigma-backend-opensearch pysigma-pipeline-windows
  2. Guarda las dos reglas. La primera es «Local User Creation» (id 66b6be3d-55d0-4f47-9855-d69df21740ea, autor Patrick Bareiss, licencia DRL 1.1), ya analizada campo a campo en el módulo 14, con los dos metadatos propios de este repositorio añadidos al final, en rules/windows/win_security_user_creation.yml:

    title: Local User Creation
    id: 66b6be3d-55d0-4f47-9855-d69df21740ea
    status: test
    description: |
        Detects local user creation on Windows servers, which shouldn't happen in an Active Directory environment. Apply this Sigma Use Case on your Windows server logs and not on your DC logs.
    references:
        - https://patrick-bareiss.com/detecting-local-user-creation-in-ad-with-sigma/
    author: Patrick Bareiss
    date: 2019-04-18
    modified: 2021-01-17
    tags:
        - attack.persistence
        - attack.t1136.001
    logsource:
        product: windows
        service: security
    detection:
        selection:
            EventID: 4720
        condition: selection
    falsepositives:
        - Domain Controller Logs
        - Local accounts managed by privileged account management tools
    level: low
    owner: equipo-deteccion-soc
    ads_doc: docs/ads/t1136-001-local-user-creation.md

    La segunda es «Program Executions in Suspicious Folders» (id a39d7fa7-3fbd-4dc2-97e1-d87f546b1bbc, autor Florian Roth, Nextron Systems, licencia DRL 1.1), ya vista en el módulo 11, en rules/linux/lnx_auditd_susp_exe_folders.yml:

    title: Program Executions in Suspicious Folders
    id: a39d7fa7-3fbd-4dc2-97e1-d87f546b1bbc
    status: test
    description: Detects program executions in suspicious non-program folders related to malware or hacking activity
    references:
        - Internal Research
    author: Florian Roth (Nextron Systems)
    date: 2018-01-23
    modified: 2021-11-27
    tags:
        - attack.t1587
        - attack.t1584
        - attack.resource-development
    logsource:
        product: linux
        service: auditd
    detection:
        selection:
            type: 'SYSCALL'
            exe|startswith:
                - '/tmp/'
                - '/var/www/'
                - '/home/*/public_html/'
                - '/usr/local/apache2/'
                - '/usr/local/httpd/'
                - '/var/apache/'
                - '/srv/www/'
                - '/home/httpd/html/'
                - '/srv/http/'
                - '/usr/share/nginx/html/'
                - '/var/lib/pgsql/data/'
                - '/usr/local/mysql/data/'
                - '/var/lib/mysql/'
                - '/var/vsftpd/'
                - '/etc/bind/'
                - '/var/named/'
        condition: selection
    falsepositives:
        - Admin activity (especially in /tmp folders)
        - Crazy web applications
    level: medium
    owner: equipo-deteccion-soc
    ads_doc: docs/ads/t1587-suspicious-exec-folders.md
  3. Escribe el ADS de la primera regla en docs/ads/t1136-001-local-user-creation.md, instanciando la plantilla de nueve secciones del módulo 1 para este caso concreto (aquí en formato reducido, como referencia de lo que tiene que contener):

    Goal: detectar la creacion de una cuenta local en un servidor Windows
          que deberia gestionar sus cuentas via Active Directory.
    Categorization: T1136.001, tactica Persistence.
    Strategy Abstract: EventID 4720 del canal Security, sin exigir mas
          contexto porque la creacion de cuentas locales en servidores de
          dominio es en si misma la senal.
    Technical Context: canal Security de Windows, EventID 4720. No
          requiere Sysmon.
    Blind Spots and Assumptions: no cubre creacion de cuentas via API o
          PowerShell remoto si ese log no llega al mismo canal; asume que
          la politica de auditoria de Gestion de cuentas de usuario esta
          activa (modulo 3).
    False Positives: herramientas de gestion de cuentas privilegiadas que
          crean cuentas locales de servicio de forma legitima (documentado
          en el campo falsepositives de la regla).
    Validation: test atomico T1136.001 de Atomic Red Team, ya ejecutado
          en el modulo 14 de este curso.
    Priority: media-baja (level: low en la regla); sube si el servidor
          afectado es critico.
    Response: confirmar con el propietario del servidor si la cuenta era
          esperada; si no, tratar como posible persistencia y escalar
          segun el procedimiento del curso de DFIR.
    Owner: equipo-deteccion-soc
  4. Comprueba las dos reglas juntas con sigma check:

    sigma check detecciones/rules/

    Esta es la salida real de ejecutarlo, generada en esta sesión sobre las dos reglas de este ejercicio:

    Parsing Sigma rules
    Checking Sigma rules
    
    === Summary ===
    Found 0 errors, 0 condition errors and 0 issues.
    No rule errors found.
    No condition errors found.
    No validation issues found.
  5. Convierte cada regla a una consulta real de OpenSearch (el motor que hay detrás del indexador de tu laboratorio Wazuh del módulo 2), cada una con el pipeline que le corresponde:

    sigma convert -t opensearch_lucene -p windows-logsources rules/windows/win_security_user_creation.yml
    sigma convert -t opensearch_lucene --without-pipeline rules/linux/lnx_auditd_susp_exe_folders.yml

    Estas son las dos salidas reales, generadas en esta sesión:

    Channel:Security AND EventID:4720
    type:SYSCALL AND (exe:(/tmp/* OR /var/www/* OR /home/*/public_html/* OR /usr/local/apache2/* OR /usr/local/httpd/* OR /var/apache/* OR /srv/www/* OR /home/httpd/html/* OR /srv/http/* OR /usr/share/nginx/html/* OR /var/lib/pgsql/data/* OR /usr/local/mysql/data/* OR /var/lib/mysql/* OR /var/vsftpd/* OR /etc/bind/* OR /var/named/*))

    Nota que la regla Linux no llevó pipeline: no existe hoy en el directorio oficial de pySigma un pipeline de mapeo específico para auditd, como ya se explicó en el módulo 7, así que sus nombres de campo pasan directos.

  6. Escribe el procedimiento de despliegue como fichero versionado, docs/DESPLIEGUE.md. No hace falta que sea largo, tiene que ser seguible por cualquiera del equipo sin preguntar:

    Procedimiento de despliegue - detecciones
    
    1. Toda regla nueva o modificada entra por solicitud de cambio,
       nunca directo a la rama principal.
    2. El pipeline de CI (.github/workflows/ci-detecciones.yml) tiene
       que pasar en verde: sigma check sin errores, sin IDs duplicados,
       conversion correcta a cada backend afectado.
    3. Revision por pares obligatoria antes de fusionar (ver seccion de
       revision de este modulo). Quien revisa comprueba tambien que
       existe docs/ads/<nombre-de-la-regla>.md para la regla y que esta actualizado.
    4. Tras fusionar, el job de despliegue convierte la regla con
       sigma convert y publica la consulta resultante primero en el
       indice o la carpeta de reglas de staging, nunca directo a
       produccion.
    5. La regla vive en staging un minimo de 7 dias naturales. Quien la
       escribio revisa a diario si dispara y contra que.
    6. Pasado ese plazo sin ruido inesperado, se promociona a produccion
       cambiando su ubicacion (o su status, segun el backend) en un
       commit propio, para que quede en el historial como paso separado
       de la creacion original de la regla.
    7. Si una regla ya en produccion genera un problema (satura la cola
       de busquedas programadas, dispara en bucle), se desactiva de
       inmediato y solo despues se investiga. Si el propio despliegue
       introdujo el problema, se redespliega el commit anterior, no se
       parchea sobre lo que ya esta activo.
    8. Cada despliegue a produccion deja registrado el hash del commit
       desplegado en la propia busqueda o regla del SIEM, para poder
       responder en cualquier momento que version esta activa.
  7. Por último, escribe el fichero de flujo de trabajo .github/workflows/ci-detecciones.yml con el contenido ya mostrado en la sección de despliegue de este módulo, y verifica en tu propia cuenta de GitHub (con un repositorio de prueba, no en uno real de tu organización) que la solicitud de cambio se bloquea si introduces deliberadamente una regla con un id duplicado o con la condition mal escrita.

Preguntas frecuentes

¿Necesito un SIEM real conectado para hacer el ejercicio de este módulo?

No. sigma check y sigma convert corren en local sin ninguna conexión, y son justamente la capa de pruebas que no exige datos. Conectar el resultado a tu Wazuh del módulo 2 es un paso opcional de más, útil si quieres ver la consulta funcionando contra telemetría real, pero no hace falta para completar el ejercicio.

¿Añadir campos como owner a una regla Sigma rompe la conversión o la compatibilidad con SigmaHQ?

No. La propia especificación permite campos personalizados a nivel raíz, y pySigma los guarda en un diccionario propio (custom_attributes) sin que afecten a la conversión. Lo que sí conviene evitar es reutilizar el nombre de un campo oficial con otro significado (por ejemplo, un author con el nombre del equipo en vez de la persona autora), porque eso confunde a quien lea la regla más adelante, aunque la herramienta no lo rechace.

¿Sigue mereciendo la pena aprender contentctl si Splunk está apostando por Detection Studio?

Depende de si tu organización paga Splunk Enterprise Security. Detection Studio es una función integrada de esa capa comercial; contentctl sigue siendo gratuito, de código abierto, y sigue empaquetando el mismo repositorio de contenido que Splunk actualiza con regularidad. Si trabajas con la versión gratuita de Splunk, o quieres entender cómo se prueba contenido de detección contra datos reales sin pagar ES, contentctl sigue siendo la referencia práctica.

¿Qué diferencia hay entre un filtro de Sigma y una excepción con fecha de caducidad?

El filtro es el mecanismo técnico: un fichero YAML que Sigma sabe interpretar y que recorta el resultado de una o varias reglas sin tocar su lógica. La excepción es la decisión documentada detrás de ese filtro (quién la aprobó, por qué, hasta cuándo), y no forma parte de la especificación de Sigma: es una convención que añade el propio repositorio, como el registro exceptions.yml de este módulo, para que el filtro no quede huérfano de contexto ni se vuelva permanente por descuido.

¿Los 300 días que usa SigmaHQ para promocionar una regla de experimental a test son una norma que debería copiar?

Son el criterio operativo de ese proyecto concreto, automatizado en su propio flujo de trabajo mensual, no una cifra que la especificación de Sigma imponga. Tu organización puede fijar un plazo distinto según su propio volumen de cambios y de falsos positivos; lo que sí merece la pena copiar es la idea de fondo, que el paso de un estado a otro se dispare solo (con confirmación humana final) en vez de depender de que alguien se acuerde de revisarlo a mano.

¿Qué hago si la ayuda de una herramienta dice que una función «no está implementada todavía» pero al ejecutarla funciona?

Confía en lo que observas ejecutando la herramienta tú mismo, no en una frase suelta de un texto de ayuda que puede llevar tiempo sin actualizarse, como pasa con sigma check --help. Antes de construir un pipeline sobre cualquier comando, ejecútalo contra un caso que debería fallar y otro que debería pasar, y confirma que el código de salida y el mensaje son los que esperas. Es la misma disciplina de «verifica en tu entorno» de todo este curso, aplicada a la propia herramienta de pruebas.