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
- 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.
- 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.
- La documentación es importante: Las especificaciones de Microsoft y los RFC explican estos comportamientos, convirtiendo transformaciones misteriosas en patrones detectables.
- 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.
- 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.
- 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
- Desglose de SDFlags:0x7: Owner (0x1) + Group (0x2) + DACL (0x4) = todo excepto SACL
- 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:
- Sintaxis (Parte 1): conversión de OID a operación bit a bit
- Formato (Parte 2): normalización de espacios en blanco
- Control (Parte 3): modificación de SDFlags
- 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.