Este es el primer post de una serie sobre los fideicomisos forestales de Active Directory. Explicará qué son exactamente los fideicomisos forestales y cómo se protegen con el filtrado SID. Si eres nuevo en los fideicomisos de Active Directory, te recomiendo que empieces por leer la guía en profundidad de harmj0y sobre ellos. Después de leer su (excelente) post tuve muchas preguntas sobre cómo funciona realmente bajo el capó y cómo los fideicomisos dentro del mismo bosque de AD se comparan con los fideicomisos entre diferentes bosques. Esta serie de blogs es tanto mi viaje como mi documentación sobre cómo investigué este tema y cómo lo entiendo ahora. Prepárese para una inmersión profunda en los fideicomisos, Kerberos, tickets dorados, mimikatz y impacket!
Este post va a discutir los fideicomisos entre diferentes bosques. Un bosque es una colección de uno o varios dominios, que forman parte de uno o varios árboles de dominios. En las organizaciones con un solo dominio, ese dominio también compone todo el bosque. En la documentación de Microsoft, los fideicomisos suelen llamarse fideicomisos interforestales (fideicomisos entre dos bosques diferentes) o intraforestales (fideicomisos entre dominios del mismo bosque). Como esas palabras siempre me confunden, en este post me referiré a ellas como fideicomisos dentro del bosque o fideicomisos entre bosques (o entre bosques). También es importante tener en cuenta que mientras estamos hablando de fideicomisos entre dos bosques, los fideicomisos siempre se definen entre dominios. Los fideicomisos entre bosques sólo pueden crearse entre dos dominios raíz de bosques diferentes, por lo que cualquier mención en este post de un fideicomiso entre bosques es el fideicomiso entre dos dominios raíz diferentes.
Dentro de un mismo bosque, todos los dominios confían entre sí y se puede escalar de un dominio comprometido a todos los demás, como se explica en la investigación de Sean Metcalf sobre los fideicomisos entre dominios. Para reiterar: Un dominio de Active Directory no es un límite de seguridad, un bosque de Active Directory lo es.
También hablaremos de los identificadores de seguridad (SID). Un SID es algo que identifica de forma única un principal de seguridad, como un usuario, grupo o dominio. Uno de los dominios en los bosques de prueba tiene SID S-1-5-21-3286968501-24975625-1618430583
. El conocido grupo Domain Admins, que tiene el ID 512, tiene el SID que consiste en el SID del dominio y el ID (llamado RID en la terminología de AD), dándole el SID S-1-5-21-3286968501-24975625-1618430583-512
en este dominio.
La configuración
La configuración contiene 3 bosques de Active Directory: A, B y C. Tanto el bosque A como el bosque C tienen una confianza forestal transitiva bidireccional con el bosque B (más adelante veremos qué significa esto exactamente). El bosque A y el bosque C no tienen una confianza entre sí. El bosque A es el único bosque con dos dominios: forest-a.local
y sub.forest-a.local
. Como estos dominios están dentro del bosque A, tienen una confianza padre-hijo bidireccional entre ellos. La configuración se muestra en la siguiente imagen:
Los dominios forest-a
y forest-b
tienen un Controlador de Dominio y un servidor miembro (que no se muestra en la imagen), los otros dominios sólo constan de un único Controlador de Dominio. Toda esta configuración se ejecuta en Azure y se gestiona a través de Terraform, que maneja las máquinas virtuales, las redes, los DNS y la configuración del bosque, mientras que los Trusts se han configurado manualmente después.
De A a B, ¿qué hay en un PAC?
Supongamos que estamos en el bosque A y queremos acceder a recursos en el bosque B. Tenemos nuestro Ticket de Concesión Kerberos (TGT), que es válido en el dominio raíz del bosque A en el que nos encontramos (convenientemente llamado forest-a
). Cuando queremos acceder a recursos fuera de nuestro dominio actual (ya sea dentro del mismo bosque o de un bosque diferente), Windows necesita un ticket de servicio para este recurso, en este ejemplo forest-b-server.forest-b.local
. Nuestro TGT para forest-a
es, por supuesto, inútil en forest-b
, ya que está cifrado con el hash de la cuenta krbtgt
en forest-a
, que forest-b
no tiene. En términos de Kerberos, nos estamos autenticando en un reino diferente. Así que lo que hace nuestro cliente en segundo plano es solicitar un ticket de servicio para el recurso al que estamos intentando acceder en forest-b
en el Controlador de Dominio (DC) de forest-a
. El DC (que en términos de Kerberos es un Centro de Distribución de Claves, o KDC) no tiene un recurso localmente con el sufijo forest-b.local
, busca cualquier confianza con bosques que tengan este sufijo.
Como hay una confianza bidireccional entre el bosque A y el bosque B, el DC de forest-a
puede emitirnos un ticket de remisión (TGT) a forest-b
. Este ticket está firmado con la clave de confianza Kerberos inter-realm, y contiene los grupos de los que somos miembros en forest-a
. El bosque B aceptará este ticket y nos concederá un ticket de servicio para forest-b-server
, que podemos utilizar para autenticarnos. A continuación se muestra un esquema:
Cuando nos conectamos en nuestra estación de trabajo en el Bosque A al servidor en el Bosque B, podemos ver los tickets con el comando klist
:
El segundo ticket desde arriba es nuestro TGT inicial, que podemos utilizar para solicitar el TGT para forest-b
. Puedes ver que este TGT para forest-b
(en la parte superior) tiene el principal krbtgt
para forest-b
en el campo del servidor. Esta es la cuenta en el bosque A que está asociada con el fideicomiso (esta cuenta se llama forest-b$
y reside en la parte de Usuarios del directorio). Su parte encriptada está cifrada con la clave de confianza entre dominios que estos comparten.
El tercer ticket desde arriba es el ticket que podemos utilizar en el bosque B para contactar con el servidor de allí. Se trata de un ticket de servicio que nos ha dado el DC del bosque B. Como se puede ver en el campo Servidor, este ticket se ha dado y es válido en el dominio Kerberos FOREST-B.LOCAL
.
Ahora vamos a profundizar en lo que realmente contiene este ticket. Cada TGT de Kerberos solicitado por Windows contiene un certificado de atributos de privilegio, o PAC. El formato se describe en MS-PAC en MSDN. Este PAC contiene, entre otras cosas, los SID de los grupos a los que pertenecemos. Para poder ver lo que hay realmente en el PAC, primero tenemos que obtener los tickets de la memoria. Para ello utilizaremos Mimikatz, con el que podemos volcar todos los tickets de Kerberos al disco con el comando sekurlsa::tickets /export
. Aunque Mimikatz contiene algo de código de depuración de Kerberos, no he podido averiguar cómo funciona, y no sé escribir en C, así que modificar nada era prácticamente imposible. Por suerte, mi biblioteca favorita de Python, impacket, soporta todo tipo de cosas de Kerberos. Impacket toma los tickets Kerberos en formato ccache
, que no es el formato que exporta Mimikatz, pero podemos convertirlos fácilmente con kekeo. Sólo tenemos que ejecutar el comando misc::convert ccache ourticket.kirbi
y Kekeo lo guardará como un archivo .ccache
que podemos leer con Impacket.
Para descifrar el PAC he escrito algunas utilidades basadas en los ejemplos de Impacket, que estoy publicando como parte de esta investigación. Por supuesto, para descifrar las partes cifradas de los tickets Kerberos necesitamos las claves de cifrado, así que las he extraído para todos los dominios usando secretsdump.py y su implementación dcsync. Para el primer TGT necesitamos añadir el hash krbtgt
al archivo. Basándonos en la captura de pantalla anterior, podemos ver que necesitaremos la clave aes256-cts-hmac-sha1-96
volcada por secretsdump.
La herramienta getcfST.py
se utiliza para solicitar un Ticket de Servicio en un bosque diferente basado en un TGT en el archivo .ccache
(puedes especificar el archivo a utilizar con export KRB5CCNAME=myticket.ccaches
). Esto descifrará y volcará todo el PAC, cuya salida se puede ver aquí para los interesados. He añadido algunas líneas de código que imprimen las partes importantes para nosotros en un formato más legible para los humanos:
Username: superuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 520 (attributes: 7) -> 512 (attributes: 7) -> 519 (attributes: 7) -> 518 (attributes: 7)LogonServer: forest-a-dcLogonDomainName: forest-aExtra SIDS: -> S-1-18-1
Este es un PAC bastante por defecto. Vemos que somos miembros de varios grupos, y como esta es la cuenta de Administrador (ID 500, aunque tiene otro nombre) del Bosque A con la que estamos probando, es miembro de varios grupos de administradores por defecto, como Domain Admins (512) y Enterprise Admins (519). También tenemos el SID extra S-1-18-1, que indica que nos estamos autenticando en base a la prueba de posesión de credenciales.
Para descifrar el segundo TGT, tenemos que cambiar la clave de la cuenta krbtgt
por la de la cuenta forest-b$
, que es la clave de confianza entre dominios. En este caso el PAC se encripta con RC4, que utiliza el hash de NT como entrada (sí, ese que usas para pasar el hash). Esto es por defecto a menos que se marque la casilla «El otro dominio soporta Kerberos AES Encryption». Como es visible en el volcado en bruto, el PAC no cambió, apoyando la suposición de que el DC simplemente vuelve a cifrar el PAC como parte del billete con la clave de confianza entre dominios para el Bosque B.
El TGS es una historia ligeramente diferente. Aparte de requerir algunos cambios en el ejemplo getcfST.py, y de tener que especificar la clave AES-256 de la cuenta del equipo forest-b-server
para descifrarlo, podemos ver que se ha añadido más información al PAC (volcado en bruto aquí):
Username: superuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 520 (attributes: 7) -> 512 (attributes: 7) -> 519 (attributes: 7) -> 518 (attributes: 7)LogonServer: forest-a-dcLogonDomainName: forest-aExtra SIDS: -> S-1-18-1Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)
Vemos que se ha añadido una nueva sección, que contiene el SID del dominio forest-b
y un grupo al que pertenece nuestra cuenta en el Bosque B. Estos SID forman parte de las estructuras ResourceGroup
del PAC y sirven para almacenar la pertenencia a cualquier grupo local del dominio forest-b
. Como se explica en este post de harmj0y, los grupos locales de dominio son los únicos que pueden contener directores de seguridad de otros bosques. En el dominio forest-b
, nuestro usuario superuser
del bosque A es miembro del grupo Testgroup2
, que puedes ver a continuación.
Dado que esto se refleja dentro de nuestro PAC, que los servidores a los que nos autenticamos utilizando nuestro Ticket de Servicio Kerberos utilizan para la autorización, cualquier privilegio asignado a Testgroup2
se aplicará a la cuenta de superusuario del bosque diferente. Así es como funciona la autenticación y la autorización a través de los fideicomisos.
Tickets dorados y filtrado de SID
Hace un par de años Sean Metcalf y Benjamin Delphy trabajaron juntos para añadir el soporte del Historial de SID a Mimikatz, lo que permitía escalar de un dominio de Active Directory a otro dentro del mismo bosque. El procedimiento para ello se detalla aquí. ¿Cómo se traduce esto a los fideicomisos con otro bosque? Vamos a crear un ticket dorado con unos cuantos SID interesantes para ver cómo se procesan al cruzar la frontera del bosque. Usamos el siguiente comando de Mimikatz para crear un ticket dorado en nuestro bosque actual:
kerberos::golden /domain:forest-a.local /sid:S-1-5-21-3286968501-24975625-1618430583 /rc4:2acc1a3824a47c4fcb21ef7440042e85 /user:Superuser /target:forest-a.local /service:krbtgt /sids:S-1-5-21-3286968501-24975625-1618430583-1604,S-1-5-21-3286968501-24975625-1111111111-1605,S-1-18-1,S-1-5-21-2897307217-3322366030-3810619207-1106 /ptt
Desglosemos este comando. Estamos creando un ticket dorado en forest-a
, firmado con el hash krbtgt
de forest-a
. Como SID extra incluimos algunos SID interesantes:
-
S-1-5-21-3286968501-24975625-1618430583-1604
, el SID de un grupo del que en realidad no somos miembros -
S-1-5-21-3286968501-24975625-1111111111-1605
, el SID de un dominio que en realidad no existe -
S-1-18-1
, el SID que Windows añade indicando que nos autenticamos con prueba de posesión de credenciales -
S-1-5-21-2897307217-3322366030-3810619207-1106
, un grupo enforest-b
La bandera /ptt
inyectará el ticket en memoria, y al navegar a \forest-b-server.forest-b.local
no vemos ningún mensaje de error, indicando que el ticket fue utilizado con éxito para acceder a un recurso en forest-b
. Exportamos los tickets como antes y los desencriptamos de la misma manera que en el apartado anterior.
El TGT para forest-a
contiene los SIDs esperados:
Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106
El TGT que obtuvimos para forest-b
del Controlador de Dominio de forest-a
, firmado con la clave de confianza inter-real, en realidad contiene exactamente la misma información:
Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106
Esto sugiere de nuevo que el DC no valida el PAC, sino que se limita a volver a firmarlo con la clave inter-realm para forest-b
, aunque contenga un grupo del que en realidad no somos miembros.
Una vez que presentamos este TGT al DC en forest-b
, obtenemos de vuelta nuestro Ticket de Servicio, que tiene el siguiente PAC:
Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-18-1Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)
¿Qué ha pasado aquí? Vemos que de nuevo nuestras membresías en el dominio forest-b
han sido añadidas al PAC, pero que algunos SIDs han sido filtrados. Aquí es donde el mecanismo de seguridad de filtrado de SID entró en acción, filtrando cualquier SID que no sea parte de forest-a
. Las reglas para el filtrado de SID se describen en MSDN. Las reglas interesantes aquí son las que tienen la entrada ForestSpecific. Estos SIDs sólo se permiten desde un PAC de dentro del bosque. Como nuestro PAC viene de fuera del bosque, estos SIDs siempre serán filtrados desde nuestro PAC. Las 3 reglas que siguen a las ForestSpecific se encargan de filtrar cualquier SID que no proceda del bosque A. Esto incluye tanto el SID inexistente que suministramos, como cualquier SID no ForestSpecific que exista en el bosque B.
Sigue permitiéndonos fingir ser cualquier usuario del bosque A, por lo que si a los usuarios del bosque A se les ha concedido algún privilegio especial en el bosque B (que es probablemente la razón por la que se estableció toda la confianza en primer lugar), esos están ahora comprometidos.
Relajación del filtrado SID
Lo que me llamó la atención al principio de esta investigación es una opción para los fideicomisos que sólo está disponible a través de la herramienta netdom
, y no aparece en la interfaz gráfica. Una de las páginas de la documentación de Microsoft describe el permitir el historial de SID en los fideicomisos entre bosques. ¿Qué hace esto? Permitamos el historial de SID en el fideicomiso del bosque B al A (que afecta a los usuarios que se autentican desde A en B):
C:\Users\superuser>netdom trust /d:forest-a.local forest-b.local /enablesidhistory:yesEnabling SID history for this trust.The command completed successfully.
¿Qué ha cambiado? Veamos cómo se traduce esto en el indicador TrustAttributes del objeto Trusted Domain. Usted puede consultar esto usando varias herramientas, a continuación se muestra la salida del archivo domain_trusts.html
de ldapdomaindump ejecutado contra el bosque B, que es una herramienta que escribí hace un tiempo para recopilar información de AD.
Nuestra confianza con el bosque A ahora tiene la bandera TREAT_AS_EXTERNAL
. En la documentación pertinente de Microsoft, está escrito lo siguiente:
Si este bit está activado, una confianza entre bosques para un dominio debe tratarse como una confianza externa a efectos del filtrado SID. Los fideicomisos entre bosques se filtran de forma más estricta que los fideicomisos externos. Este atributo relaja esos fideicomisos de bosques cruzados para que sean equivalentes a los fideicomisos externos. Para más información sobre cómo se filtra cada tipo de confianza, véase la sección 4.1.2.2.
Esto apunta de nuevo a la sección en la que se describe el filtrado SID. Veamos qué ocurre si ofrecemos el mismo TGT contra el DC forest-b
:
Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)
¡Nuestro ticket de servicio del DC forest-b
incluye ahora todos los SID que pusimos en nuestro ticket Mimikatz anterior! Esto significa que podemos especificar cualquier SID que no esté filtrado como ForestSpecific en nuestro PAC y que será aceptado por el DC del bosque B.
Creemos un nuevo ticket dorado con algunos SIDs más para probar esta hipótesis:
kerberos::golden /domain:forest-a.local /sid:S-1-5-21-3286968501-24975625-1618430583 /rc4:b8e9b4b3feb56c7ba1575bf7fa3dc76f /user:Superuser /target:forest-b.local /service:krbtgt /sids:S-1-5-21-3286968501-24975625-1618430583-1604,S-1-5-21-3286968501-24975625-1111111111-1605,S-1-18-1,S-1-5-21-2897307217-3322366030-3810619207-1106,S-1-5-21-2897307217-3322366030-3810619207-512,S-1-5-21-2897307217-3322366030-3810619207-519,S-1-5-21-2897307217-3322366030-3810619207-548,S-1-5-21-2897307217-3322366030-3810619207-3101
Los nuevos SIDs incluidos aquí:
-
S-1-5-21-2897307217-3322366030-3810619207-512
: Administradores de dominio, deben ser filtrados por la regla explícita ForestSpecific -
S-1-5-21-2897307217-3322366030-3810619207-519
: Administradores de empresa, deben ser filtrados por la regla explícita ForestSpecific -
S-1-5-21-2897307217-3322366030-3810619207-548
: Account Operators, debe ser filtrado por la regla ForestSpecific que no permite SIDs entre 500 y 1000. -
S-1-5-21-2897307217-3322366030-3810619207-3101
: Es un grupo que es miembro de Domain Admins, no debe ser filtrado.
Como habrás notado, lo anterior está realmente firmado con la clave de confianza inter-real, por lo que estamos creando directamente el TGT que es válido para el Bosque B aquí, para saltar el paso de ofrecerlo al DC del Bosque A primero.
Ahora obtenemos lo siguiente en el PAC de nuestro Ticket de Servicio:
Username: SuperuserDomain SID: S-1-5-21-3286968501-24975625-1618430583UserId: 500PrimaryGroupId 513Member of groups: -> 513 (attributes: 7) -> 512 (attributes: 7) -> 520 (attributes: 7) -> 518 (attributes: 7) -> 519 (attributes: 7)LogonServer: LogonDomainName: FOREST-AExtra SIDS: -> S-1-5-21-3286968501-24975625-1618430583-1604 -> S-1-5-21-3286968501-24975625-1111111111-1605 -> S-1-18-1 -> S-1-5-21-2897307217-3322366030-3810619207-1106 -> S-1-5-21-2897307217-3322366030-3810619207-3101Extra domain groups found! Domain SID:S-1-5-21-2897307217-3322366030-3810619207Relative groups: -> 1107 (attributes: 536870919)
Algunas cosas a tener en cuenta:
- Los grupos DA/EA/Operadores de cuenta son efectivamente eliminados por el filtrado SID
- El grupo Domain Admins no se añade a la parte
ResourceGroup
del PAC, aunque el grupo 3101 es miembro directo de este grupo. Esto se debe a que el grupo Domain Admins es un grupo global, mientras que sólo los grupos locales del dominio se añaden en el PAC.
¡Lo que esto significa para un atacante es que puede falsificar cualquier grupo RID >1000 si el historial SID está habilitado a través de un Forest trust! En la mayoría de los entornos, esto permitirá a un atacante comprometer el bosque. Por ejemplo, los grupos de seguridad de Exchange, que permiten una escalada de privilegios a DA en muchas configuraciones, tienen todos RIDs mayores de 1000. Además, muchas organizaciones tienen grupos personalizados para administradores de estaciones de trabajo o servicios de asistencia que tienen privilegios de administrador local en estaciones de trabajo o servidores. Por ejemplo, acabo de dar al grupo IT-Admins
(con RID 3101 que es parte de nuestro ticket dorado) privilegios de administrador en la máquina forest-b-server
. Después de intercambiar nuestro TGT por un ticket de servicio, podemos autenticarnos con este ticket en la caja:
Conclusiones
Los fideicomisos entre bosques están por defecto estrictamente filtrados y no permiten que ningún SID de fuera de ese bosque viaje por el fideicomiso. Un atacante que comprometa un bosque en el que se confía puede, sin embargo, hacerse pasar por cualquier usuario de ese bosque, y así obtener acceso a los recursos que se han concedido explícitamente a los usuarios/grupos de ese bosque.
Si el historial de SID está habilitado para una confianza entre bosques, la seguridad se debilita significativamente y los atacantes pueden suplantar la pertenencia a cualquier grupo con un RID mayor de 1000, lo que en la mayoría de los casos puede resultar en un compromiso del bosque.Si usted es un administrador de TI, considere cuidadosamente a qué usuarios de diferentes bosques les concede acceso en su bosque, porque cada usuario al que se le concede acceso debilita el límite de seguridad entre los bosques. No recomendaría permitir el historial de SID entre bosques a menos que sea absolutamente necesario.
En la siguiente parte (o partes, quién sabe) nos sumergiremos en cómo funciona la Transitividad de la confianza y discutiremos otros tipos de confianza con dominios fuera del bosque. Esto significa que también empezaremos a jugar con Forest C y sub.forest-a
.
Las herramientas
Las herramientas utilizadas en este post están disponibles como prueba de concepto en mi GitHub. Estas herramientas requerirán una modificación manual para ser utilizables y sólo se proporcionan AS-IS a las personas que quieran reproducir o sumergirse más en esta investigación.