En el módulo anterior dejamos los campos ya normalizados: un mismo hecho, «se creó un proceso», con el mismo nombre de campo sin importar si venía de Sysmon, del registro de seguridad de Windows o de auditd. Ese trabajo resuelve un problema, pero no el que más tiempo consume en un SOC: escribir la lógica de detección en sí. Si tu organización tiene Wazuh en el laboratorio y Splunk en producción, y mañana evalúa un SIEM distinto como Elastic, escribir «detecta esto» tres veces, en tres sintaxis distintas, con tres motores que interpretan los comodines de forma diferente, es un trabajo que nadie quiere repetir cada vez que cambia de plataforma.
Sigma existe para que esa lógica se escriba una vez. Es un formato en YAML, pensado para describir un patrón sobre datos de log de forma neutral, y un conversor lo traduce después a la consulta real de cada motor. Lo que Sigma no hace, y conviene tenerlo claro antes de escribir la primera línea, es decidir por ti cómo se llaman tus campos: eso ya lo resolviste (o no) en el módulo anterior, con tus decoders y tu esquema de campos. Sigma asume que ese trabajo está hecho y construye encima.
Qué aprenderás
- Qué problema resuelve Sigma exactamente y qué queda fuera de su alcance (la normalización de campos, que ya viste).
- A leer y a escribir cada campo de una regla Sigma contra la especificación oficial: title, id, status y sus cinco valores válidos, logsource, detection y condition, falsepositives, level.
- La semántica exacta de listas y mapas dentro de detection, y cómo se combinan con el AND/OR/NOT de la condition.
- Los modificadores de campo uno a uno: contains, startswith, endswith, all, base64, base64offset, re, cidr y los de expansión, con un ejemplo real de cada uno.
- Qué son las reglas de correlación y los filtros globales de la especificación, con los siete tipos de correlación que reconoce hoy y su sintaxis.
- A convertir una regla con pySigma y sigma-cli a la consulta real de un backend, y por qué la misma regla puede no servir de nada en un SIEM y funcionar perfectamente en otro.
- Cómo está organizado el repositorio de SigmaHQ, cómo se busca una regla ya escrita y qué implica de verdad el estado de una regla.
- Qué exige la licencia DRL 1.1 si tu organización publica un panel que muestra coincidencias de reglas ajenas.
Qué resuelve Sigma y qué no
La especificación de Sigma vive en el repositorio SigmaHQ/sigma-specification, cuyo contenido se publica en dominio público. La versión que uso en todo este módulo es la 2.1.0: el propio documento la fecha el 2 de agosto de 2025 en su cabecera y en su sección de historial, aunque GitHub etiquetó y publicó el release correspondiente algo más tarde, el 12 de septiembre de 2025 (dos fechas distintas, ambas reales, según se mire el contenido del fichero o los metadatos del release). Comprobado hoy contra el repositorio, sigue siendo la versión vigente: no hay ninguna v2.2 ni posterior en la lista de tags.
El propio README del repositorio de reglas lo resume con una comparación que se sostiene bien: Sigma es para los ficheros de log lo que Snort es para el tráfico de red y YARA para los ficheros. Lo que Sigma no hace (y aquí es fácil llevarse una decepción si no lo tienes claro desde el principio) es adivinar dónde vive tu campo «línea de comandos» ni cómo se llama en tu índice concreto. Esa traducción de nombres, la vimos en detalle en el módulo anterior con la taxonomía logsource y los decoders de Wazuh, y vuelve a aparecer aquí bajo otro nombre: pipeline. Sin un pipeline que sepa mapear los nombres de campo genéricos de Sigma a los reales de tu SIEM, la conversión falla o, peor, genera una consulta que nunca encuentra nada. Escribir esa lógica es, en el fondo, el paso de quien cierra alertas a quien las diseña, justo lo que describe la guía sobre cómo trabajar en un SOC.
Anatomía de una regla, campo a campo
Toda regla Sigma sigue la misma estructura general. Solo tres atributos son obligatorios: title, logsource y detection. El resto son opcionales en la especificación, aunque como verás más adelante el propio repositorio de SigmaHQ es bastante más exigente que la especificación con lo que acepta.
title
id [opcional]
related [opcional]
- id {id-de-la-regla}
type {derived|obsolete|merged|renamed|similar}
name [opcional]
taxonomy [opcional]
status [opcional]
description [opcional]
license [opcional]
references [opcional]
author [opcional]
date [opcional]
modified [opcional]
logsource
category [opcional]
product [opcional]
service [opcional]
detection
{identificador-de-busqueda}
{lista-de-cadenas o mapa}
condition
fields [opcional]
falsepositives [opcional]
level [opcional]
tags [opcional]
scope [opcional]
Y este es el ejemplo mínimo que trae la propia especificación, de Florian Roth, para ilustrar una regla completa y funcional:
title: Whoami Execution
description: Detects a whoami.exe execution
references:
- https://speakerdeck.com/heirhabarov/hunting-for-privilege-escalation-in-windows-environment
author: Florian Roth
date: 2019-10-23
logsource:
category: process_creation
product: windows
detection:
selection:
Image: 'C:WindowsSystem32whoami.exe'
condition: selection
level: high
La tabla siguiente recoge los atributos que define la especificación, con su uso exacto tal como lo declara cada sección del documento oficial.
| Atributo | Uso | Qué contiene |
|---|---|---|
| title | Obligatorio | Resumen de qué detecta la regla, máximo 256 caracteres |
| id / related | Opcional | UUID v4 único, y relación con otras reglas: derived, obsolete, merged, renamed, similar |
| name / taxonomy | Opcional | name: identificador legible y único para referenciar la regla desde una correlación, en vez del id; taxonomy: convención de nombres de campo que usa la regla (por defecto, sigma) |
| status | Opcional | Madurez: stable, test, experimental, deprecated, unsupported |
| description | Opcional | Explicación de la actividad detectada, hasta 65.535 caracteres |
| license | Opcional | Licencia de la propia regla, como identificador SPDX |
| author | Opcional | Autor o autores, separados por coma |
| references | Opcional | Fuentes de las que se derivó la regla |
| date / modified | Opcional | Fecha de creación y de última modificación, formato AAAA-MM-DD |
| logsource | Obligatorio | category, product y service que fijan el origen del log |
| detection | Obligatorio | Los identificadores de búsqueda y la condition que los combina |
| fields | Opcional | Campos de log que conviene mostrar al analista al revisar la coincidencia |
| falsepositives | Opcional | Lista de falsos positivos conocidos |
| level | Opcional | Criticidad: informational, low, medium, high, critical |
| tags | Opcional | Etiquetas con espacio de nombres, normalmente técnicas de ATT&CK |
| scope | Opcional | Restringe la regla a un subconjunto de máquinas: server, workstation… |
El campo status y sus cinco valores
La especificación define status con exactamente cinco valores posibles, sin margen para inventarse un sexto:
stable: la regla se considera lista para producción o para un panel.test: mayormente estable, puede necesitar algún ajuste según el entorno.experimental: puede dar falsos positivos o ser ruidosa, pero también puede señalar algo interesante.deprecated: sustituida o cubierta por otra regla, enlazada mediante el camporelated.unsupported: no se puede usar tal cual está (formato de correlación antiguo, campos a medida).
Aquí hay un matiz que la especificación general no impone pero que sí impone el repositorio de SigmaHQ: toda regla nueva tiene que arrancar con status: experimental, según sus convenciones de repositorio. Nadie entra directo por la puerta de stable: ese estado se gana con tiempo, como verás en el apartado sobre el repositorio.
logsource: el mismo contrato que ya conoces
El campo logsource se explicó a fondo en el módulo anterior, así que aquí solo el recordatorio necesario: category agrupa por función (cortafuegos, web…), product selecciona todos los canales de un producto (windows incluye Security, System, Application y los más recientes) y service afina a un subconjunto concreto (sshd en Linux, el canal Security en Windows). Los tres se escriben en minúscula, con guion bajo en vez de espacio, y pueden combinarse o usarse solos.
La sección detection en profundidad
Dentro de detection vive uno o varios identificadores de búsqueda (search-identifiers, normalmente llamados selection, filter o keywords por convención, aunque el nombre es libre) y, siempre, una condition que decide cómo se combinan.
Listas y mapas: el AND/OR que no se escribe
Cada identificador de búsqueda es una lista o un mapa, y cada estructura tiene su propia semántica implícita, sin que tengas que escribir «and» u «or» en ningún sitio:
- Una lista de cadenas se une con OR. Un identificador con
'EVILSERVICE'y'svchost.exe -n evil'coincide si aparece cualquiera de las dos, buscando en el mensaje completo del log (esto es una búsqueda por keyword, ver más abajo). - Un mapa (pares campo: valor) se une con AND.
EventLog: Securityjunto aEventID: 4769exige que se cumplan las dos condiciones a la vez. - Cuando el valor de un campo dentro de un mapa es a su vez una lista, esa lista interna se une con OR.
EventID: [517, 1102]significa «EventID 517 o EventID 1102». - Una lista de mapas une cada mapa con OR entre sí (y, dentro de cada mapa, sus campos con AND).
Este ejemplo, tomado también de la especificación, junta las cuatro reglas en un caso concreto: coincide con el registro de eventos «Security», con el Event ID 4769, con TicketOptions 0x40810000 y con TicketEncryption 0x17, todo a la vez, porque los cuatro campos viven en el mismo mapa:
detection:
selection:
EventLog: Security
EventID: 4769
TicketOptions: '0x40810000'
TicketEncryption: '0x17'
condition: selection
Keywords, comodines, valores especiales y null
Cuando el identificador de búsqueda es directamente una lista sin nombres de campo, se busca el texto en el evento completo (esto se conoce como búsqueda por keywords). En este ejemplo de la especificación, la regla coincide si el evento entero contiene «event::clear» o «event::drop»:
detection:
mimikatz_keywords:
- 'event::clear'
- 'event::drop'
condition: mimikatz_keywords
Los comodines son * (cualquier número de caracteres) y ? (exactamente un carácter). Sigma trata todos los valores como cadenas insensibles a mayúsculas por defecto (las expresiones regulares con el modificador re son la excepción: son sensibles a mayúsculas salvo que añadas el submodificador i).
Dos valores merecen mención aparte porque su comportamiento no es intuitivo. Una cadena vacía se escribe ''. Un valor nulo se escribe null, y la especificación insiste en un punto que se pasa por alto con facilidad: null no puede mezclarse dentro de una lista de valores de un mismo campo, porque no comparte tipo con nada más. Si necesitas expresar «este campo no es null», hace falta una selección aparte y negarla en la condition:
detection:
selection:
EventID: 4738
filter:
PasswordLastSet: null
condition: selection and not filter
Cuando lo que te interesa es solo si un campo existe en el evento (con independencia de su valor, incluido un valor vacío), se usa el modificador exists:
detection:
selection:
EventID: 4738
PasswordLastSet|exists: true
condition: selection
La condition: sintaxis y precedencia
La condition combina los identificadores de búsqueda con estas construcciones, todas tomadas literalmente de la especificación:
keywords1 or keywords2
1 of them
all of them
all of selection*
1 of selection* and keywords
1 of selection* and not 1 of filter*
keywords and not filters
selection1 and (keywords1 or keywords2)
1 of them y all of them operan sobre todos los identificadores de búsqueda que no empiecen por guion bajo (por convención, los que empiezan por _ quedan fuera). La especificación desaconseja all of them de forma explícita: obliga a quien reutilice tu regla más adelante a incluir cualquier identificador nuevo en ese «todos», sin poder filtrar. El patrón 1 of selection* o all of filter*, con comodines sobre el propio nombre del identificador, se recomienda en su lugar porque deja margen a quien filtre después.
La precedencia de operadores, de menos a más vinculante, es: or, and, not, x of identificador y, por encima de todo, los paréntesis. Nada exótico si ya escribiste lógica booleana alguna vez, pero conviene tenerlo memorizado porque una condition mal parentizada es de los fallos más difíciles de detectar a simple vista en una revisión de código.
Modificadores de campo, uno a uno
Los modificadores se añaden tras el nombre del campo con una barra vertical, y se pueden encadenar: fieldname|mod1|mod2: valor, aplicados en ese orden. La especificación distingue dos tipos: los modificadores de transformación cambian el valor (y a veces la lógica AND/OR) y funcionan con cualquier backend; los modificadores de tipo cambian cómo se debe interpretar el valor (por ejemplo, como expresión regular) y el backend concreto tiene que soportarlos explícitamente o falla la conversión.
| Modificador | Para qué sirve | Ejemplo |
|---|---|---|
| contains | Rodea el valor con comodines: coincide en cualquier posición del campo | Description|contains: 'Test executable' |
| startswith | Exige el valor al principio del campo | User|startswith: 'adm_' |
| endswith | Exige el valor al final del campo | Image|endswith: 'example.exe' |
| all | Cambia el OR por defecto de una lista de valores por AND | CommandLine|contains|all: ['process ', 'call ', 'create '] |
| base64 | Codifica el valor completo en Base64 antes de comparar | CommandLine|base64: 'IEX' |
| base64offset | Genera las tres variantes de un valor que puede aparecer Base64 desplazado 0, 1 o 2 bytes dentro de una cadena mayor | CommandLine|base64offset|contains: '::FromBase64String' |
| re | Trata el valor como expresión regular PCRE, con soporte limitado a comodines, anclas, cuantificadores, clases y alternancia | CommandLine|re: '^cmd.exe /c .{200,}$' |
| cidr | Trata el valor como rango CIDR, en IPv4 o IPv6 | DestinationIp|cidr: '10.0.0.0/8' |
| exists | Comprueba solo si el campo está presente, no su valor | PasswordLastSet|exists: true |
| expand | Activa la sustitución de un placeholder (%Servers%…) durante la conversión | Image|expand: '%Windir%System32*.exe' |
| windash | Genera todas las permutaciones de guion, barra y guiones largos usados como marcador de opción en Windows | CommandLine|contains|windash: '-noprofile' |
Por qué existe base64offset
Este es el modificador que menos se entiende a la primera, así que merece un ejemplo trabajado con una herramienta real en vez de una explicación abstracta. Cuando un atacante codifica un fragmento de comando en Base64 y lo mete dentro de otro comando más largo, la posición exacta del fragmento dentro de la cadena completa cambia cómo se codifican los tres bytes que quedan a caballo entre el fragmento y lo que lo rodea (Base64 trabaja en bloques de tres bytes de entrada). Buscar el texto ya codificado tal cual solo encuentra la coincidencia si el fragmento cae exactamente alineado a un múltiplo de tres bytes desde el principio de la cadena, algo que con texto arbitrario alrededor casi nunca ocurre. base64offset resuelve esto generando, de una vez, las tres codificaciones posibles según el fragmento empiece desplazado 0, 1 o 2 bytes.
Para verlo en la práctica instalé sigma-cli en esta misma sesión y convertí una regla real que usa este modificador (la analizo entera más abajo). El fragmento de la regla es este:
detection:
selection:
- CommandLine|base64offset|contains: '::FromBase64String'
Y esta es la salida real de sigma convert para esa única línea, contra el backend de Splunk:
CommandLine="*OjpGcm9tQmFzZTY0U3RyaW5n*" OR CommandLine="*o6RnJvbUJhc2U2NFN0cmluZ*" OR CommandLine="*6OkZyb21CYXNlNjRTdHJpbm*"
Tres cadenas Base64 distintas para el mismo texto «::FromBase64String», una por cada desplazamiento posible, todas unidas con OR y ya envueltas en comodines por el contains encadenado. Nadie escribe esas tres cadenas a mano en cada regla; para eso está el modificador.
Reglas de correlación: la parte que casi nadie conoce
La mayoría de quien trabaja con Sigma conoce bien la anatomía de una regla suelta y no ha tocado nunca las reglas de correlación, documentadas en un fichero aparte, la Sigma Correlation Rules Specification, también en su versión 2.1.0. Sirven para enlazar varias reglas Sigma ya existentes en un comportamiento compuesto: «más de X intentos fallidos contra el mismo host» o «las alertas A y B juntas en la misma ventana son sospechosas aunque no lo sean por separado». El enfoque anterior, mezclar agregaciones y un operador «near» dentro de la condition de una regla normal, la especificación lo declara obsoleto: acoplaba la lógica de un evento suelto con la de una relación entre varios.
Una regla de correlación es un documento YAML propio (recomendado con el prefijo de fichero mr_), con su title obligatorio, su id, y una sección correlation con un type obligatorio. La especificación define hoy exactamente siete tipos, ni uno más:
| Tipo | Qué mide | Atributos obligatorios, aparte de type y rules |
|---|---|---|
| event_count | Cuántos eventos ocurren en la ventana, por cada grupo | group-by, timespan, condition |
| value_count | Cuántos valores distintos toma un campo, por cada grupo | group-by, timespan, condition, field (dentro de condition) |
| temporal | Que varias reglas coincidan dentro de la ventana, en cualquier orden | group-by, timespan |
| temporal_ordered | Igual que temporal, pero exige el orden indicado en rules | group-by, timespan |
| value_sum | Que la suma de un campo numérico cumpla el límite | group-by, timespan, condition, field |
| value_avg | Que la media de un campo numérico cumpla el límite | group-by, timespan, condition, field |
| value_percentile | En qué percentil cae el valor observado (la mediana es el percentil 50) | group-by, timespan, condition, field |
La condition de una correlación es un mapa con exactamente un criterio: gt, gte, lt, lte, eq o neq, y admite combinar dos (gt y lte juntos) para definir un rango. Este ejemplo, tomado literalmente de la especificación, dispara cuando hay cien o más intentos de acceso fallidos contra el mismo equipo en una hora:
title: Many failed logins
id: 0e95725d-7320-415d-80f7-004da920fc11
correlation:
type: event_count
rules:
- 5638f7c0-ac70-491d-8465-2a65075e0d86
group-by:
- ComputerName
timespan: 1h
condition:
gte: 100
Y este otro encadena dos reglas ya definidas (por su name, no por su id, para que se lea mejor) exigiendo que aparezcan en ese orden dentro de la hora, algo que temporal a secas no garantiza:
correlation:
type: temporal_ordered
rules:
- many_failed_logins
- successful_login
group-by:
- User
timespan: 1h
El formato de timespan es siempre número seguido de una letra en minúscula: s segundos, m minutos, h horas, d días (una hora y media se escribe 90m, no hay una unidad compuesta). Y un detalle que conviene revisar antes de dar por buena una correlación en producción: la especificación obliga al backend de conversión a lanzar un error, no una advertencia silenciosa, si la correlación pide algo que el motor de destino no puede hacer (por ejemplo, agregar un recuento pero no poder filtrarlo después). Si tu conversión termina sin quejarse, es una señal razonable de que el backend cubrió toda la lógica pedida.
Filtros globales
La especificación de filtros resuelve un problema distinto y más aburrido: aplicar la misma exclusión de entorno a varias reglas a la vez, sin editar cada una. El caso típico es un script de GPO legítimo que dispara varias reglas de detección genéricas: en vez de tocar cada regla, un fichero de filtro (prefijo recomendado mf_) referencia las reglas afectadas por su id y añade su propia condición de exclusión. Este es el ejemplo de la especificación:
title: Filter Administrator account
description: The valid administrator account start with adm_
logsource:
category: process_creation
product: windows
filter:
rules:
- 6f3e2987-db24-4c78-a860-b4f4095a7095
- df0841c0-9846-4e9f-ad8a-7df91571771b
selection:
User|startswith: 'adm_'
condition: selection
Un filtro no tiene level ni status propio, porque no describe una alerta: enriquece y recorta las que ya existen.
De la regla a la consulta: pySigma, sigma-cli y los pipelines
Todo lo anterior es texto YAML sin capacidad de hacer nada por sí solo. Para convertirlo en una consulta real hace falta pySigma, la librería de Python que sustituyó a la antigua herramienta sigmac, y su interfaz de línea de comandos, sigma-cli. Comprobado en PyPI en esta misma sesión, las versiones actuales son pysigma 1.4.0 (publicada el 27 de junio de 2026) y sigma-cli 3.1.0 (7 de julio de 2026). El README de ambos proyectos todavía lleva la insignia «Status: pre-release», una etiqueta que llama la atención dado lo asentada que está la herramienta en el ecosistema (la usa, entre otros, Security Onion).
La instalación es la que documenta el propio proyecto:
python -m pip install sigma-cli
sigma-cli por sí solo no trae ningún backend: hay que instalarlos como plugins, y el propio comando te dice cuáles hay disponibles:
sigma plugin list
sigma plugin install splunk
Este es el listado real de backends que trae registrados el directorio de plugins de pySigma, consultado en esta sesión: 31 backends y otros 5 paquetes de pipelines independientes, cada uno con un estado propio (stable, testing o devel) que no tiene nada que ver con el status de una regla Sigma, es la madurez del propio conversor.
| Backend | Convierte a | Estado |
|---|---|---|
| splunk | SPL y consultas de tstats sobre modelos de datos | stable |
| elasticsearch | Lucene, ES|QL y EQL | stable |
| opensearch | Lucene y reglas de alerta de OpenSearch | stable |
| kusto | KQL, para Microsoft Advanced Hunting | stable |
| crowdstrike | CrowdStrike Logscale | stable |
| sentinelone | Deep Visibility | stable |
| qradar / ibm-qradar-aql | AQL de IBM QRadar | stable |
| loki | LogQL de Grafana Loki | stable |
| powershell | Consultas PowerShell | testing |
| stix | STIX 2.0 y taxonomías STIX Shifter | devel |
No hay un backend de Wazuh en ese directorio (lo comprobé buscando el listado completo): el motor de reglas de Wazuh no lo alimenta pySigma directamente. Lo que sí hay, y encaja con lo que instalaste en el laboratorio del módulo 2, es un backend de OpenSearch, y el indexador de Wazuh está construido justamente sobre OpenSearch. Es el camino que sigo en el ejercicio de más abajo.
Por qué la misma regla falla en un SIEM y funciona en otro
Convertir sin decirle a sigma-cli qué pipeline usar produce, con el backend de Splunk, este error real (lo generé en esta sesión intentando convertir sin especificar ningún pipeline):
Processing pipeline required by backend! Define a custom pipeline or choose a predefined one.
Get all available pipelines for splunk with:
sigma list pipelines splunk
If you never heard about processing pipelines you should get familiar with them
(https://sigmahq-pysigma.readthedocs.io/en/latest/Processing_Pipelines.html).
If you know what you're doing add --without-pipeline to your command line to suppress this error.
El pipeline es exactamente el mismo concepto que el mapeo de campos de Wazuh o los field sets de ECS que viste en el módulo anterior, aplicado en el momento de la conversión en vez de en el momento de la ingesta. Para demostrarlo convertí la misma regla del ejemplo de base64offset con dos pipelines distintos, contra el mismo backend de Splunk. Con el pipeline sysmon:
sigma convert -t splunk -p sysmon proc_creation_win_powershell_base64_frombase64string.yml
EventID=1 CommandLine="*OjpGcm9tQmFzZTY0U3RyaW5n*" OR CommandLine="*o6RnJvbUJhc2U2NFN0cmluZ*" OR CommandLine="*6OkZyb21CYXNlNjRTdHJpbm*" OR CommandLine IN ("*OgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcA*", "*oAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnA*", "*6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZw*")
Y con el pipeline windows-audit, que mapea la misma regla al evento de seguridad de Windows en vez de a Sysmon:
sigma convert -t splunk -p windows-audit proc_creation_win_powershell_base64_frombase64string.yml
EventID=4688 CommandLine="*OjpGcm9tQmFzZTY0U3RyaW5n*" OR CommandLine="*o6RnJvbUJhc2U2NFN0cmluZ*" OR CommandLine="*6OkZyb21CYXNlNjRTdHJpbm*" OR CommandLine IN ("*OgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcA*", "*oAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnA*", "*6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZw*")
Misma regla, mismo YAML de origen, y la única diferencia entre las dos consultas es EventID=1 frente a EventID=4688: exactamente los dos Event IDs que ya conoces de los módulos 3 y 4 para creación de proceso. Si tu índice solo tiene telemetría de Sysmon y conviertes con windows-audit, la consulta es sintácticamente correcta y no va a encontrar nunca nada, porque busca un Event ID que tu índice no contiene. Ese es, en la práctica, el motivo real por el que «la misma regla funciona en un SIEM y no en otro»: casi nunca es un problema del formato Sigma, es el pipeline equivocado para la telemetría que de verdad tienes.
El repositorio SigmaHQ: organización, estado y búsqueda
El repositorio público de reglas, SigmaHQ/sigma, se organiza en carpetas de primer nivel que separan tipos de reglas muy distintos entre sí, algo que el propio README explica y que confirmé contando los ficheros .yml de cada una en esta sesión:
| Carpeta | Qué contiene | Reglas .yml hoy |
|---|---|---|
| rules/ | Detección genérica, agnóstica de amenaza concreta, organizada por product (windows, linux, macos…) y dentro por categoría | 3.142 |
| rules-emerging-threats/ | Amenazas puntuales: campañas APT, día cero, malware concreto | 470 |
| rules-threat-hunting/ | Punto de partida para cazar, no pensadas para alertar directamente | 140 |
| rules-placeholder/ | Reglas que solo cobran sentido final al convertirse, con placeholders | 17 |
| rules-compliance/ | Violaciones de marcos como CIS Controls, NIST o ISO 27001 | 3 |
| deprecated/ | Reglas retiradas, mantenidas por referencia histórica | 167 |
| unsupported/ | Reglas que no se pueden usar tal cual con el formato actual | 87 |
Dentro de rules/, el nombre de fichero sigue una convención documentada en el propio proyecto: prefijo por categoría (proc_creation_win_ para creación de proceso en Windows, registry_set_ para escrituras de registro, aws_ o azure_ para reglas de nube) seguido de una descripción corta separada por guiones bajos. No es una norma decorativa: sirve para que grep -r "tu_termino" rules/ encuentre algo útil sin tener que abrir carpeta por carpeta. La propia guía de contribución recomienda esta búsqueda por texto, junto con GitHub Code Search, grep.app y un buscador dedicado, Sigma Search Engine, antes de escribir una regla nueva que pueda duplicar una ya existente.
El estado de una regla también decide qué paquete de descarga la incluye. El proyecto distribuye tres paquetes principales, según documenta su propio Releases.md: Core solo incluye reglas de nivel high o critical con estado test o stable (la recomendación para quien empieza, porque no debería dar muchos falsos positivos); Core+ añade el nivel medium; Core++ suma también las reglas experimental. El propio documento explica que llegar a test o stable exige, aproximadamente, medio año de antigüedad sin falsos positivos reportados: no es una etiqueta que ponga el autor a su gusto. El último paquete de reglas publicado, comprobado en esta sesión, corresponde al tag r2026-07-01, del 9 de julio de 2026.
Lo que la especificación permite y lo que SigmaHQ exige
Aquí está el matiz que más confunde a quien lee solo la especificación general: SigmaHQ impone, por sus propias convenciones, que status, description, references, author, date, tags, falsepositives y level sean obligatorios, aunque la especificación general los declare opcionales. También prohíbe algo que parece razonable a primera vista y no lo es: enlazar directamente a una página de attack.mitre.org dentro de references. La técnica va en tags, con su propio espacio de nombres attack.*; el hueco de references se reserva para la fuente externa (un blog, una charla, un tuit) de la que salió la idea de la regla.
La licencia DRL 1.1: qué exige de verdad
Todo el contenido de SigmaHQ/sigma se publica bajo la Detection Rule License 1.1 (DRL 1.1), con identificador SPDX DRL-1.1. El texto completo cabe en pocos párrafos, así que merece leerse literal en vez de fiarse de un resumen de tercera mano. Este es el fragmento que de verdad importa a quien vaya a usar estas reglas en su organización:
«If you share the Rules (including in modified form), you must retain the following if it is supplied within the Rules: 1. identification of the authors(s) (‘author’ field) of the Rule… 2. a URI or hyperlink to the Rule set or explicit Rule to the extent reasonably practicable… If you use the Rules (including in modified form) on data, messages based on matches with the Rules must retain the following if it is supplied within the Rules: 1. identification of the authors(s) (‘author’ field) of the Rule…»
El permiso de fondo es amplio, prácticamente equivalente a una licencia tipo MIT: usar, copiar, modificar, publicar y hasta vender copias de las reglas, sin restricción de ámbito comercial. La condición que acompaña a ese permiso tiene dos caras, y la segunda es la que se pasa por alto con más frecuencia. No solo hay que citar al autor cuando compartes el fichero de la regla en sí: la licencia obliga también a citar al autor en cualquier mensaje o vista que se genere a partir de una coincidencia de esa regla. Si tu SOC monta un panel que muestra «alerta disparada por la regla X» con el nombre de la regla pero sin decir quién la escribió, ese panel no cumple la DRL 1.1 en la lectura literal del texto, aunque nadie te vaya a mandar un burofax por ello. Para una organización que use reglas de SigmaHQ tal cual, la forma más simple de cumplir es guardar el campo author como un campo más del evento indexado y mostrarlo en el mismo panel.
Una regla real, analizada línea a línea
Esta es la regla que ya usé más arriba para explicar base64offset, copiada tal cual del repositorio oficial. Su autor es Florian Roth, de Nextron Systems, y el fichero vive en rules/windows/process_creation/proc_creation_win_powershell_base64_frombase64string.yml (enlace permanente al commit que comprobé en esta sesión).
title: PowerShell Base64 Encoded FromBase64String Cmdlet
id: fdb62a13-9a81-4e5c-a38f-ea93a16f6d7c
status: test
description: Detects usage of a base64 encoded "FromBase64String" cmdlet in a process command line
references:
- Internal Research
author: Florian Roth (Nextron Systems)
date: 2019-08-24
modified: 2023-04-06
tags:
- attack.stealth
- attack.t1140
- attack.execution
- attack.t1059.001
logsource:
category: process_creation
product: windows
detection:
selection:
- CommandLine|base64offset|contains: '::FromBase64String'
# UTF-16 LE
- CommandLine|contains:
- 'OgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcA'
- 'oAOgBGAHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnA'
- '6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZw'
condition: selection
falsepositives:
- Unknown
level: high
Va por partes. status: test significa que la regla ya pasó del estado inicial obligatorio (experimental) pero todavía no llegó a stable. description empieza por «Detects», tal como pide la convención de SigmaHQ. En references aparece el texto literal «Internal Research», no una URL: la propia convención de SigmaHQ pide una referencia pública «si es posible», y esta regla es un ejemplo real de que ese «si es posible» se toma en serio, no toda regla del repositorio tiene detrás un enlace externo.
El campo modified (2023-04-06) es posterior a date (2019-08-24): la regla lleva casi cuatro años en el repositorio con al menos un ajuste documentado. En tags, la primera etiqueta es attack.stealth, y aquí hay un detalle que solo se ve comparando fuentes: el apéndice de tags de la propia especificación de Sigma, en su lista de tácticas del namespace attack, todavía enumera defense-evasion como táctica única y no incluye ninguna entrada para «stealth». Esta regla, pese a eso, ya usa attack.stealth, que se corresponde con la división de Defense Evasion en Stealth y Defense Impairment que trajo la matriz de MITRE ATT&CK en su versión 19.1, publicada el 28 de abril de 2026. El etiquetado de las reglas de SigmaHQ sigue los datos de ATT&CK directamente, más rápido de lo que se actualiza el texto del apéndice de la especificación: un recordatorio de que hasta la documentación oficial tiene su propio desfase.
En detection, selection es una lista de dos mapas de un solo campo cada uno, unidos por OR: coincide si el CommandLine contiene, en Base64 con cualquiera de los tres desplazamientos posibles, el texto ::FromBase64String (la variante en texto plano de una sola cadena, para procesos con el flag -EncodedCommand de PowerShell), o si contiene directamente alguna de las tres cadenas literales que corresponden a la misma técnica cifrada en UTF-16LE, la codificación que PowerShell usa por dentro para sus cadenas de texto. Dos formas de codificar el mismo indicador, cubiertas en la misma regla porque un atacante puede usar cualquiera de las dos. condition: selection no necesita nada más porque solo hay un identificador. falsepositives: Unknown es, según la propia convención de SigmaHQ, el valor correcto cuando el autor no conoce ninguno concreto (distinto de Unlikely, reservado para cuando el autor sí espera que no los haya). level: high cierra la regla: una coincidencia aquí merece revisión pronta, no una cola de baja prioridad.
Errores frecuentes al escribir reglas
Tres fallos explican, en mi experiencia leyendo reglas de otras personas, la mayoría de las que dejan de funcionar al cabo de unos meses o que nunca llegan a disparar.
El primero es pegarse al nombre de la herramienta en vez de al comportamiento. Una regla que busca Image|endswith: 'mimikatz.exe' deja de servir en cuanto alguien renombra el binario, algo tan simple como copy mimikatz.exe m.exe. La regla del apartado anterior es un buen contraejemplo: no busca ningún nombre de herramienta, busca el patrón de comando que produce la técnica en sí (una llamada a FromBase64String codificada), sea cual sea el binario que la ejecute.
El segundo es escribir rutas completas que se rompen con cualquier variación del entorno. Image: 'C:UsersjgarciaAppDataLocalTempevil.exe' solo funciona para ese usuario, esa unidad y ese perfil concreto; cambia la letra de unidad, el idioma de la instalación (que traduce «Usuarios» en vez de «Users» en algunas versiones) o el nombre de usuario, y la regla deja de coincidir sin que nadie note el motivo. El modificador endswith sobre la parte final del camino que sí es estable (AppDataLocalTempevil.exe) aguanta mucho mejor ese tipo de variación.
El tercero es olvidar o equivocar el campo de logsource. Una regla con category: process_creation pero sin product: windows puede acabar convirtiéndose contra un pipeline pensado para Linux si el backend no tiene forma de distinguir la plataforma por otro medio, y el resultado es, otra vez, una consulta que no falla con un error visible, simplemente no encuentra nunca lo que se supone que debería.
Ejercicio: escribe, convierte y comprueba tu propia regla
Vas a escribir una regla Sigma desde cero para un comportamiento de descubrimiento muy concreto, convertirla al lenguaje del laboratorio que montaste en el módulo 2 y comprobarla contra la telemetría que ya grabaste allí.
El comportamiento es la enumeración de la pertenencia a grupos locales sensibles de Windows, visible en los eventos 4798 («se enumeró la pertenencia de un usuario a los grupos locales») y 4799 («se enumeraron los miembros de un grupo local con seguridad habilitada»), documentados por Microsoft en su árbol archivado (ms.date 2021-09-07). Ambos eventos comparten campos, comprobados en la documentación oficial: SubjectUserName (quién pregunta), TargetUserName (el usuario o, curiosamente, también el grupo, cuando es el 4799: el propio XML de ejemplo de Microsoft usa ese mismo nombre de campo para un valor como «Administrators») y CallerProcessName (qué proceso preguntó). La propia guía de recomendaciones de Microsoft para el evento 4799 sugiere vigilar de cerca los grupos locales críticos: administradores integrados, operadores de copia de seguridad y similares. Esta técnica corresponde a ATT&CK T1069.001, «Permission Groups Discovery: Local Groups», dentro de la táctica Discovery.
-
Escribe la regla. Guárdala como
local_group_enum_criticos.yml:title: Enumeracion de grupos locales criticos via 4798 o 4799 id: b357d982-7b39-4968-ab39-14ee37b55110 status: experimental description: Detecta la enumeracion de la pertenencia a grupos locales sensibles (Administrators, Backup Operators, Remote Desktop Users) a traves de los eventos 4798 y 4799 del canal Security de Windows author: Curso SOC cursosdeciberseguridad.com date: 2026-07-27 references: - https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/auditing/event-4799 logsource: product: windows service: security detection: selection: EventID: - 4798 - 4799 TargetUserName|contains: - 'Admin' - 'Backup Operators' - 'Remote Desktop Users' condition: selection fields: - SubjectUserName - CallerProcessName - TargetUserName falsepositives: - Herramientas de administracion e inventario que consultan de forma habitual la pertenencia a estos grupos level: medium -
Valídala con sigma-cli antes de convertirla. Esta es la salida real, generada en esta sesión:
sigma check local_group_enum_criticos.ymlParsing 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. -
Instala el backend de OpenSearch (el motor sobre el que corre el indexador de Wazuh) y su pipeline de mapeo de canales de Windows, y convierte la regla:
python -m pip install pysigma-backend-opensearch pysigma-pipeline-windows sigma convert -t opensearch_lucene -p windows-logsources local_group_enum_criticos.ymlEsta es la salida real de esa conversión:
Channel:Security AND ((EventID:(4798 OR 4799)) AND (TargetUserName:(*Admin* OR *Backup Operators* OR *Remote Desktop Users*))) -
Comprueba la consulta contra la telemetría que ya tienes grabada. En el laboratorio del módulo 2 descargaste
discovery_local_user_or_group_windows_security_4799_4798.evtxdesde la carpeta Discovery de EVTX-ATTACK-SAMPLES y lo importaste en tu Wazuh, bajo el patrón de índicewazuh-archives-*. Abre Discover en el panel, con ese mismo patrón de índice activo. La barra de búsqueda usa DQL por defecto: cambia a sintaxis Lucene desde el selector de idioma de consulta que hay junto a la barra (documentado en la guía oficial de OpenSearch Dashboards) y pega la consulta generada en el paso anterior. Verifica en tu entorno cuántos documentos coinciden: no puedo darte aquí un número de resultados que no haya obtenido yo mismo contra tu instancia con tus datos importados, y el número exacto no cambia la lección. -
Compara lo que acabas de hacer con lo que habrías hecho sin Sigma: escribir la misma búsqueda directamente en la sintaxis Lucene de tu Wazuh de hoy, y tener que reescribirla de cero, a mano, el día que tu organización cambie a Splunk o a Elastic. La regla YAML del paso 1 no cambia nunca por ese motivo; solo cambia el pipeline y el backend que eliges en el paso 3.
Preguntas frecuentes
¿Sigma normaliza mis campos de log por mí?
No. Sigma da por hecho que tus campos ya están normalizados y construye la lógica de detección encima de esa base, mediante el pipeline de conversión. Si tus decoders o tu esquema de campos están mal, ninguna regla Sigma, por bien escrita que esté, va a arreglarlo. Ese trabajo previo es el que viste en el módulo anterior.
¿Necesito sigma-cli para escribir reglas, o solo para convertirlas?
Solo para convertirlas y para validarlas con sigma check. Una regla Sigma es un fichero YAML de texto plano: se escribe con cualquier editor, sin instalar nada. sigma-cli entra en juego cuando quieres traducir esa regla a la consulta real de un motor, o comprobar que no tiene errores de sintaxis ni de condition.
¿Qué diferencia práctica hay entre status: experimental y status: test?
Ninguna regla puede saltarse experimental al entrar en el repositorio de SigmaHQ: es el punto de partida obligatorio para toda regla nueva. Pasar a test y después a stable no depende de que el autor decida que ya está madura, depende del tiempo que lleve en uso sin que se reporten falsos positivos, según el propio criterio que usa el proyecto para montar el paquete Core de descarga.
¿Puedo usar una regla de SigmaHQ en un panel comercial de mi empresa sin pedir permiso?
Sí, la DRL 1.1 permite el uso comercial sin pedir permiso previo. La condición no es económica, es de atribución: tienes que conservar la identificación del autor tanto si compartes la regla en sí como si el panel muestra una coincidencia generada a partir de ella. En la práctica, lo más simple es guardar el campo author junto al resto de metadatos de cada alerta e imprimirlo donde se vea la coincidencia.
¿Qué pasa si mi SIEM no tiene un backend de pySigma?
La regla sigue siendo perfectamente legible y sigue documentando tu lógica de detección, pero no hay conversión automática: hay que traducirla a mano a la sintaxis de consulta de tu motor, revisando el logsource y los modificadores uno a uno. El directorio oficial de plugins cubre más de treinta motores distintos hoy, así que antes de traducir a mano merece la pena comprobar primero si ya existe un backend, aunque esté en estado testing o devel.
¿Una regla de correlación sustituye a las reglas normales que enlaza?
No, las complementa. Una regla de correlación no genera resultados por sí sola: referencia una o varias reglas Sigma normales (por su id o por su name) y añade la lógica de agrupación y de ventana temporal sobre las coincidencias de esas reglas. El atributo generate, opcional, decide si esas reglas referenciadas se convierten también como reglas independientes o si solo se genera la consulta de correlación.
