De código a cobertura (parte 3): SDFlags, el campo de registro que había estado ignorando y que desbloqueó la detección de rutas de ataque

La historia hasta ahora

En la Parte 1, aprendimos que las herramientas de reconocimiento LDAP de Impacket usan filtros basados en OID que se transforman en operaciones bit a bit en los registros del Event ID 1644, lo que rompe nuestras reglas de detección basadas en coincidencia de cadenas.

La Parte 2 reveló cómo las variaciones de espacios en blanco en el formato de los filtros LDAP pueden hacer que consultas idénticas se registren de forma distinta, creando otro punto ciego de detección.

Ahora, en la Parte 3, voy a compartir un tipo de historia diferente. No sobre un problema que investigaba activamente, sino sobre algo frente a lo que había pasado de largo en mis registros durante semanas. Algo que ni siquiera estaba observando porque estaba tan concentrado en filtros y atributos.

Todo comenzó el día en que finalmente noté un campo llamado "SDFlags".

Lo que no estaba observando

Después de las Partes 1 y 2, había estado totalmente enfocado en los filtros y atributos LDAP. Estaba construyendo reglas de detección basadas en patrones de filtros, rastreando qué atributos consultaban los atacantes, correlacionando búsquedas. Los registros del Event 1644 eran mi territorio.

Pero solo estaba observando partes específicas de esos registros:

  • Campo Filter - Esto es lo que analicé en la Parte 1
  • Campo Attributes - Esto era lo importante para las reglas de detección
  • Entradas Visited/Returned - Para entender el alcance de la consulta

Entonces, un día, mientras revisaba registros, algo finalmente registró en mi visión periférica:


Figura 1: Consulta de enumeración de grupos de SharpHound capturada en el Event ID 1644, que muestra SDFlags:0x5 (Owner + DACL) y el filtro sAMAccountType característico para todos los tipos de grupo.

SDFlags: 0x5

Había visto este campo docenas de veces antes. Pero había estado tan concentrado en el filtro y los atributos que mi cerebro simplemente... lo había pasado por alto. Era ruido de registro. Metadatos adicionales. No lo que estaba buscando.

Hasta el día en que realmente lo vi.

"¿Qué es SDFlags?"

La primera investigación: ¿Cuándo aparece?

Una vez que noté SDFlags, ya no pude dejar de verlo. Empecé a buscar patrones:

# When does SDFlags appear?

Get-WinEvent -FilterHashtable @{

    LogName = 'Directory Service'

    ID = 1644

    StartTime = (Get-Date).AddDays(-7)

} | Where-Object { $_.Message -match 'SDFlags' } | 

Measure-Object

# Result: 127 events with SDFlags in the past week


# What attributes are requested when SDFlags appear?

Get-WinEvent -FilterHashtable @{

    LogName = 'Directory Service'

   ID = 1644

    StartTime = (Get-Date).AddDays(-7)

} | Where-Object { $_.Message -match 'SDFlags' } | 

ForEach-Object {

    if ($_.Message -match "Attribute selection:\s*\r?\n(.+?)\r?\n") {

        $matches[1]

    }

} | Group-Object | Sort-Object Count -Descending | Select-Object -First 5

# Resultado: el 100% de las consultas de SDFlags incluyen "nTSecurityDescriptor"

Patrón descubierto: SDFlags SOLO aparece cuando las consultas solicitan el atributo nTSecurityDescriptor.

Bien, entonces SDFlags y nTSecurityDescriptor están conectados. Pero, ¿qué ES nTSecurityDescriptor? ¿Y por qué debería importarme?

Esto se estaba convirtiendo en una investigación más profunda de lo que esperaba.

Siguiendo el OID: ¿Qué es SDFlags?

Tomé el OID del campo "Controls" y busqué: 1.2.840.113556.1.4.801

La especificación MS-ADTS de Microsoft, sección 3.1.1.3.4.1.11: LDAP_SERVER_SD_FLAGS_OID

Según la especificación, este control se usa para especificar qué información del descriptor de seguridad se debe devolver.

Entonces está filtrando qué datos se devuelven. Pero, ¿filtrando QUÉ exactamente?

Los valores de la máscara de bits:

  • 0x1 = OWNER_SECURITY_INFORMATION
  • 0x2 = GROUP_SECURITY_INFORMATION
  • 0x4 = DACL_SECURITY_INFORMATION
  • 0x8 = SACL_SECURITY_INFORMATION

Entonces 0x5 = 0x1 + 0x4 = Owner + DACL.

Por qué importa SDFlags: el problema de permisos de SACL

Aquí hay algo que no entendí al principio: ¿por qué las herramientas necesitan usar SDFlags?

La respuesta son los permisos. Como explica FalconForce en su publicación de blog sobre SOAPHound, cuando consultas nTSecurityDescriptor sin SDFlags, Active Directory intenta devolver los cuatro componentes por defecto (Owner, Group, DACL y SACL). Pero la mayoría de las cuentas no tienen permiso para leer la SACL.

Aquí está el problema: cuando AD no puede devolver todos los componentes solicitados, no devuelve nada.

SDFlags resuelve esto al permitirte especificar exactamente qué componentes quieres. Al solicitar solo Owner + DACL (0x5) o solo DACL (0x4), las herramientas evitan el problema de permisos de la SACL y recuperan con éxito los datos del descriptor de seguridad.

Por eso las herramientas de rutas de ataque deben usar SDFlags. Sin esto, las consultas fallan silenciosamente para las cuentas sin privilegios.

Cayendo por la madriguera: ¿qué es realmente nTSecurityDescriptor?

Volví a la documentación de Microsoft y encontré MS-DTYP, sección 2.4.6: SECURITY_DESCRIPTOR. La estructura contiene cuatro componentes: OwnerSid, GroupSid, DACL (lista de control de acceso discrecional) y SACL (lista de control de acceso del sistema).

Necesitaba ver esto en acción. Hora del laboratorio:

Figura 2: Consultar nTSecurityDescriptor revela quién tiene los permisos sobre el grupo Domain Admins. Esto es lo que buscan los atacantes: las relaciones de permisos que revelan rutas de ataque.

Mi grupo Domain Admins de laboratorio tenía permisos estándar esperados: System, Administrators, Domain Admins y Administradores Empresariales con GenericAll, además de algunos SIDs conocidos con ReadProperty.

Pero aquí está lo que me impactó: podía ver los permisos.

nTSecurityDescriptor contiene:

  • Quién tiene acceso (IdentityReference)
  • Qué puede hacer (ActiveDirectoryRights)
  • Si está permitido o denegado (AccessControlType)

En mi laboratorio, todo estaba configurado correctamente. Pero en un entorno de producción con años de cambios, modificaciones manuales y permisos heredados, ¿qué podrías encontrar?

  • Mesa de ayuda con WriteProperty en Domain Admins (puede agregar miembros)
  • Una cuenta de usuario normal que es Owner de un grupo privilegiado (puede modificar permisos)
  • Cuentas de servicio con GenericAll en grupos administrativos
  • Contratistas con derechos extendidos que no deberían tener

Esto es lo que las herramientas de rutas de ataque necesitan descubrir. Sin consultar nTSecurityDescriptor, no puedes ver estas relaciones de permisos. Sabrías:

  • Qué grupos existen
  • Quién es miembro de qué
  • Pero NO quién tiene permisos para modificar esos grupos

Esa última parte, los datos de permisos, es lo que hace posible descubrir rutas de ataque.

El momento "ajá": así es como funciona BloodHound

Pensé de inmediato en BloodHound. Lo había ejecutado antes en mi laboratorio y me mostraba rutas de ataque. Pero nunca entendí realmente CÓMO encontraba esas rutas.

Ahora tenía sentido.

Lo que BloodHound necesita para encontrar rutas de ataque:

  • ¿Quiénes son los usuarios? (consulta LDAP básica)
  • ¿Qué grupos existen? (consulta LDAP básica)
  • ¿Quién es miembro de qué? (atributo member)
  • ¿QUIÉN TIENE PERMISOS SOBRE QUÉ?: ¡esto es nTSecurityDescriptor!

Sin nTSecurityDescriptor, no puedes ver:

  • Que un grupo de mesa de ayuda puede modificar grupos privilegiados
  • Que el usuario X es Owner del grupo Y (y el Owner siempre puede modificar permisos)
  • Que una cuenta de servicio tiene GenericAll en objetos administrativos
  • Que usuarios aparentemente no relacionados tienen derechos de restablecimiento de contraseña

Esta es LA pieza crítica del descubrimiento de rutas de ataque. Necesitas las relaciones de permisos, no solo las de membresía.

Volví a revisar las consultas de SharpHound en mis registros. Cada una solicitaba nTSecurityDescriptor.

Recolección DCOnly de SharpHound:

Attributes: nTSecurityDescriptor, sAMAccountName, objectSid, ...

  SDFlags: 0x4

Enumeración de grupos de SharpHound:

  Attributes: nTSecurityDescriptor, sAMAccountName, objectSid, member, ...

  SDFlags: 0x5

Enumeración de plantillas de certificados de SharpHound:

  Attributes: nTSecurityDescriptor, distinguishedName, name, ...

  SDFlags: 0x5

TODAS consultan nTSecurityDescriptor. Porque sin eso, no puedes mapear quién tiene la capacidad de comprometer qué.

Entendiendo SDFlags: ¿por qué valores distintos?

Ahora que entendía qué era nTSecurityDescriptor, tenía otra pregunta: ¿por qué aparece SDFlags? ¿Y por qué los valores son distintos?

Después de observar SharpHound en acción, el patrón quedó claro: cada vez que solicitaba nTSecurityDescriptor, usaba el control SDFlags. La documentación de Microsoft dice que SDFlags permite especificar qué componentes del descriptor de seguridad recuperar: Owner, Group, DACL o SACL.

Revisé mis registros y encontré dos patrones distintos:

Patrón 1: SDFlags=0x4 (solo DACL)

Cuándo lo vi:

  • Enumeración general de usuarios
  • Consultas de objetos de computadora
  • Enumeración de objetos de dominio
  • Consultas de relaciones de confianza

Lo que preguntan: "¿Quién tiene permisos sobre estos objetos?"

Ejemplo de los registros:

Filter: (samAccountType=805306368)  # Computer accounts

Attributes: nTSecurityDescriptor, sAMAccountName, dNSHostName, ...

SDFlags: 0x4

Para las computadoras, la propiedad (Owner) generalmente no importa para las rutas de ataque. Solo necesitas saber "¿alguien tiene GenericAll o AllExtendedRights?"

Patrón 2: SDFlags=0x5 (Owner + DACL)

Cuándo lo vi:

  • Enumeración de grupos (¡el 100 % de las veces!)
  • Consultas de plantillas de certificados (¡el 100 % de las veces!)
  • Enumeración de objetos de alto valor

Lo que preguntan: "¿Quién tiene permisos sobre estos objetos Y quién es su Owner?"

Ejemplo de los registros:

Filter: (|(sAMAccountType=268435456)(sAMAccountType=268435457)...)  # Security groups

Attributes: nTSecurityDescriptor, sAMAccountName, objectSid, member, ...

SDFlags: 0x5

Esto tiene sentido si piensas en las rutas de ataque. En un entorno mal configurado, podrías encontrar algo así:

Group: "Domain Admins"

Members: captain, thor, thanos

DACL (lo que muestra SDFlags=0x4):

  - "Help Desk" tiene WriteProperty (puede agregar miembros)

Owner (lo que además muestra SDFlags=0x5):

  - "loki" (¡usuario normal!) es Owner del grupo

Rutas de ataque:

  • Ruta 1: cualquiera en Help Desk puede agregarse a sí mismo a Domain Admins
  • Ruta 2: loki es Owner del grupo, modifica la DACL, se otorga WriteProperty y se agrega a Domain Admins

Sin la información de Owner (0x5 la incluye, 0x4 no), ¡te perderías completamente la Ruta 2!

Por eso SharpHound usa 0x5 para los grupos. Ser Owner de un grupo es una ruta de ataque crítica que suele estar mal configurada.

Si SDFlags se usa por rendimiento, por convención o solo por cómo el desarrollador escribió el código no importa realmente para fines de detección. Lo que importa es el patrón:

  • Herramientas administrativas legítimas: consultan nTSecurityDescriptor SIN SDFlags
  • SharpHound: SIEMPRE usa SDFlags al consultar nTSecurityDescriptor

Esa diferencia lo hace detectable.

Confirmando en el código fuente

Nasreddine Bencherchali revisó el código fuente de SharpHound y encontró exactamente dónde se implementa SDFlags, y me envió los enlaces relevantes para confirmar lo que yo veía en los registros.

La bandera se establece en LdapConnectionPool.cs de SharpHoundCommon. En mis pruebas, distintos métodos de recolección produjeron valores diferentes: 0x4 para la enumeración general y 0x5 para los grupos.


Figura 3: LdapConnectionPool.cs muestra SDFlags configurado como Dacl | Owner (0x5) al solicitar descriptores de seguridad.

En mis pruebas, distintos métodos de recolección produjeron valores diferentes: 0x4 para enumeración general y 0x5 para grupos.

La oportunidad de detección

Después de finalmente notar SDFlags y entender qué significaba, me di cuenta de que había pasado por alto una firma de detección casi perfecta.

El uso de SDFlags es extremadamente raro en operaciones legítimas.

Revisé nuestro entorno:

  • Herramientas de administración legítimas: En mis pruebas, rara vez usan SDFlags (consultan objetos específicos, no enumeración masiva)
  • Software de respaldo: No usa SDFlags (no necesita datos de permisos)
  • Herramientas de monitoreo: No usan SDFlags (no necesitan descriptores de seguridad)

Lo único que encontré usando SDFlags:

  1. SharpHound (herramienta de rutas de ataque)
  2. Mis propias pruebas (después de aprender sobre esto)

Eso es todo.

El patrón de detección:

Enumeración masiva de LDAP (más de 100 objetos)

  + atributo nTSecurityDescriptor

  + control SDFlags (0x4 o 0x5)

= Descubrimiento de rutas de ataque en curso

Herramienta

Patrón de filtro

SDFlags

Atributos

SharpHound

Group sAMAccountType

0x5

nTSecurityDescriptor, member, ...

Scripts personalizados

Varían

Raro

Varían

Administradores legítimos

DN específico

Generalmente ninguno

Atributos básicos

Aún mejor, pude diferenciar herramientas: la combinación de SDFlags=0x5 + enumeración de grupos es ALTAMENTE específica de BloodHound/SharpHound.

Validación del patrón

Para confirmar esto, ejecuté SharpHound en mi laboratorio con diferentes métodos de recolección:


Prueba 1:
DCOnly (SharpHound.exe -c DCOnly):

Los registros del evento 1644 mostraron:

  • Múltiples consultas con SDFlags: 0x4 (usuarios, computadoras, dominios)
  • Múltiples consultas con SDFlags: 0x5 (grupos, plantillas de certificados)
  • TODAS las consultas solicitaron nTSecurityDescriptor


Prueba 2:
Recolección de grupos (SharpHound.exe -c Group):

Los registros del evento 1644 mostraron:

  • SDFlags constante: 0x5 para TODAS las consultas de grupos
  • Filtro: (|(sAMAccountType=268435456)(sAMAccountType=268435457)...)
  • Siempre incluyó nTSecurityDescriptor


Prueba 3: Comparación con actividad legítima de administrador

Les pedí a nuestros administradores de AD que realizaran tareas comunes mientras yo monitoreaba el evento 1644:


Actividad legítima:

Administrador usando ADUC para revisar los miembros de "Domain Admins":

  Filtro: (distinguishedName=CN=Domain Admins,CN=Users,DC=corp,DC=local)

  Atributos: member, description

  SDFlags: [None]

Administrador usando PowerShell para obtener miembros de grupo:

  Filtro: (sAMAccountName=Server Admins)

  Atributos: member, sAMAccountName

  SDFlags: [None]


Actividad de SharpHound:

Enumeración de grupos de SharpHound:

  Filtro: (|(sAMAccountType=268435456)(sAMAccountType=268435457)...)

  Atributos: nTSecurityDescriptor, member, sAMAccountName, objectSid

  SDFlags: 0x5

  Resultado: 847 grupos


La diferencia es obvia:

  • Legítimo: DN específico, atributos básicos, sin SDFlags
  • Herramienta de ataque: Enumeración masiva, atributos de seguridad, SDFlags presente

Creación de reglas de detección

Después de entender qué significa SDFlags y confirmar el patrón, creé reglas de detección basadas en esta investigación.

En mis pruebas, SharpHound usó de manera constante SDFlags 0x4 y 0x5. Otras herramientas de rutas de ataque pueden usar valores diferentes, lo cual exploraremos en la Parte 4.

Nota: Lo siguiente son extractos de lógica de detección que muestran los criterios clave de selección. Los nombres de campo usan Elastic Common Schema (ECS). Ajusta según tu SIEM. El filtro Source coincide con conexiones remotas IP:puerto, filtrando las operaciones locales del DC.

Enumeración de grupos de SharpHound (SDFlags 0x5):

detection:

  selection_base:

    event.code: 1644

  selection_filter_samtypes:

    winlog.event_data.LDAPFilter|contains|all:

      - '(sAMAccountType=268435456)'

      - '(sAMAccountType=268435457)'

      - '(sAMAccountType=536870912)'

      - '(sAMAccountType=536870913)'

  selection_scope_subtree:

    winlog.event_data.SearchScope: 'subtree'

  selection_sdflags_owner_dacl:

    winlog.event_data.SearchFlags:

      - 'SDflags:0x5;'

  selection_src:

    winlog.event_data.Source|re: '^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$'

  condition: selection_base and selection_filter_samtypes and selection_scope_subtree and selection_sdflags_owner_dacl and selection_src

Conclusiones clave

  1. A veces las señales más valiosas son las que todavía no estás mirando. Estaba tan enfocado en filtros y atributos que SDFlags no se me registró hasta que un día por fin lo hizo.
  2. nTSecurityDescriptor es LA pieza crítica para el descubrimiento de rutas de ataque. Sin esto, herramientas como BloodHound no pueden funcionar. Esto lo convierte en un punto de detección confiable.
  3. SDFlags revela la intención. La combinación cuenta la historia:
    • Un objeto + nTSecurityDescriptor = un administrador revisando permisos
    • Objetos masivos + nTSecurityDescriptor + SDFlags = descubrimiento de rutas de ataque
  4. La información de Owner importa para los grupos. SDFlags=0x5 (frente a 0x4) solicita específicamente datos de Owner, por eso SharpHound lo usa para la enumeración de grupos. La propiedad de grupo suele estar mal configurada y crea rutas de ataque.
  5. Los administradores legítimos no usan SDFlags. En nuestro monitoreo de producción, solo SharpHound y herramientas de investigación de seguridad lo usaron. Establece una línea base de tu entorno antes de implementar.

Conclusión: presta atención a lo que estás ignorando

Llevaba semanas revisando los registros del evento 1644. Analizando filtros, rastreando atributos y creando reglas de detección. Y todo el tiempo, SDFlags estuvo ahí mismo en los registros. Simplemente no lo veía.

El día que por fin noté ese campo, todo cambió. Lo que empezó como "Espera, ¿qué es eso?" se convirtió en:

  • Entender cómo funciona nTSecurityDescriptor
  • Darme cuenta de que esto es LA base del descubrimiento de rutas de ataque
  • Reconocer la oportunidad de detección en SDFlags
  • Crear reglas de detección de alta confianza
  • Encontrar un patrón que solo aparece durante la enumeración de rutas de ataque

Esto es lo que realmente es la ingeniería de detección: no solo analizar aquello en lo que te enfocas, sino detenerte de vez en cuando a preguntarte: "¿Qué más hay en estos registros a lo que no le estoy prestando atención?".

La próxima vez que estés metido de lleno en una investigación, tómate un momento para mirar los campos que has estado pasando por alto. Podrías encontrar la oportunidad de detección que no sabías que estabas buscando.

Gracias a Jonathan Johnson, Charlie Clark, Anton Ovrutsky, Matt Anderson, Tyler Bohlmann, Lindsey O'Donnell-Welch, Michael Haag, Nasreddine Bencherchali, y Adam Bienvenu por su ayuda en la revisión de esta publicación.

También te podría interesar