De código a cobertura (parte 5A): cuando todas tus detecciones fallan: el punto ciego de ADWS

Introducción: La detección que no estaba ahí

Pensé que tenía cubierto el reconocimiento de Active Directory. Después de cuatro partes de esta serie, había construido detecciones para:

  • Parte 1: Transformación de OID: cómo los filtros LDAP de Impacket se convierten en operaciones bit a bit en los registros
  • Parte 2: La pesadilla de los espacios en blanco: construcción de reglas Sigma que manejan el caos de formato
  • Parte 3: SDFlags: el parámetro LDAP oculto que revela BloodHound
  • Parte 4: El patrón (!FALSE): cómo se transforman las consultas de SOAPHound y cómo detectarlas

Esta parte es diferente. No hay código de atacante que analizar. El actor de amenazas usó PowerShell nativo. Esta es la historia de por qué eso bastó para evadirlo todo.

Las reglas Sigma estaban analizando los filtros del evento 1644. Estaba seguro de mí mismo.

Luego revisé los registros de una intrusión real.

El comando que lo cambió todo

El actor de amenazas había enumerado con éxito todo un entorno de Active Directory. Cada objeto de computadora. Cada propiedad. Descriptores de seguridad. Membresías de grupos. Nombres principales de servicio (SPN). Todo.

El comando estaba ahí mismo en los registros de PowerShell:

geT-aDcompUTER -Filter {eNaBLed -eq $TruE} -PROPErTieS *

Ninguna de mis detecciones se activó.

El atacante extrajo más de 500 objetos de computadora con propiedades completas y descriptores de seguridad, y se fue sin dejar rastro. Mis detecciones de las partes 1 a 4 fueron completamente ciegas a esto.

Ofuscación de mayúsculas y minúsculas. ¡Eso era todo! "¡No puede ser en serio!" Eso fue lo que evadió meses de trabajo de ingeniería de detección.

Pero esto fue lo que realmente me molestó: la ofuscación de mayúsculas y minúsculas ni siquiera era la razón por la que funcionó.

Incluso si hubieran escrito un Get-ADComputer perfectamente normal sin ninguna ofuscación, mis detecciones tampoco se habrían activado. La mezcla de mayúsculas y minúsculas era irrelevante, solo una evasión adicional sobre un punto ciego cuya existencia yo desconocía.

Esa comprensión me llevó a semanas de investigación: ¿por qué la enumeración de AD mediante PowerShell, ofuscada o no, era completamente invisible para todo lo que había construido?

Por qué falló la detección: el punto ciego de ADWS

Para corregir mi brecha de detección, necesitaba entender exactamente cómo PowerShell se comunica con Active Directory y por qué es invisible para la detección LDAP tradicional. Esto es lo que aprendí por las malas:

PowerShell no usa LDAP a través de la red.

Cuando ejecutas Get-ADComputer desde una estación de trabajo, PowerShell no establece una conexión LDAP al puerto 389 o 636. En cambio, usa Active Directory Web Services (ADWS), un servicio web basado en protocolo SOAP/XML que funciona en el puerto 9389 con cifrado TLS.

El tráfico es completamente invisible para la detección LDAP basada en red:


Figura 1: El punto ciego de ADWS. PowerShell nunca toca el puerto 389; usa protocolo SOAP cifrado en el puerto 9389, y para cuando se convierte en LDAP, la IP del cliente es localhost.

El detalle arquitectónico crítico

Cuando ADWS consulta Active Directory en nombre de una sesión remota de PowerShell, realiza esas consultas como localhost (::1 o 127.0.0.1) en el controlador de dominio. La IP original del cliente se registra en eventos específicos de ADWS (1138/1139), pero la consulta LDAP real (evento 1644) muestra localhost.

Esto genera tres problemas de detección:

  1. Los sensores LDAP de red son ciegos - La consulta nunca toca el puerto 389/636
  2. El evento 1644 muestra localhost - Parece actividad interna del sistema, no enumeración remota
  3. La IP real del atacante está oculta - Enterrada en los registros LDAP como [::1]

El evento 1644 muestra:

Client: [::1]:57132 ← ¡Localhost, no la IP del atacante!

User: DOMAIN\attacker

Filter: ( & ( ! (userAccountControl&2) ) (objectClass=computer) ... )

Para mi lógica de detección, esto parecía completamente normal: localhost hablando consigo mismo con una cuenta de dominio válida.

La pista falsa de la ofuscación de mayúsculas y minúsculas

El atacante usó ofuscación de mayúsculas y minúsculas (geT-aDcompUTER), tratando de evadir el registro de línea de comandos de PowerShell, los patrones de registro de bloques de script y las herramientas de seguridad basadas en coincidencia de cadenas.

Pero era irrelevante. PowerShell no distingue entre mayúsculas y minúsculas, y para cuando la consulta llega al controlador de dominio, se traduce a LDAP estándar:

PowerShell: geT-aDcompUTER -Filter {eNaBLed -eq $TruE}

↓

Capa de traducción de ADWS

↓

LDAP: ( & ( ! (userAccountControl&2) ) (objectClass=computer) )

Sin rastro de la ofuscación de mayúsculas y minúsculas. Sin rastro de la sintaxis de PowerShell. Solo LDAP limpio y optimizado.

La ofuscación logró evadir las detecciones de endpoint, pero ese no era el verdadero problema. El verdadero problema era que yo no tenía ninguna estrategia de detección para ADWS en absoluto.

La investigación que llevó hasta aquí

Después de este incidente, pasé semanas en el laboratorio:

  1. Habilitando el registro de ingeniería de campo en los controladores de dominio
  2. Capturando tráfico con Wireshark en un puerto espejo observando el puerto 9389
  3. Ejecutando cada comando de enumeración que se me ocurrió
  4. Correlacionando eventos entre los ID de evento 1138, 1139, 1166, 1167 y 1644
  5. Entendiendo la arquitectura de ADWS - Cómo PowerShell realmente se comunica con AD

Esta publicación de blog es el resultado de esa investigación. No es teórica; son lecciones aprendidas de una falla de detección real, análisis de registros real y pruebas reales en mi entorno de laboratorio MARVEL.LOCAL.

Esta es la parte 5A: cómo aprendí a detectar lo que no podía ver.

Qué hace diferente la detección de ADWS

ADWS requiere un enfoque de detección fundamentalmente distinto al de las partes 1 a 4. La siguiente tabla compara ambos flujos de datos lado a lado:


Partes 1–4: Detección de LDAP en la red

Parte 5A: Correlación de múltiples eventos basada en endpoint

Flujo

[Script de Python del atacante]

↓

Puerto 389/636 (LDAP/LDAPS)

↓

Captura de paquetes de red

↓

Analizar el filtro LDAP y evaluarlo

↓

Alerta de detección

[PowerShell Get-ADComputer]

↓

Puerto 9389 (ADWS, cifrado)

↓

[DC: Servicio ADWS]

↓

Eventos 1138/1139: Conexión + autenticación

↓

[LDAP interno vía ::1]

↓

Evento 1644: Detalles de la consulta

Eventos 1166/1167: Estadísticas e índices

↓

Correlacionar por ID de operación

↓

Alerta de detección

Resultado

❌ No detecta PowerShell

• PowerShell usa el puerto 9389, no el 389/636

• El evento 1644 muestra ::1, no la IP del atacante

• No hay firmas específicas de la herramienta

• Parece actividad normal del controlador de dominio

✅ Detecta PowerShell

• El controlador de dominio registra cada paso del proceso ADWS

• El evento 1644 muestra el filtro LDAP real

• Los eventos 1138/1139 revelan quién hizo la consulta

• La correlación de múltiples eventos ofrece una detección de alta fidelidad

El dato clave: el evento 1644 por sí solo muestra localhost, lo que oculta al atacante. Pero cuando lo correlacionas con los eventos de conexión de ADWS, obtienes el panorama completo, incluyendo quién está realmente detrás de la consulta. Esa es la brecha de detección que cierra esta publicación.

Análisis del comando del actor de amenazas

Analicemos qué hizo realmente el atacante y por qué evadió la detección:

El comando

geT-aDcompUTER -Filter {eNaBLed -eq $TruE} -PROPErTieS *

Qué pretendía el atacante

Ofuscación de mayúsculas y minúsculas: geT-aDcompUTER en lugar de Get-ADComputer

  • Objetivo: evadir las detecciones de registro de línea de comandos de PowerShell
  • Objetivo (target): soluciones EDR que buscan coincidencias exactas de cadenas
  • Resultado: evadió con éxito la detección de endpoints

Lógica del filtro: {eNaBLed -eq $TruE}

  • Objetivo: obtener solo cuentas de computadora habilitadas (objetivos activos)
  • Traducción: PowerShell convierte esto en la verificación bit a bit !(userAccountControl&2)
  • Resultado: consulta eficiente usando índices de AD

Volcado completo de propiedades: -PROPErTieS *

  • Objetivo: recopilar la máxima información para su explotación posterior
  • Qué obtiene:
    • versiones del sistema operativo (niveles de parche)
    • nombres principales de servicio (ataques de Kerberos)
    • marcas de tiempo del último inicio de sesión (activas frente a obsoletas)
    • nombres DNS (mapeo de red)
    • membresías de grupos (identificación de privilegios)
    • descriptores de seguridad (enumeración de permisos)
    • más de 50 atributos por objeto de computadora

Qué sucedió realmente en el controlador de dominio

Cuando se ejecuta el comando, esta es la cadena de eventos de la recreación del laboratorio en MARVEL.LOCAL:

Lado del cliente de PowerShell (WAKANDA-WRKSTN):

geT-aDcompUTER -Filter {eNaBLed -eq $TruE} -PROPErTieS *

# La ofuscación de mayúsculas y minúsculas no importa: PowerShell no distingue mayúsculas de minúsculas

Controlador de dominio - servicio ADWS (Earth-DC):

  • recibe una solicitud cifrada de protocolo SOAP/XML en el puerto 9389
  • descifra el payload TLS
  • analiza la consulta de PowerShell desde el sobre de protocolo SOAP
  • la traduce a la sintaxis de filtro LDAP
  • hace una conexión LDAP LOCAL a NTDS mediante [::1]
  • devuelve resultados envueltos en protocolo SOAP/XML

Registros de eventos generados:

Evento 1644 (lo que detectó mi detección de la Parte 5):

Cliente: [::1]:57132  ← ¡Localhost! ¡No es la IP del atacante!

Usuario: MARVEL\loki

Filtro: ( & ( ! (userAccountControl&2) ) (objectClass=computer) 

         (objectCategory=CN=Computer,CN=Schema,CN=Configuration,DC=marvel,DC=local) )

Selección de atributos: [all_with_list]name,objectClass,objectGUID,...[más de 50 atributos]

Controles del servidor: SDflags:0x7

Entradas visitadas: 3

Entradas devueltas: 3

Tiempo de búsqueda: 16 ms

Por qué mis detecciones no lo captaron:

  • la IP del cliente era [::1]: filtré el localhost como "actividad del sistema"
  • el usuario era una cuenta de dominio válida: no había patrón de credential stuffing
  • la consulta era eficiente (relación de eficiencia 3/3): parecía una consulta de administrador optimizada
  • bajo recuento de objetos (solo 3 computadoras en el dominio de prueba): por debajo del umbral volumétrico
  • tiempo de consulta rápido (16 ms): no lo bastante lento para activar las alertas de búsquedas costosas

Mis líneas base decían: "Esto se ve normal". No lo era.

Lo que me perdí: el contexto de ADWS

El evento 1644 por sí solo no cuenta toda la historia. Necesitaba ver:

Evento 1138 (conexión ADWS):

User: MARVEL\loki  

Client: [::1]:57132  ← ¡Esto se correlaciona con el evento 1644!

Operation: ldap_search

Operation ID: 5

Evento 1139 (autenticación ADWS):

Operation ID: 5  ← ¡La misma operación!

Status: 0 (Success)

User: MARVEL\loki

Eventos 1166 (estadísticas por objeto):

Operation ID: 5

Object: CN=WAKANDA-WRKSTN,CN=Computers,DC=marvel,DC=local

Object: CN=Earth-DC,CN=Computers,DC=marvel,DC=local  

Object: CN=Asgard-WS,CN=Computers,DC=marvel,DC=local

Evento 1167 (uso de índices):

Operation ID: 5

Index: idx_objectCategory:3:N

El patrón que debí haber visto:

Todos estos eventos comparten el Operation ID 5 y ocurren dentro de una ventana de 10 segundos. Cuando los correlacionas:

Evento 1138 (inicio) + evento 1644 (detalles LDAP) + evento 1166 (resultados) + 

Evento 1167 (índices) + evento 1139 (completo) 


= sesión de enumeración de AD con PowerShell

Pero yo no estaba correlacionando eventos. Estaba mirando el evento 1644 de forma aislada.

El indicador [all_with_list]

El campo de selección de atributos mostraba:

[all_with_list]name,objectClass,objectGUID,sAMAccountName,dNSHostName,...

Ese prefijo [all_with_list] es exclusivo del parámetro -Properties * de PowerShell.

No deberías ver esto en:

  • consultas LDAP en Python (especifican atributos explícitamente)
  • DirectorySearcher de .NET (especifica PropertiesToLoad)
  • herramientas LDAP nativas (listan atributos exactos)
  • consultas nativas de Windows (solo atributos mínimos)

Este prefijo SOLO aparece cuando alguien ejecuta:

Get-ADUser -Properties *

Get-ADComputer -Properties *

Get-ADGroup -Properties *

Debí haber alertado sobre [all_with_list] combinado con objectClass=computer u objectClass=user, pero no lo estaba buscando.

La señal de alerta SDflags:0x7

Los controles del servidor mostraban SDflags:0x7. En binario: 0x7 = 0b111, lo que significa:

  • Bit 1 (0x1): OWNER_SECURITY_INFORMATION
  • Bit 2 (0x2): GROUP_SECURITY_INFORMATION
  • Bit 3 (0x4): DACL_SECURITY_INFORMATION

El atacante solicitó descriptores de seguridad completos: quién es el propietario de cada objeto de computadora y quién tiene permisos sobre ellos. Este es un reconocimiento clásico de escalada de privilegios.

Las consultas administrativas normales no solicitan descriptores de seguridad. BloodHound sí lo hace. PowerView también. Los atacantes también.

Pero mi detección no estaba analizando los controles del servidor en absoluto.

Lecciones aprendidas

De este único incidente:

  1. El evento 1644 por sí solo es insuficiente: necesitas los eventos de ADWS circundantes para tener contexto
  2. La IP localhost significa ADWS; no la filtres, correlaciónala con los eventos 1138/1139
  3. [all_with_list] es una señal clara de volcados masivos de propiedades por PowerShell
  4. Los SDflags en los controles del servidor indican enumeración de descriptores de seguridad
  5. Los ID de operación conectan toda la cadena de ataque a través de los cinco tipos de eventos
  6. La ofuscación de mayúsculas y minúsculas es irrelevante en la capa LDAP: concéntrate en lo que el DC realmente registra, no en cómo se veía la sintaxis de PowerShell

El resto de esta publicación de blog trata sobre cómo construí detecciones que realmente funcionan.

Arquitectura de ADWS: cómo PowerShell realmente se comunica con AD

La pila de ADWS

Active Directory Web Services no es un simple envoltorio alrededor de LDAP. Es una pila completa de servicios web basada en protocolo SOAP que ofrece una interfaz completamente diferente a Active Directory:

Módulo PowerShell ActiveDirectory

          ↓

   .NET System.DirectoryServices

          ↓

      API cliente de ADWS

          ↓

    WS-Management (WinRM)

          ↓

   Servicio ADWS (puerto 9389)

          ↓

   Base de datos de Active Directory

Cuando ejecutas Get-ADUser -Filter *, esto es lo que realmente ocurre:

  1. PowerShell llama al módulo ActiveDirectory
  2. El módulo usa clases de .NET DirectoryServices
  3. Estas clases se conectan a ADWS mediante WS-Management
  4. ADWS traduce la solicitud en consultas LDAP internas
  5. ADWS consulta la base de datos de AD directamente (sin LDAP de red)
  6. Los resultados se devuelven a través de la misma pila

Por eso la detección de LDAP basada en red pasa por alto por completo la enumeración con PowerShell. Las consultas LDAP ocurren dentro del controlador de dominio, nunca llegan a la red.

El problema del localhost

Este es el detalle arquitectónico crítico que hace posible la detección de ADWS:

Cuando ADWS consulta Active Directory en nombre de una sesión remota de PowerShell, hace esas consultas como localhost en el controlador de dominio. Esto crea un patrón distintivo en los registros:

Evento 1644: consulta LDAP desde ::1 (o 127.0.0.1)

Evento 1138: conexión ADWS desde [IP del atacante]

Evento 1139: conexión ADWS autenticada

Evento 1166: operación Enumerate de ADWS

Evento 1167: sesión ADWS finalizada

Los servidores Windows modernos usan IPv6 de forma predeterminada, así que normalmente verás ::1 (localhost de IPv6) en los registros del Evento 1644, aunque algunos sistemas pueden usar 127.0.0.1 (localhost de IPv4). Tus reglas de detección deben contemplar ambos patrones.

La combinación de consultas LDAP de localhost (Evento 1644) con conexiones ADWS remotas (Eventos 1138/1139) es la señal reveladora. Las aplicaciones legítimas no generan este patrón. Los atacantes que hacen enumeración con PowerShell sí.

El patrón de correlación de cinco eventos

Cada evento aporta una pieza del rompecabezas:

Event ID

Origen

Qué te indica

Campos clave

1138

ActiveDirectory_WebServices

Conexión iniciada

ClientAddress, InstanceId

1139

ActiveDirectory_WebServices

Qué cuenta se autenticó

PrincipalName, InstanceId

1644

ActiveDirectory_DomainService

La consulta LDAP real

Filter, Attributes, ClientIP, EntriesReturned

1166

ActiveDirectory_WebServices

Estadísticas por objeto

InstanceId, TimeElapsed

1167

ActiveDirectory_WebServices

Sesión completa

InstanceId

La clave de correlación: Todos los eventos comparten el mismo Operation ID o InstanceId dentro de una ventana de tiempo corta. Así es como los vinculas en una sola sesión de enumeración.

Nota técnica: Windows Server 2016+ usa IPv6 de forma predeterminada, así que normalmente verás ::1 en los registros del Evento 1644. Algunos sistemas usan 127.0.0.1. Tu detección debe tener en cuenta ambos casos.

Detección en acción: pruebas reales de laboratorio

Revisemos un escenario real de detección del laboratorio MARVEL.LOCAL con registros de eventos reales de Windows.

Entorno de laboratorio:

  • Cliente: WAKANDA-WRKSTN (10.1.1.14) - Windows 10/11, cuenta de usuario MARVEL\loki
  • Controlador de dominio: Earth-DC.marvel.local (10.1.1.11) - Windows Server 2022, con registro de Field Engineering habilitado

Desde la estación de trabajo cliente, ejecuté:

PS C:\> Get-ADComputer -Filter * -Properties *

Lo que vio la red

1.113 paquetes de protocolo SOAP/XML cifrado con TLS en el puerto 9389. Ningún filtro LDAP visible. Ningún atributo visible. Detección de red: ciega.

Lo que vio el Evento 1644

Log Name:      Directory Service

Event ID:      1644

User:          MARVEL\loki

Client:        [::1]:57132

Filter:

 ( & (objectClass=*) (objectClass=computer)     (objectCategory=CN=Computer,CN=Schema,CN=Configuration,DC=marvel,DC=local) )

Attribute selection:

[all_with_list]name,objectClass,objectGUID,sAMAccountName,

nTSecurityDescriptor,servicePrincipalName,memberOf...

Server controls: SDflags:0x7

Visited entries: 3

Returned entries: 3

Search time (ms): 16

Indicadores clave

Indicador

Qué significa

Cliente: [::1]:57132

Traducción de ADWS: la IP real queda oculta

Prefijo [all_with_list]

Se usó PowerShell -Properties *

SDflags:0x7

Enumeración del descriptor de seguridad (Owner+Group+DACL, excluye SACL)

Usuario: MARVEL\loki

Se conserva el contexto de la cuenta de usuario autenticada

Uniendo las piezas

Operation ID 5 (Get-ADComputer de loki):

  1138 START:    MARVEL\loki, [::1]:57132, ldap_search

  1644 DETAILS:  Filter, attributes, 3 entries, 16ms

  1166 STATS:    Per-object timing

  1167 INDEXES:  idx_objectCategory:3:N

  1139 COMPLETE: Status 0

Los cinco eventos están vinculados por el Operation ID. Esa es tu clave de correlación.

Curiosamente, también observé consultas LDAP simultáneas con ANONYMOUS LOGON desde la misma estación de trabajo que mostraban la IP real, un recordatorio de que no toda la enumeración usa ADWS, y la detección LDAP directa (Partes 1-4) sigue siendo importante.

Conclusiones clave de las pruebas reales

  1. El patrón de localhost es consistente: Cada consulta de PowerShell a AD mediante ADWS muestra el cliente como [::1] o 127.0.0.1, nunca la IP real del cliente. Esto es arquitectónico, no un error.
  2. La correlación de múltiples eventos es esencial: Un solo Evento 1644 por sí solo es ruidoso. Pero correlacionar 1138+1644+1166+1167+1139 por Operation ID da una detección de alta fidelidad.
  3. El [all_with_list] prefijo solo aparece con -Properties *. Es un indicador claro de enumeración masiva, no de una consulta de administrador específica.
  4. La detección de red pasa por alto a PowerShell por completo: La captura de Wireshark muestra protocolo SOAP/XML cifrado en el puerto 9389. No puedes ver el filtro LDAP, los atributos solicitados ni los objetos devueltos. La detección basada en el equipo mediante eventos de Windows es la ÚNICA forma de detectar esto.

Conclusión

La enumeración de AD con PowerShell está en todas partes. Es el método predeterminado para la administración legítima, y es el método preferido para los atacantes que quieren pasar inadvertidos. La detección de un solo evento no funciona: el Evento 1644 por sí solo es demasiado ruidoso, y los eventos de ADWS por sí solos son demasiado comunes.

Pero cuando correlacionas varios eventos entre sí, cuando ves consultas LDAP de localhost provenientes de conexiones ADWS, cuando rastreas sesiones de enumeración completas mediante InstanceId, cuando aplicas análisis estadístico para detectar anomalías, desbloqueas una detección de alta fidelidad del reconocimiento basado en PowerShell.

Cada parte se construyó sobre la anterior. Cada técnica de detección llenó un vacío. Y ahora tenemos una cobertura integral del reconocimiento de Active Directory: desde herramientas de Python que atacan LDAP a través de la red, hasta comandos nativos de PowerShell que usan ADWS internamente.

Todavía queda un problema: el Evento 1644 muestra localhost, no la IP real del atacante. En la Parte 5B, mostraré cómo resolver esto con la correlación del Evento 5156 y la ventana de tiempo de aproximadamente 60-80 ms que hace posible la atribución de la IP de origen.

Gracias a Jonathan Johnson, Anton Ovrutsky, Matt Anderson, Tyler Bohlmann, Michael Haag, Nasreddine Bencherchali, y Adam Bienvenu por su ayuda en la revisión de esta publicación.

También te podría interesar

Más información sobre Fallas de higiene cibernética que invitan a los hackers, y cómo detenerlas
Fallas de higiene cibernética que invitan a los hackers, y cómo detenerlas
Blog

Una mala gestión de credenciales, configuraciones incorrectas y cuentas olvidadas son el tapete de bienvenida que buscan los atacantes. Huntress documenta las fallas de higiene cibernética que ve con más frecuencia. Y cómo solucionarlas de verdad.

Más información sobre Los 36 ciberataques más comunes [2026]
Los 36 ciberataques más comunes [2026]
Blog

Cada día ocurren 600 millones de ciberataques en todo el mundo. El primer paso para reducir tu riesgo es saber cómo lucen los más comunes. Aquí tienes 36 de ellos, y lo que realmente funciona contra cada uno.

Más información sobre Cerrando la brecha: Managed ITDR ahora admite la deshabilitación de identidades para identidades sincronizadas con Active Directory
Cerrando la brecha: Managed ITDR ahora admite la deshabilitación de identidades para identidades sincronizadas con Active Directory
Blog

Una de las funciones más solicitadas de Huntress Managed ITDR ya está disponible: la deshabilitación de identidades para identidades sincronizadas con Active Directory. Esto es lo que significa para tu flujo de contención y respuesta.