Panorama#

Un dominio de Active Directory no es un límite de seguridad; el bosque lo es. Esta distinción, fácil de pasar por alto, es la que gobierna este capítulo: dos dominios de un mismo bosque confían entre sí de una manera que no admite filtrado, de modo que comprometer un dominio hijo equivale a comprometer el bosque entero. Cruzar a otro bosque, en cambio, exige que una protección concreta —el SID filtering, el filtrado de identificadores de seguridad— esté deshabilitada. Comprender dónde caen esos límites, y qué mecanismos permiten atravesarlos, es el trabajo de la fase final de un compromiso de dominio: cuando el atacante ya reunió privilegios en un dominio (típicamente por las vías de 4.4 · AD CS o 4.5 · Credential dumping) y busca proyectarlos al resto de la organización.

Hay dos familias de mecanismos que cruzan esos límites, y este capítulo las trata en orden. La primera son las relaciones de confianza (trusts): los enlaces explícitos que un administrador crea entre dominios o bosques para que los usuarios de uno accedan a recursos del otro. La segunda es la delegación Kerberos: el mecanismo legítimo por el cual un servicio actúa en nombre de un usuario hacia un tercer servicio —la base del inicio de sesión único en aplicaciones de varias capas— y, mal configurado, la fuente más rica de rutas de suplantación (impersonation) de todo Active Directory. Ambas familias comparten un rasgo: no explotan una vulnerabilidad de software sino el diseño de confianza del propio directorio, de modo que la defensa es casi siempre de configuración y postura, no de parcheo.

flowchart TD
  A["Privilegios en un dominio\n(Domain Admin o equivalente)"] --> B{"¿Hacia dónde\nescalo?"}
  B -->|"Dominio hijo → raíz\n(mismo bosque)"| C["SID History\nGolden Ticket + SID de\nEnterprise Admins (RID 519)"]
  B -->|"Bosque → bosque\n(SID filtering off)"| D["Trust Ticket\nclave de la cuenta trust$\n→ TGT inter-realm"]
  B -->|"Bastión PAM\n(PIM trust)"| E["Shadow Security Principals\ncomprometer el bastión = DA"]
  A2["Escritura en AD\no admin sobre un host"] --> F{"¿Qué tipo de\ndelegación?"}
  F -->|"Máquina cachea TGTs"| G["Unconstrained\ncoaccionar al DC → TGT del DC$\n→ DCSync"]
  F -->|"Servicio delega a\nSPN fijo"| H["Constrained / S4U\nimpersonar DA hacia\nel servicio permitido"]
  F -->|"Escritura sobre el\natributo del recurso"| I["RBCD\nsembrar cuenta de máquina\n→ S4U2proxy → takeover"]
  C --> Z["Compromiso del bosque"]
  D --> Z
  E --> Z
  G --> Z
  H --> Z
  I --> Z

Trusts: el bosque como límite de seguridad#

Una relación de confianza deja que los principales de seguridad (usuarios, máquinas, grupos) de un dominio se autentiquen contra recursos de otro. El detalle que la vuelve explotable es cómo Kerberos transporta la identidad a través del enlace: mediante el SID History, un atributo que arrastra los identificadores de seguridad (SID, Security Identifier) que una cuenta acumuló, pensado originalmente para preservar accesos durante migraciones de dominio. Un Golden Ticket forjado (ver 4.2 · Kerberos) puede inyectar en ese SID History cualquier SID que el atacante decida, y ahí empieza el abuso.

Escalada dentro del bosque (hijo → raíz). Con el hash de la cuenta krbtgt del dominio hijo —obtenido por el DCSync de 4.5 · Credential dumping— el atacante forja un Golden Ticket e inyecta en su SID History el SID del grupo Enterprise Admins del dominio raíz (el RID 519, en lugar del 512 de Domain Admins). Como el SID filtering no se aplica a los trusts internos de un bosque, el controlador de dominio raíz acepta ese SID y concede acceso de administrador de la empresa. Es una escalada directa de un dominio hijo al control del bosque completo: la razón técnica de por qué “comprometer el hijo es comprometer el bosque”.

# Obtener el SID del dominio y forjar el ticket con el SID de Enterprise Admins (RID 519) Convert-NameToSid target.domain.com\krbtgt # → S-1-5-21-… (o lookupsid.py de Impacket) mimikatz # kerberos::golden /user:Administrator /domain:hijo.corp.local /sid:S-1-5-21-<hijo> \ /krbtgt:<hash krbtgt> /sids:S-1-5-21-<raiz>-519 /ptt

Escalada entre bosques (Trust Ticket). Cruzar a un bosque distinto es más caro. Cada trust tiene una cuenta asociada (dominio-confiado$) cuya clave cifra los tickets inter-realm. Volcando esa clave —lsadump::trust /patch en Mimikatz— el atacante forja un inter-realm TGT (un TGT de referencia hacia el otro bosque) y pide desde él un ticket de servicio contra el objetivo. Pero esta vía requiere que el SID filtering esté deshabilitado en el trust, cosa que no es la configuración por defecto entre bosques: el SID filtering descarta precisamente los SID “extranjeros” inyectados por SID History, que es lo que anula el ataque. Por eso la escalada cross-forest es condicional, mientras que la intra-forest es incondicional.

# Volcar la clave del trust y forjar un TGT inter-realm hacia el bosque destino mimikatz # lsadump::trust /patch mimikatz # kerberos::golden /domain:hijo.corp.local /sid:S-1-5-21-<origen> /rc4:<clave trust> \ /user:Administrator /service:krbtgt /target:destino.local /ticket:trust.kirbi Rubeus.exe asktgs /ticket:trust.kirbi /service:CIFS/dc.destino.local /ptt
El SID filtering es la línea que separa “compromiso de dominio” de “compromiso de bosque a bosque”. Está activo por defecto en los trusts externos y de bosque, y es lo que impide que los SID inyectados vía SID History crucen el límite. Deshabilitarlo (a veces se hace por conveniencia operativa entre organizaciones fusionadas) es lo que abre la puerta al Trust Ticket. Auditar qué trusts tienen SID filtering desactivado es un control de postura de primer orden.

PAM trust: el bastión que gestiona. Microsoft introdujo PAM (Privileged Access Management) con un “bosque bastión” (bastion / red forest) que administra cuentas privilegiadas de otros bosques mediante Shadow Security Principals —objetos que mapean un grupo del bastión a un grupo privilegiado del bosque gestionado, con tiempo de vida acotado—. La consecuencia adversaria es directa: comprometer el bastión otorga administración de dominio sobre cada bosque que gestiona, y sembrar una cuenta propia como miembro de un Shadow Principal es persistencia de alcance total. Un bosque pensado para reducir el privilegio se convierte, si cae, en el punto único de compromiso de todos los demás.

# Detectar un PAM/PIM trust y enumerar los Shadow Security Principals Get-ADTrust -Filter {(ForestTransitive -eq $True)} Get-ADObject -SearchBase "CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=bastion,DC=local" \ -Filter * -Properties member

Delegación Kerberos: suplantar en nombre de otro#

La delegación resuelve un problema real: una aplicación web que consulta una base de datos SQL debe hacerlo como el usuario conectado, no como su propia cuenta de servicio, para que los permisos de la base se apliquen a la persona correcta. Kerberos ofrece tres mecanismos para ello, y los tres, mal acotados, se convierten en rutas de suplantación. La diferencia entre ellos es quién controla el permiso de delegar y hacia qué, y esa diferencia es exactamente la que determina el ataque.

Unconstrained: la máquina que guarda todos los TGT#

La unconstrained delegation —delegación no restringida— es la forma más antigua y más peligrosa. Una máquina marcada con este atributo (TRUSTED_FOR_DELEGATION, el bit 524288 de userAccountControl) cachea en su memoria el TGT completo de cualquiera que se autentique contra ella, para poder reusarlo hacia cualquier servicio. Si el atacante controla una máquina así —o compromete una que ya lo esté—, todo TGT que llegue queda a su disposición. Y aquí entra la coerción de 4.3 · NTLM: forzando a un controlador de dominio a autenticarse contra la máquina controlada (con SpoolSample/PrinterBug sobre MS-RPRN o PetitPotam sobre MS-EFSRPC), su TGT —el del DC$— queda en memoria. Como la cuenta de máquina de un DC posee por defecto los derechos de replicación, ese TGT robado habilita un DCSync inmediato. Es una cadena que va de “controlo una máquina cualquiera con delegación” a “controlo el dominio” sin necesitar ninguna credencial privilegiada previa, y los controladores de dominio suelen tener unconstrained habilitado de fábrica.

# Localizar máquinas con unconstrained delegation Get-ADComputer -Filter {TrustedForDelegation -eq $True} # BloodHound: MATCH (c:Computer {unconstraineddelegation:true}) RETURN c # Escuchar TGT entrantes en la máquina controlada, y coaccionar al DC para que se autentique Rubeus.exe monitor /interval:1 printerbug.py 'dominio/usuario:contraseña'@<DC-victima> <maquina-unconstrained> # o PetitPotam # Reusar el TGT del DC$ capturado y hacer DCSync Rubeus.exe asktgs /ticket:<TGT del DC$ en b64> /service:LDAP/dc.corp.local /ptt mimikatz # lsadump::dcsync /user:corp\krbtgt

Constrained (KCD): S4U2self + S4U2proxy#

La constrained delegation (Kerberos Constrained Delegation, KCD) acota la delegación a una lista de servicios concretos: el atributo msDS-AllowedToDelegateTo de la cuenta enumera hacia qué SPN (Service Principal Name) puede delegar. El mecanismo se apoya en dos extensiones: S4U2self (Service for User to Self), que permite a un servicio obtener un ticket “a nombre de” cualquier usuario hacia sí mismo, y S4U2proxy (Service for User to Proxy), que reenvía ese ticket hacia el servicio permitido. Si el atacante controla la cuenta de servicio (su hash), puede pedir vía S4U2self un ticket que suplanta a cualquier usuario —incluido un Domain Admin— y proyectarlo hacia el servicio delegado. El detalle que amplifica el abuso: el nombre del servicio en el ticket de S4U2proxy no está en la parte cifrada del ticket, de modo que se puede sustituir (/altservice) por otro servicio de la misma máquina —pedir un ticket para time/ y usarlo como cifs/ o host/—, ampliando el acceso más allá de lo que el administrador creyó autorizar.

# Identificar cuentas con constrained delegation y hacia qué servicios Get-DomainComputer -TrustedToAuth | select name,msds-allowedtodelegateto # Impersonar a Administrator hacia el servicio permitido (Impacket) getST.py -spn CIFS/sql01.corp.local -impersonate Administrator 'corp/svc_sql:contraseña' # El SPN se puede sustituir por otro servicio del mismo host (no está cifrado en el ticket) Rubeus.exe s4u /user:svc_sql /rc4:<hash> /impersonateuser:Administrator \ /msdsspn:"time/sql01.corp.local" /altservice:cifs /ptt

Una variante local de este mecanismo permite escalar en el propio host: los procesos que corren como Network Service o bajo un Application Pool de IIS actúan con la identidad de la cuenta de máquina, así que desde ahí se puede lanzar S4U2self (/self) para obtener un ticket que suplanta a un administrador hacia la propia máquina. El S4U2proxy falla, pero el ticket de S4U2self ya sirve, y su nombre de servicio también se sustituye (tgssub) para apuntar al servicio deseado.

RBCD: la delegación que vive en el recurso#

La Resource-Based Constrained Delegation (RBCD, delegación restringida basada en recursos) invierte dónde vive el permiso. En la delegación clásica el atributo está en el que delega; en RBCD está en el recurso destino, en su atributo msDS-AllowedToActOnBehalfOfOtherIdentity. La consecuencia es la que convierte a RBCD en el abuso de delegación más frecuente hoy: si el atacante puede escribir ese atributo del objetivo, él mismo elige quién puede delegar hacia el objetivo. La receta es sembrar una cuenta de máquina bajo su control como delegador autorizado y luego usar S4U para suplantar a un Domain Admin hacia el objetivo. Con eso, cualquier primitiva de escritura sobre el objeto —una arista GenericWrite, GenericAll o WriteDACL de las que mapea 4.1 · Reconocimiento de AD, o las que trata 4.7 · ACLs y persistencia— se convierte en takeover completo del objetivo, controlador de dominio incluido.

La creación de la cuenta de máquina que RBCD necesita se apoya en el MachineAccountQuota, la cuota que por defecto permite a cualquier usuario del dominio crear hasta diez cuentas de equipo. Ponerla en cero es, por eso, una de las mitigaciones de mayor rendimiento del capítulo.

# 1) Crear una cuenta de máquina (abusa del MachineAccountQuota por defecto) addcomputer.py -computer-name 'evilpc$' -computer-pass 'Passw0rd!' 'corp/usuario:contraseña' # 2) Escribir el atributo RBCD del objetivo apuntando a nuestra cuenta rbcd.py -delegate-to 'DC01$' -delegate-from 'evilpc$' -action write 'corp/usuario:contraseña' # 3) Pedir un ticket que suplanta a Administrator hacia el objetivo getST.py -spn 'cifs/dc01.corp.local' -impersonate Administrator 'corp/evilpc$:Passw0rd!'

El Bronze Bit: romper la protección de Protected Users#

El Bronze Bit (CVE-2020-17049) es la vulnerabilidad que anula el control que debería frenar toda esta familia. Las cuentas marcadas como “sensitive and cannot be delegated” o incluidas en el grupo Protected Users están, por diseño, excluidas de la delegación. El fallo: el flag forwardable de un ticket S4U —el que determina si el ticket puede reenviarse— está protegido solo por el cifrado del servicio, no por una firma del KDC. Quien posea el hash de la cuenta de servicio puede, por lo tanto, activar ese flag por su cuenta e impersonar incluso a usuarios explícitamente protegidos de delegación. Rompe la última red de seguridad de la delegación, y por eso su parche (más Protected Users) es indispensable en cuentas de alto valor.

# Impersonar a un usuario protegido forzando el flag forwardable (Impacket con soporte Bronze Bit) getST.py -spn cifs/servicio.corp.local -impersonate usuario_protegido \ -hashes :<NTLM del servicio> corp/svc_service # Un DC parcheado responde: KRB_AP_ERR_MODIFIED (Message stream modified)

Defensa y detección#

La delegación y los trusts se defienden ante todo por postura, porque casi ninguno de estos abusos explota un fallo parcheable: explotan configuraciones que el diseño permite. La detección complementa, pero la reducción de superficie es lo que corta las cadenas de raíz.

  • Unconstrained delegation. Deshabilitarla en todo lo que no la necesite estrictamente, y nunca en servidores expuestos. Poner las cuentas privilegiadas en Protected Users y marcarlas “sensitive and cannot be delegated” impide que sus TGT queden cacheables. Deshabilitar el Print Spooler en los controladores de dominio neutraliza SpoolSample como gatillo de coerción. Detección: conexiones entrantes MS-RPRN/MS-EFSRPC hacia hosts que no son DC, y el uso posterior de un TGT del DC$ desde una IP anómala.
  • RBCD. MachineAccountQuota = 0 elimina la creación de cuentas de máquina de la que depende el ataque. La siembra del atributo genera un Event ID 5136 (modificación de objeto de directorio) sobre msDS-AllowedToActOnBehalfOfOtherIdentity —una escritura sobre ese atributo específico es intrínsecamente sospechosa y es la firma de detección más limpia de RBCD—.
  • Constrained delegation y S4U. El uso de S4U produce un Event ID 4769 (solicitud de ticket de servicio) que incluye el campo Transited Services poblado —la marca de una delegación en curso—; correlacionarlo con la cuenta que lo origina y el usuario suplantado delata la impersonación. Auditar quién tiene TrustedToAuth (msDS-AllowedToDelegateTo no vacío) como inventario de postura.
  • Bronze Bit. Parchear CVE-2020-17049 en todos los DC; con el parche, el intento falla con KRB_AP_ERR_MODIFIED. Protected Users refuerza la defensa por si algún DC quedara sin parchear.
  • Trusts. Auditar qué trusts tienen SID filtering deshabilitado (el que abre el Trust Ticket cross-forest) y revisar la existencia de PAM/PIM trusts y sus Shadow Security Principals. El SID History anómalo en un ticket —un SID de Enterprise Admins que no corresponde a la cuenta— es detectable auditando el atributo sIDHistory y correlacionando los Event ID 4768/4769 con orígenes inesperados.

El mapeo defensivo consolidado de Active Directory —la correlación de estos eventos con la línea base, las reglas SIEM y los playbooks de respuesta a un abuso de delegación— se desarrolla en 4.9 · Defensa de AD. La coerción compartida con el relay de NTLM está en 4.3 · NTLM, y el DCSync que remata la cadena de unconstrained, en 4.5 · Credential dumping.

Referencias#