De código a cobertura (Parte 4): cazando a SOAPHound - El patrón (!FALSE)

La historia hasta ahora

En 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.

La Parte 3 presentó los SDFlags (Security Descriptor Flags), un parámetro LDAP oculto que cambia la forma en que los controladores de dominio procesan y registran las consultas, lo que permite a los atacantes enumerar permisos mientras evaden la detección basada en firmas.

Hoy descubrimos un cuarto desafío que cambia radicalmente cómo pensamos la detección LDAP: consultas que se transforman por completo antes de registrarse.

Si has estado creando reglas de detección basadas en firmas de herramientas conocidas, estás a punto de descubrir por qué ciertos patrones de enumeración son invisibles para tu SIEM. Esto involucra una herramienta llamada SOAPHound, un atributo LDAP inexistente y una transformación que ocurre antes de que se escriban tus registros.

El problema: la consulta (!soaphound=*) nunca aparece en tus registros: se convierte en (! (FALSE)) mediante la optimización LDAP, y la mayoría de los defensores nunca han visto este patrón antes.

El descubrimiento

Mientras probaba varias herramientas de enumeración de AD para detectar patrones, me aseguré de incluir SOAPHound de FalconForce. A diferencia de las herramientas basadas en LDAP de las Partes 1 a 3, SOAPHound usa Active Directory Web Services (ADWS) en el puerto 9389, comunicándose mediante protocolo SOAP/XML. Esta arquitectura hace que el Event 1644 muestre Client: [::1] (localhost), porque ADWS actúa como proxy local de la consulta LDAP. Este proxy localhost dificulta la atribución.

Al examinar el código fuente, encontré algo inusual:

Figura 1: Construcción de la consulta LDAP de SOAPHound que muestra el patrón codificado ldapquery = "(!soaphound=*)" en los distintos modos de recolección.

Esa consulta (!soaphound=*) llamó la atención de inmediato. "soaphound" no es un atributo LDAP válido en ningún esquema de Active Directory. No está en la referencia de atributos de Microsoft, y no pasaría la validación del esquema.

Entonces, ¿por qué usarlo? Y lo más importante: ¿qué sucede cuando consultas un atributo que no existe?

Probando la consulta misteriosa

Configuré un laboratorio con el registro del Event ID 1644 habilitado (usando la configuración de la Parte 1) y ejecuté la consulta directamente:

# Test the SOAPHound query directly using ADSI

$searcher = [adsisearcher]"(!soaphound=*)"

$searcher.PageSize = 1000  # Ensure we get all results

$results = $searcher.FindAll()

Write-Host "Query: (!soaphound=*)"

Write-Host "Results: $($results.Count) objects"

Salida:

Query: (!soaphound=*)

Results: 311 objects

¡La consulta funcionó! Devolvió todos los objetos de mi dominio de prueba (311 objetos). Pero esto no debería ser posible: estamos consultando un atributo que no existe.

La revelación del registro de eventos

Cuando revisé el Event ID 1644 para ver cómo se había registrado esta consulta, encontré algo inesperado:

Internal event: A client issued a search operation with the following options.

Client:

[fe80::f196:f23f:d7bf:b6c30]:56005

Starting node:

DC=marvel,DC=local

Filter:

( !  (FALSE) )

Search scope:

subtree

Attribute selection:

[all]

Server controls:

[none]

Visited entries:

3815

Returned entries:

334

User:

MARVEL\thanos

La consulta (!soaphound=*) había sido registrada como (! (FALSE)). ¡La firma de la herramienta había desaparecido por completo!

Entendiendo la transformación

Este comportamiento en realidad está documentado, aunque no es muy conocido. Según la especificación técnica de Active Directory de Microsoft, las condiciones de filtro no definidas (incluidas las referencias a atributos inexistentes) se evalúan como FALSE en lugar de como indefinidas.

Si bien la RFC 4511 especifica que los filtros con atributos no reconocidos deberían evaluarse como Undefined (lo que no devolvería resultados), la implementación de Active Directory se desvía del estándar al convertirlos en FALSE."

El desglose completo de la transformación

QUIÉN: El servicio LDAP del controlador de dominio realiza esta transformación

QUÉ: Cualquier consulta de un atributo inexistente con negación se convierte en (! (FALSE))

DÓNDE: La transformación ocurre del lado del servidor durante el análisis de la consulta, antes de la ejecución

CUÁNDO: Después de la validación del esquema pero antes de registrarse en el Event ID 1644

POR QUÉ: Optimización LDAP: el controlador de dominio simplifica la expresión por eficiencia

CÓMO: Este es el proceso exacto:


Figura 2: Proceso de transformación de la consulta LDAP que muestra cómo (!soaphound=*) se convierte en (! (FALSE)) mediante la optimización del lado del servidor. El controlador de dominio evalúa el atributo inexistente, devuelve FALSE y luego optimiza la negación.

Esto es brillante desde la perspectiva de un atacante: el nombre de la herramienta queda incrustado en la consulta como una tarjeta de presentación, pero desaparece antes de registrarse.

Verificando el patrón

Para confirmar que esto no era exclusivo de "soaphound", probé varios atributos inexistentes:


Figura 3: Probar varias consultas con atributos inexistentes demuestra que este es un comportamiento universal de LDAP, no específico de SOAPHound. Cada consulta devuelve todos los objetos del dominio mientras se registra como (! (FALSE)).

Cada consulta de atributo inexistente produjo resultados y patrones de registro idénticos. Este es un comportamiento universal, no específico de SOAPHound.

SOAPHound real vs. consultas de prueba: la conexión con SDFlags

Cuando capturé la ejecución real de SOAPHound y la comparé con nuestras consultas de prueba, surgió una diferencia importante que se conecta con la Parte 3:

Consulta de prueba (sin atributos específicos)

Filter: ( !  (FALSE) )

Attribute selection: [all]

Server controls: [none]

User: MARVEL\thanos

Ejecución real de SOAPHound

Filter: ( !  (FALSE) )

Attribute selection:

name,sAMAccountName,cn,dNSHostName,objectSid,objectGUID,primaryGroupID,distinguishedName,lastLogonTimestamp,pwdLastSet,servicePrincipalName,

description,operatingSystem,sIDHistory,nTSecurityDescriptor,userAccountControl,whenCreated,lastLogon,displayName,title,homeDirectory,

userPassword,unixUserPassword,scriptPath,adminCount,member,msDS-Behavior-Version,msDS-AllowedToDelegateTo,gPCFileSysPath,gPLink,gPOptions,objectClass

Server controls: SDFlags:0x7

User: MARVEL\loki

SOAPHound usa consistentemente SDFlags:0x7 en todos los modos de enumeración basados en LDAP (buildcache, bhdump, certdump), incluso cuando no solicita descriptores de seguridad. Esto parece ser un comportamiento codificado de forma fija en su implementación de ADWS. Esto difiere significativamente de SharpHound (Parte 3), que usa SDFlags:0x4 o 0x5 solo cuando se necesita para la enumeración de descriptores de seguridad.

Para la detección de SOAPHound, la combinación del patrón FALSE + SDFlags:0x7 + la lista de atributos ofrece el indicador más sólido. SDFlags:0x7 solicita Owner (0x1) + Group (0x2) + DACL (0x4) = 0x7, es decir, todo excepto SACL. Esto es significativo porque SOAPHound lo solicita incluso en el modo buildcache, donde no pide nTSecurityDescriptor, lo que confirma que es un comportamiento codificado de forma fija.

El equipo de FalconForce explicó este requisito en su blog: "Al principio, nuestros intentos de recuperar los atributos nTSecurityDescriptor de los objetos fallaron por errores de permisos... Para evitar la limitación anterior y aun así consultar nTSecurityDescriptor, hay que usar un control LDAP para especificar que no se desea el SACL. El control es LDAP_SERVER_SD_FLAGS_OID. Como resultado, agregamos ese control a nuestras solicitudes de EnumerationContext, lo que permitió recuperar correctamente los atributos nTSecurityDescriptor a través de ADWS."

Esto explica por qué SOAPHound codifica de forma fija SDFlags:0x7. Sin este control, los usuarios sin privilegios no pueden recuperar descriptores de seguridad en absoluto. Sin embargo, nuestras pruebas revelaron que SOAPHound usa esta bandera incluso cuando no solicita nTSecurityDescriptor (como en el modo buildcache), lo que confirma que se implementa como un ajuste general y no como lógica condicional.

Los atributos en el Event ID 1644 coinciden exactamente con el código fuente: es una firma perfecta.

Patrones reales de SOAPHound

Cuando ejecuté SOAPHound real en el laboratorio, esto fue lo que apareció en el Event ID 1644:

Patrón 1: Enumeración de objetos de dominio

Client: [::1]:59435
Starting node: DC=marvel,DC=local

Filter: ( ! (FALSE) )

Search scope: subtree

Attribute selection: 

name,sAMAccountName,cn,dNSHostName,objectSid,objectGUID,primaryGroupID,

distinguishedName,lastLogonTimestamp,pwdLastSet,servicePrincipalName,

description,operatingSystem,sIDHistory,nTSecurityDescriptor,userAccountControl,

whenCreated,lastLogon,displayName,title,homeDirectory,userPassword,

unixUserPassword,scriptPath,adminCount,member,msDS-Behavior-Version,

msDS-AllowedToDelegateTo,gPCFileSysPath,gPLink,gPOptions,objectClass

Server controls: SDFlags:0x7

Visited entries: 3815

Returned entries: 334

Patrón 2: Enumeración de plantillas de certificados

Client: [::1]:59435
Starting node: CN=Configuration,DC=marvel,DC=local

Filter: ( ! (FALSE) )

Search scope: subtree

Attribute selection:

name,displayName,nTSecurityDescriptor,objectGUID,dNSHostName,

certificateTemplates,cACertificate,msPKI-Minimal-Key-Size,

msPKI-Certificate-Name-Flag,msPKI-Enrollment-Flag,msPKI-Private-Key-Flag,

pKIExtendedKeyUsage,pKIOverlapPeriod,pKIExpirationPeriod,objectClass

Server controls: SDFlags:0x7

Visited entries: 147

Returned entries: 147

El patrón (! (FALSE)) apareció de forma constante en todos los tipos de enumeración, siempre acompañado de listas extensas de atributos que indican un reconocimiento completo.

Creación de una detección confiable

Ahora que entendemos la transformación, construyamos reglas de detección que tengan en cuenta los patrones que descubrimos:

Regla SIGMA 1: Detección de SOAPHound de alta fidelidad

detection:

    selection_base:

        EventID: 1644

        Message|contains: '(! (FALSE))'

    # Atributos específicos de SOAPHound

    selection_soaphound_attrs:

        Message|contains|all:

            - 'msDS-Behavior-Version'

            - 'gPCFileSysPath'

            - 'gPLink'

            - 'gPOptions'

            - 'nTSecurityDescriptor'

    

    # Atributos de alto riesgo

    selection_sensitive:

        Message|contains:

            - 'userPassword'

            - 'unixUserPassword'

            - 'msDS-AllowedToDelegateTo'

    

    condition: selection_base and (selection_soaphound_attrs or selection_sensitive)

Regla SIGMA 2: Detección de SOAPHound con SDFlags:0x7 esperado

detection:

    selection_base:

        EventID: 1644

        Message|contains: 

            - '(! (FALSE))'

            - 'SDFlags:0x7'

    selection_attrs:

        Message|contains|all:

            - 'nTSecurityDescriptor'

            - 'msDS-Behavior-Version'

    condition: all of selection_*

Comparación: firmas de herramientas de enumeración en las cuatro partes

Así es como aparecen las distintas herramientas de enumeración de AD en los retos de detección que exploramos:

Herramienta

Parte 1: transformación de OID

Parte 2: espacios en blanco

Parte 3: SDFlags

Parte 4: negación FALSE

Firma única

Impacket GetUserSPNs

userAccountControl&512

Espaciado estándar

Ninguno

No

Filtro UAC bit a bit

SharpHound

userAccountControl&512

Varía

SDFlags:0x4 or 0x5

No

SDFlags + consultas de grupo

SOAPHound

No

Estándar

Always SDFlags:0x7

(! (FALSE))

FALSE + SDFlags:0x7 + atributos

Scripts personalizados

Varía

A menudo irregular

Rara vez

Posible

Patrones inconsistentes

Matriz de prioridad de detección

Patrón

Confianza

Tasa de falsos positivos

Cobertura de detección

FALSE + SDFlags:0x7 + atributos

Crítica (99%)

Casi nula

Específica de SOAPHound

Negación FALSE sola

Alta (85%)

Muy baja

Cualquier consulta de atributo inexistente

SDFlags:0x4/0x5 + consultas masivas

Alta (80%)

Baja

SharpHound

Transformaciones de OID

Media (70%)

Media

La mayoría de las herramientas .NET

Anomalías de espacios en blanco

Baja (40%)

Alta

Varias herramientas

Conclusiones clave

  1. Las transformaciones de LDAP son de múltiples capas: Ahora hemos visto cuatro tipos de transformación diferentes, cada uno en una etapa de procesamiento distinta.
  2. El patrón de negación FALSE es engañoso de forma única: A diferencia de los otros patrones, donde algo cambia, aquí la consulta original desaparece por completo.
  3. La documentación es importante: Las especificaciones de Microsoft y los RFC explican estos comportamientos, convirtiendo transformaciones misteriosas en patrones detectables.
  4. Los patrones simples pueden ser muy confiables: El patrón (! (FALSE)) es lo bastante inusual como para ser un indicador fuerte con pocos falsos positivos.
  5. La detección completa requiere entender todas las capas: Los atacantes pueden combinar técnicas de las cuatro partes, lo que exige una detección en capas.
  6. El comportamiento de SDFlags de SOAPHound difiere del de SharpHound:
    • SharpHound: usa de forma consistente SDFlags:0x7 en todas las operaciones
    • SOAPHound: SDFlags:0x7 aparece solo cuando se solicita explícitamente nTSecurityDescriptor en la lista de atributos
    • Cuando SOAPHound usa los atributos predeterminados [all], no aparece ningún SDFlags
  7. Desglose de SDFlags:0x7: Owner (0x1) + Group (0x2) + DACL (0x4) = todo excepto SACL
  8. El patrón de detección confiable para SOAPHound:
    • Negación de filtro FALSE (!(FALSE))
    • Cliente [::1] (localhost/IPv6)
    • Lista de atributos específica que coincide con los patrones de SOAPHound
    • SDFlags:0x7 cuando nTSecurityDescriptor está en la lista de atributos (depende del modo)

Conclusión

La transformación de (!soaphound=*) a (! (FALSE)) representa la culminación de nuestro recorrido por la detección de LDAP. Combinado con SDFlags de la Parte 3, ahora tenemos una imagen completa de cómo SOAPHound evade la detección tradicional mientras deja artefactos forenses distintivos.

Esta serie ha revelado cuatro capas de transformación de LDAP:

  1. Sintaxis (Parte 1): conversión de OID a operación bit a bit
  2. Formato (Parte 2): normalización de espacios en blanco
  3. Control (Parte 3): modificación de SDFlags
  4. Lógica (Parte 4): optimización de atributos inexistentes

SOAPHound combina de forma astuta técnicas de las Partes 3 y 4, usando tanto SDFlags para la enumeración de permisos como la negación FALSE para la evasión de firmas. Sin embargo, de forma paradójica, esta combinación crea un patrón aún más distintivo para la detección.

El patrón (! (FALSE)) es quizás la técnica de evasión más elegante que hemos encontrado: explota un comportamiento documentado de LDAP para crear una firma que desaparece mediante la optimización. Pero ahora que entendemos la transformación, se convierte en nuestro indicador más fuerte.

Recuerda: Tus adversarios están leyendo especificaciones y encontrando optimizaciones ingeniosas. Tu detección debe ser igual de sofisticada.

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