Queries de Bloodhound en la consola de Neo4j
Neo4j tiene su propio lenguaje de consulta llamado Cypher y explotarlo dentro de una base de datos de Bloodhound puede resultarnos tremendamente útil.
Todo en la base de datos de Neo4j se representa usando términos comunes de la teoría de grafos, particularmente edges (en español ‘bordes’ suena raro) y nodos.
En la base de datos de BloodHound, un nodo puede representar uno de los siguientes objetos en un entorno de Active Directory:
- Usuario
- Grupo
- Computadora
- Dominio
Los nodos representan objetos discretos sobre los que se puede actuar al moverse a través de un entorno. La otra parte del gráfico son los llamados edges que representan las relaciones entre los nodos.
En la base de datos BloodHound, los bordes representan las siguientes relaciones:
- MemberOf: el nodo A (un usuario, grupo o computadora) es miembro del nodo B (un grupo)
- AdminTo: el nodo A (un usuario, grupo o computadora) tiene derechos de administrador local en el nodo B (una computadora)
- HasSession: el nodo A (un usuario) tiene una sesión en el nodo B (una computadora)
- TrustedBy: el nodo B (un dominio) confía en el nodo A (un dominio)
Edges representan las acciones necesarias para actuar sobre los nodos. Juntos, crean las rutas que usamos en BloodHound para demostrar cómo se puede abusar de los diferentes permisos en Active Directory para llegar a nuestro objetivo.
En el siguiente post recogemos un buen recopilatorio con varias consultas en Cypher para investigar nuestros datos de BloodHound directamente desde la consola web de Neo4j yendo más allá de lo que puede proporcionar la interfaz gráfica de usuario (GUI).
Computers
Match (n:Computer) return properties(n)
Generar una lista de todos los sistemas operativos
MATCH (c:Computer) RETURN DISTINCT(c.operatingsystem)
Encontrar todas las computadoras con Windows 7
MATCH (c:Computer) WHERE toUpper(c.operatingsystem) CONTAINS "SERVER" RETURN c
Encontrar todas las computadoras con sesiones de usuarios de un dominio diferente (buscando oportunidades de compromiso entre dominios).
MATCH (c:Computer)-[:HasSession]->(u:User {domain:'EXAMPLE.COM'}) WHERE NOT c.domain = u.domain RETURN u.name,COUNT(c)
Devolver el nombre de cada computadora en la base de datos donde al menos un SPN para la computadora contiene la cadena “MSSQL”.
MATCH (c:Computer) WHERE ANY (x IN c.serviceprincipalnames WHERE toUpper(x) CONTAINS "MSSQL") RETURN c.name,c.serviceprincipalnames ORDER BY c.name ASC
Sacar el número de computadoras que no tienen admins
MATCH (n)-[r:AdminTo]->(c:Computer) WITH COLLECT(c.name) as compsWithAdmins MATCH (c2:Computer) WHERE NOT c2.name in compsWithAdmins RETURN COUNT(c2)
Obtener los nombres de las computadoras sin administradores, ordenadas en orden alfabético
MATCH (n)-[r:AdminTo]->(c:Computer) WITH COLLECT(c.name) as compsWithAdmins MATCH (c2:Computer) WHERE NOT c2.name in compsWithAdmins RETURN c2.name ORDER BY c2.name ASC
Buscar cualquier computadora que NO sea un controlador de dominio en el que se confíe para realizar una delegación unconstrained.
MATCH (c1:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.objectsid ENDS WITH "-516" WITH COLLECT(c1.name) AS domainControllers MATCH (c2:Computer {unconstraineddelegation:true}) WHERE NOT c2.name IN domainControllers RETURN c2.name,c2.operatingsystem ORDER BY c2.name ASC
Encontrar todas las instancias de una cuenta de computadora que tenga derechos de administrador local en otras computadoras. Devolver en orden descendente la cantidad de computadoras en las que la cuenta de computadora tiene derechos de administrador local.
MATCH (c1:Computer) OPTIONAL MATCH (c1)-[:AdminTo]->(c2:Computer) OPTIONAL MATCH (c1)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c3:Computer) WITH COLLECT(c2) + COLLECT(c3) AS tempVar,c1 UNWIND tempVar AS computers RETURN c1.name,COUNT(DISTINCT(computers)) ORDER BY COUNT(DISTINCT(computers)) DESC
Encontrar computadoras con descripciones y mostrarlas (junto con la descripción, a veces los administradores guardan datos sensibles en las descripciones de los objetos de dominio, como las contraseñas):
MATCH (c:Computer) WHERE c.description IS NOT NULL RETURN c.name,c.description
Alternativamente, encuentra cada equipo que tiene derechos de administrador local en otros equipos y muestra estos equipos:
MATCH (c1:Computer) OPTIONAL MATCH (c1)-[:AdminTo]->(c2:Computer) OPTIONAL MATCH (c1)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c3:Computer) WITH COLLECT(c2) + COLLECT(c3) AS tempVar,c1 UNWIND tempVar AS computers RETURN c1.name AS COMPUTER,COLLECT(DISTINCT(computers.name)) AS ADMIN_TO_COMPUTERS ORDER BY c1.name
Mostrar los equipos (excluyendo los controladores de dominio) en los que los administradores de dominio han iniciado sesión:
MATCH (n:User)-[:MemberOf*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'}) WITH n as privusers
Encontrar el porcentaje de equipos con ruta de acceso a Administradores de Dominio:
MATCH (totalComputers:Computer {domain:'DOMAIN.GR'}) MATCH p=shortestPath((ComputersWithPath:Computer {domain:'DOMAIN.GR'})-[r*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'})) WITH COUNT(DISTINCT(totalComputers)) as totalComputers, COUNT(DISTINCT(ComputersWithPath)) as ComputersWithPath RETURN 100.0 * ComputersWithPath / totalComputers AS percentComputersToDA
Encontrar en cada equipo quién puede RDP (buscando sólo los usuarios habilitados):
MATCH (c:Computer) OPTIONAL MATCH (u:User)-[:CanRDP]->(c) WHERE u.enabled=true OPTIONAL MATCH (u1:User)-[:MemberOf*1..]->(:Group)-[:CanRDP]->(c) where u1.enabled=true WITH COLLECT(u) + COLLECT(u1) as tempVar,c UNWIND tempVar as users RETURN c.name AS COMPUTER,COLLECT(DISTINCT(users.name)) as USERS ORDER BY USERS desc
Encontrar en cada equipo el número de usuarios con derechos de administrador (administradores locales) y mostrar los usuarios con derechos de administrador:
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 DESC
Contar el número de equipos en los que cada usuario del dominio tiene privilegios de administrador derivados:
MATCH (u:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c:Computer) RETURN count(DISTINCT(c.name)) AS COMPUTER, u.name AS USER ORDER BY u.name
Mostrar los nombres de los equipos en los que cada usuario del dominio tiene privilegios de administrador derivados:
MATCH (u:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c:Computer) RETURN DISTINCT(c.name) AS COMPUTER, u.name AS USER ORDER BY u.name
Encontrar el número de equipos que no tienen Admins locales:
MATCH (n)-[r:AdminTo]->(c:Computer) WITH COLLECT(c.name) as compsWithAdmins MATCH (c2:Computer) WHERE NOT c2.name in compsWithAdmins RETURN COUNT(c2)
Usuarios
Mostrar todos los miembros del grupo de administradores de dominio:
MATCH (u:User)-[:MemberOf]->(g:Group {name:'DOMAIN ADMINS@WHITEOAK.ORG'}) return u.name, u.displayname
Buscar nodos de usuario donde el nombre de la propiedad contiene el texto “admin”, y luego devolver las propiedades de nombre, nombre para mostrar y descripción para cualquier coincidencia
MATCH (u:User) WHERE u.name CONTAINS "ADMIN" return u.name, u.displayname, u.description LIMIT 10
Mostrar todos los usuarios que son administradores en más de una máquina
MATCH (U:User)-[r:MemberOf|:AdminTo*1..]->(C:Computer) WITH U.name as n, COUNT(DISTINCT(C)) as c WHERE c>1 RETURN n
Mostrar todas las sesiones de los usuarios de la OU con el siguiente GUID
MATCH p=(o:OU {guid:'045939B4-3FA8-4735-YU15-7D61CFOU6500'})-[r:Contains*1..]->(u:User) MATCH (c:Computer)-[rel:HasSession]->(u) return u.name,c.name
Listar de usuarios únicos con una ruta (sin ruta “GetChanges”, sin “CanRDP”) a un grupo etiquetado como “highvalue”
MATCH (u:User) MATCH (g:Group {highvalue: True}) MATCH p = shortestPath((u:User)-[r:AddMember|AdminTo|AllExtendedRights|AllowedToDelegate|Contains|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GetChangesAll|GpLink|HasSession|MemberOf|Owns|ReadLAPSPassword|TrustedBy|WriteDacl|WriteOwner*1..]->(g)) RETURN DISTINCT(u.name),u.enabled order by u.name
Mostrar la cantidad de usuarios que tienen derechos de administrador en cada computadora, en orden descendente
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 computerName,COUNT(DISTINCT(admins)) AS adminCount ORDER BY adminCount DESC
Buscar usuarios que no estén marcados como “Sensibles y no se puede delegar” que tengan acceso administrativo a una computadora, y donde esos usuarios tengan sesiones en servidores con Delegación unconstrained habilitada.
MATCH (u:User {sensitive:false})-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c1:Computer) WITH u,c1 MATCH (c2:Computer {unconstraineddelegation:true})-[:HasSession]->(u) RETURN u.name AS user,c1.name AS AdminTo,c2.name AS TicketLocation ORDER BY user ASC
Igual que arriba, pero solo devuelve la lista de usuarios.
MATCH (u:User {sensitive:false})-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c1:Computer) WITH u,c1 MATCH (c2:Computer {unconstraineddelegation:true})-[:HasSession]->(u) RETURN DISTINCT(u.name)
Devuelve todos los usuarios que pueden volver a cualquier sistema, si pertenecen a cuentas adm o svr.
MATCH (c:Computer) MATCH (n:User)-[r:MemberOf]->(g:Group) WHERE g.name = 'DOMAIN ADMINS@EXAMPLE.COM' optional match (g:Group)-[:CanRDP]->(c) OPTIONAL MATCH (u1:User)-[:CanRDP]->(c) where u1.enabled = true and u1.name contains 'ADM' OR u1.name contains 'SVR' OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:CanRDP]->(c) where u2.enabled = true and u2.name contains 'ADM' OR u2.name contains 'SVR' WITH COLLECT(u1) + COLLECT(u2) + collect(n) as tempVar,c UNWIND tempVar as users RETURN c.name,COLLECT(users.name) as usernames ORDER BY usernames desc
Porcentaje de estadísticas de usuarios habilitados que tienen una ruta a un grupo de alto valor.
MATCH (u:User {domain:'EXAMPLE.COM',enabled:True}) MATCH (g:Group {domain:'EXAMPLE.COM'}) WHERE g.highvalue = True WITH g, COUNT(u) as userCount MATCH p = shortestPath((u:User {domain:'EXAMPLE.COM',enabled:True})-[*1..]->(g)) RETURN toint(100.0 * COUNT(distinct u) / userCount)
Devolver el nombre de usuario y la cantidad de computadoras para las que el nombre de usuario es administrador
MATCH (U:User)-[r:MemberOf|:AdminTo*1..]->(C:Computer) WITH U.name as n, COUNT(DISTINCT(C)) as c RETURN n,c ORDER BY c DESC
¿Qué permisos tienen Todos/Usuarios autenticados/Usuarios de dominio/Equipos de dominio?
MATCH p=(m:Group)- [r:AddMember|AdminTo|AllExtendedRights|AllowedToDelegate|CanRDP|Contains|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GetChanges|GetChangesAll|HasSession|Owns|ReadLAPSPassword|SQLAdmin|TrustedBy|WriteDACL|WriteOwner|AddAllowedToAct|AllowedToAct]->(t) WHERE m.objectsid ENDS WITH '-513' OR m.objectsid ENDS WITH '-515' OR m.objectsid ENDS WITH 'S-1-5-11' OR m.objectsid ENDS WITH 'S-1-1-0' RETURN m.name,TYPE(r),t.name,t.enabled
Encontrar todos los usuarios con un SPN/Encontrar todos los usuarios Kerberoastable con contraseñas establecidas por última vez hace más de 5 años (en la consola)
MATCH (u:User) WHERE n.hasspn=true AND WHERE u.pwdlastset < (datetime().epochseconds - (1825 * 86400)) and NOT u.pwdlastset IN [-1.0, 0.0] RETURN u.name, u.pwdlastset order by u.pwdlastset
Usuarios Kerberoastable con más privilegios.
MATCH (u:User {hasspn:true}) OPTIONAL MATCH (u)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (u)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH u,COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS comps RETURN u.name,COUNT(DISTINCT(comps)) ORDER BY COUNT(DISTINCT(comps)) DESC
Buscar usuarios que hayan iniciado sesión en los últimos 90 días. Cambiar 90 por el umbral que se desee. (En la consola)
MATCH (u:User) WHERE u.lastlogon < (datetime().epochseconds - (90 * 86400)) and NOT u.lastlogon IN [-1.0, 0.0] RETURN u.name, u.lastlogon order by u.lastlogon
Enumerar los usuarios y sus tiempos de inicio de sesión + pwd últimos tiempos establecidos en formato legible para el ser humano
MATCH (n:User) WHERE n.enabled = TRUE RETURN n.name, datetime({epochSeconds: toInteger(n.pwdlastset) }), datetime({epochSeconds: toInteger(n.lastlogon) }) order by n.pwdlastset
Enumerar todos los usuarios con administración local y contar instancias
OPTIONAL MATCH (c1)-[:AdminTo]->(c2:Computer) OPTIONAL MATCH (c1)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c3:Computer) WITH COLLECT(c2) + COLLECT(c3) AS tempVar,c1 UNWIND tempVar AS computers RETURN c1.name,COUNT(DISTINCT(computers)) ORDER BY COUNT(DISTINCT(computers)) DESC
Encontrar todos los usuarios que forman parte del grupo VPN
Match (u:User)-[:MemberOf]->(g:Group) WHERE g.name CONTAINS "VPN" return u.name,g.name
Encontrar los usuarios que nunca se han conectado y la cuenta sigue activa
MATCH (n:User) WHERE n.lastlogontimestamp=-1.0 AND n.enabled=TRUE RETURN n.name ORDER BY n.name
Grupos
Devolver los grupos cuyo nombre contiene la cadena “ADM”.
MATCH (g:Group) WHERE g.name =~ '(?i).*ADM.*' RETURN g.name
Buscar grupos con usuarios y equipos que pertenezcan al grupo.
MATCH (c:Computer)-[r:MemberOf*1..]->(groupsWithComps:Group) WITH groupsWithComps MATCH (u:User)-[r:MemberOf*1..]->(groupsWithComps) RETURN DISTINCT(groupsWithComps) as groupsWithCompsAndUsers
Mostrar los grupos con más administradores locales
MATCH (g:Group) WITH g OPTIONAL MATCH (g)-[r:AdminTo]->(c:Computer) WITH g,COUNT(c) as expAdmin OPTIONAL MATCH (g)-[r:MemberOf*1..]->(a:Group)-[r2:AdminTo]->(c:Computer) WITH g,expAdmin,COUNT(DISTINCT(c)) as unrolledAdmin RETURN g.name,expAdmin,unrolledAdmin, expAdmin + unrolledAdmin as totalAdmin ORDER BY totalAdmin DESC
Grupos que tienen local admin.
MATCH p=(m:Group)-[r:AdminTo]->(n:Computer) RETURN m.name, n.name ORDER BY m.name
Encontrar qué grupos pueden hacer RDP .
MATCH p=(m:Group)-[r:CanRDP]->(n:Computer) RETURN m.name, n.name ORDER BY m.name
Encontrar los grupos que pueden restablecer las contraseñas.
MATCH p=(m:Group)-[r:ForceChangePassword]->(n:User) RETURN m.name, n.name ORDER BY m.name
Buscar qué grupos tienen derechos de administrador local.
MATCH p=(m:Group)-[r:AdminTo]->(n:Computer) RETURN m.name, n.name ORDER BY m.name
Listar los grupos de todos los usuarios propietarios.
MATCH (m:User) WHERE m.owned=TRUE WITH m MATCH p=(m)-[:MemberOf*1..]->(n:Group) RETURN m.name, n.name ORDER BY m.name
Listar los grupos únicos de todos los usuarios propietarios
MATCH (m:User) WHERE m.owned=TRUE WITH m MATCH (m)-[r:MemberOf*1..]->(n:Group) RETURN DISTINCT(n.name)
Encontrar qué usuarios tienen derechos de administrador local.
MATCH p=(m:User)-[r:AdminTo]->(n:Computer) RETURN m.name, n.name ORDER BY m.name
Grupo de Active Directory con derechos privilegiados por defecto sobre los usuarios y grupos del dominio, además de la capacidad de iniciar sesión en los Controladores de Dominio
MATCH (u:User)-[r1:MemberOf*1..]->(g1:Group {name:'ACCOUNT OPERATORS@DOMAIN.GR'}) RETURN u.name
Encontrar qué grupos del dominio son administradores de qué equipos:
MATCH (g:Group) OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GROUP, COLLECT(computers.name) AS AdminRights
Encontrar qué grupos del dominio (excluyendo a los administradores del dominio y a los administradores de la empresa) son administradores de qué equipos:
MATCH (g:Group) WHERE NOT (g.name =~ '(?i)domain admins@.*' OR g.name =~ "(?i)enterprise admins@.*") OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GROUP, COLLECT(computers.name) AS AdminRights
Encontrar qué grupos del dominio (excluyendo los grupos de alto privilegio marcados con AdminCount=true) son administradores de qué equipos:
MATCH (g:Group) WHERE g.admincount=false OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GROUP, COLLECT(computers.name) AS AdminRights
Encontrar los grupos con más privilegios en el dominio (grupos que son Admins to Computers. Se calcularán los grupos anidados):
MATCH (g:Group) OPTIONAL MATCH (g)-[:AdminTo]->(c1:Computer) OPTIONAL MATCH (g)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c2:Computer) WITH g, COLLECT(c1) + COLLECT(c2) AS tempVar UNWIND tempVar AS computers RETURN g.name AS GroupName,COUNT(DISTINCT(computers)) AS AdminRightCount ORDER BY AdminRightCount DESC
Encontrar el porcentaje de grupos sin privilegios (basado en admincount:false) al grupo de Administradores de Dominio:
MATCH (totalGroups:Group {admincount:false}) MATCH p=shortestPath((GroupsWithPath:Group {admincount:false})-[r*1..]->(g:Group {name:'DOMAIN ADMINS@DOMAIN.GR'})) WITH COUNT(DISTINCT(totalGroups)) as totalGroups, COUNT(DISTINCT(GroupsWithPath)) as GroupsWithPath RETURN 100.0 * GroupsWithPath / totalGroups AS percentGroupsToDA
Qué permisos tiene Todo el mundo/usuarios autenticados/usuarios del dominio/equipos del dominio
MATCH p=(m:Group)-[r:AddMember|AdminTo|AllExtendedRights|AllowedToDelegate|CanRDP|Contains|ExecuteDCOM|ForceChangePassword|GenericAll|GenericWrite|GetChanges|GetChangesAll|HasSession|Owns|ReadLAPSPassword|SQLAdmin|TrustedBy|WriteDACL|WriteOwner|AddAllowedToAct|AllowedToAct]->(t)
WHERE m.objectsid ENDS WITH '-513' OR m.objectsid ENDS WITH '-515' OR m.objectsid ENDS WITH 'S-1-5-11' OR m.objectsid ENDS WITH 'S-1-1-0' RETURN m.name,TYPE(r),t.name,t.enabled
OUs
Devolver cada OU que contiene la cadena “CITRIX”.
MATCH (o:OU) WHERE o.name =~ "(?i).*CITRIX.*" RETURN o
Devolver cada OU en la base de datos ordenado por la cantidad de computadoras en esa OU.
MATCH (o:OU)-[:Contains]->(c:Computer) RETURN o.name,o.guid,COUNT(c) ORDER BY COUNT(c) DESC
Devolver cada unidad organizativa de la base de datos que contiene un equipo servidor. Devuelve filas donde las columnas son el nombre de la unidad organizativa, el nombre de la computadora y el sistema operativo de la computadora. Solo consola web Neo4j.
MATCH (o:OU)-[:Contains]->(c:Computer) WHERE toUpper(o.name) CONTAINS "SERVER" RETURN o.name,c.name,c.operatingsystem
Ver las OUs según el número de miembros. (En la consola)
MATCH (o:OU)-[:Contains]->(c:Computer) RETURN o.name,o.guid,COUNT(c) ORDER BY COUNT(c) DESC
Devolver cada OU que tenga un servidor Windows en ella (En la consola)
MATCH (o:OU)-[:Contains]->(c:Computer) WHERE toUpper(c.operatingsystem) STARTS WITH "WINDOWS SERVER" RETURN o.name
Relaciones de confianza
Devolver las relaciones del cross domain con ‘HasSession’
MATCH p=((S:Computer)-[r:HasSession*1]->(T:User)) WHERE NOT S.domain = T.domain RETURN p
Delegaciones
Mostrar todos los usuarios que pueden realizar constrained delegation, devolver resultado ordenado por el numero de equipos targeteados
MATCH (u:User)-[:AllowedToDelegate]->(c:Computer) RETURN u.name,COUNT(c) ORDER BY COUNT(c) DESC
Encontrar la delegación constrained (en la consola)
MATCH (u:User)-[:AllowedToDelegate]->(c:Computer) RETURN u.name,COUNT(c) ORDER BY COUNT(c) DESC
Buscar equipos que permitan la delegación unconstrained y que NO sean controladores de dominio. (En la consola)
MATCH (c1:Computer)-[:MemberOf*1..]->(g:Group) WHERE g.objectsid ENDS WITH '-516' WITH COLLECT(c1.name) AS domainControllers MATCH (c2:Computer {unconstraineddelegation:true}) WHERE NOT c2.name IN domainControllers RETURN c2.name,c2.operatingsystem ORDER BY c2.name ASC
Encontrar usuarios que NO están marcados como “Sensibles y no pueden ser delegados” y tienen acceso administrativo a un equipo, y donde esos usuarios tienen sesiones en servidores con Delegación unconstrained habilitada (por NotMedic):
MATCH (u:User {sensitive:false})-[:MemberOf*1..]->(:Group)-[:AdminTo]->(c1:Computer) WITH u,c1 MATCH (c2:Computer {unconstraineddelegation:true})-[:HasSession]->(u) RETURN u.name AS user,COLLECT(DISTINCT(c1.name)) AS AdminTo,COLLECT(DISTINCT(c2.name)) AS TicketLocation ORDER BY user ASC
Buscar usuarios con permisos de delegación restringida y los objetivos correspondientes en los que se les permite delegar:
MATCH (u:User) WHERE u.allowedtodelegate IS NOT NULL RETURN u.name,u.allowedtodelegate
Alternativamente, buscar usuarios con permisos de delegación constrained, los objetivos correspondientes donde se les permite delegar, los usuarios con privilegios que pueden ser suplantados (basado en sensitive:false y admincount:true) y encontrar donde estos usuarios (con privilegios de delegación restringidos) tienen sesiones activas (user hunting) así como contar las rutas más cortas hacia ellos:
OPTIONAL MATCH (u:User {sensitive:false, admincount:true}) WITH u.name AS POSSIBLE_TARGETS OPTIONAL MATCH (n:User) WHERE n.allowedtodelegate IS NOT NULL WITH n AS USER_WITH_DELEG, n.allowedtodelegate as DELEGATE_TO, POSSIBLE_TARGETS OPTIONAL MATCH (c:Computer)-[:HasSession]->(USER_WITH_DELEG) WITH USER_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS,c.name AS USER_WITH_DELEG_HAS_SESSION_TO OPTIONAL MATCH p=shortestPath((o)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(USER_WITH_DELEG)) WHERE NOT o=USER_WITH_DELEG WITH USER_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS,USER_WITH_DELEG_HAS_SESSION_TO,p RETURN USER_WITH_DELEG.name AS USER_WITH_DELEG, DELEGATE_TO, COLLECT(DISTINCT(USER_WITH_DELEG_HAS_SESSION_TO)) AS USER_WITH_DELEG_HAS_SESSION_TO, COLLECT(DISTINCT(POSSIBLE_TARGETS)) AS PRIVILEGED_USERS_TO_IMPERSONATE, COUNT(DISTINCT(p)) AS PATHS_TO_USER_WITH_DELEG
Encontrar equipos con permisos de delegación constrained y los correspondientes objetivos en los que se les permite delegar:
MATCH (c:Computer) WHERE c.allowedtodelegate IS NOT NULL RETURN c.name,c.allowedtodelegate
Alternativamente, busca equipos con permisos de delegación constrained, los objetivos correspondientes donde se les permite delegar, los usuarios privilegiados que pueden ser suplantados (basado en sensitive:false y admincount:true) y encuentra quién es LocalAdmin en estos equipos así como cuenta las rutas más cortas hacia ellos:
OPTIONAL MATCH (u:User {sensitive:false, admincount:true}) WITH u.name AS POSSIBLE_TARGETS OPTIONAL MATCH (n:Computer) WHERE n.allowedtodelegate IS NOT NULL WITH n AS COMPUTERS_WITH_DELEG, n.allowedtodelegate as DELEGATE_TO, POSSIBLE_TARGETS OPTIONAL MATCH (u1:User)-[:AdminTo]->(COMPUTERS_WITH_DELEG) WITH u1 AS DIRECT_ADMINS,POSSIBLE_TARGETS,COMPUTERS_WITH_DELEG,DELEGATE_TO OPTIONAL MATCH (u2:User)-[:MemberOf*1..]->(:Group)-[:AdminTo]->(COMPUTERS_WITH_DELEG) WITH COLLECT(DIRECT_ADMINS) + COLLECT(u2) AS TempVar,COMPUTERS_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS UNWIND TempVar AS LOCAL_ADMINS OPTIONAL MATCH p=shortestPath((o)-[r:MemberOf|HasSession|AdminTo|AllExtendedRights|AddMember|ForceChangePassword|GenericAll|GenericWrite|Owns|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM|AllowedToDelegate|ReadLAPSPassword|Contains|GpLink|AddAllowedToAct|AllowedToAct*1..]->(COMPUTERS_WITH_DELEG)) WHERE NOT o=COMPUTERS_WITH_DELEG WITH COMPUTERS_WITH_DELEG,DELEGATE_TO,POSSIBLE_TARGETS,p,LOCAL_ADMINS RETURN COMPUTERS_WITH_DELEG.name AS COMPUTERS_WITH_DELG, LOCAL_ADMINS.name AS LOCAL_ADMINS_TO_COMPUTERS_WITH_DELG, DELEGATE_TO, COLLECT(DISTINCT(POSSIBLE_TARGETS)) AS PRIVILEGED_USERS_TO_IMPERSONATE, COUNT(DISTINCT(p)) AS PATHS_TO_USER_WITH_DELEG
Otros
Mostrar todas las sesiones activas de DA.
MATCH (n:User)-[:MemberOf*1..]->(g:Group) WHERE g.objectid ENDS WITH '-512' MATCH p = (c:Computer)-[:HasSession]->(n) return p
Encontrar todas las sesiones activas que tiene un miembro de un grupo.
MATCH (n:User)-[:MemberOf*1..]->(g:Group {name:'DOMAIN ADMINS@TESTLAB.LOCAL'}) MATCH p = (c:Computer)-[:HasSession]->(n) return p
¿Puede un objeto del dominio “A” hacer algo a un objeto del dominio “B”?
MATCH (n {domain:"TEST.LOCAL"})-[r]->(m {domain:"LAB.LOCAL"}) RETURN LABELS(n)[0],n.name,TYPE(r),LABELS(m)[0],m.name
Encontrar todas las conexiones a un dominio/bosque diferente.
MATCH (n)-[r]->(m) WHERE NOT n.domain = m.domain RETURN LABELS(n)[0],n.name,TYPE(r),LABELS(m)[0],m.name
Sacar los usuarios que no son AdminCount 1, tienen generic all y no tienen admin local .
MATCH (u:User)-[:GenericAll]->(c:Computer) WHERE NOT u.admincount AND NOT (u)-[:AdminTo]->(c) RETURN u.name, c.name
Ajustar la consulta a la zona horaria local (cambiar el parámetro de zona horaria).
MATCH (u:User) WHERE NOT u.lastlogon IN [-1.0, 0.0] return u.name, datetime({epochSeconds:toInteger(u.lastlogon), timezone: '+10:00'}) as LastLogon
Fuentes:
- https://gist.github.com/seajaysec/c7f0995b5a6a2d30515accde8513f77d
- https://www.whiteoaksecurity.com/blog/cypher-query-primer-bloodhound/
- https://hausec.com/2019/09/09/bloodhound-cypher-cheatsheet/
- https://phackt.com/pentesting-bloodhound-cypher-queries
- https://gist.github.com/seajaysec/a4d4a545047a51053d52cba567f78a9b
- https://infinitelogins.com/2022/01/28/bloodhound-cheatsheet-custom-queries-neo4j-lookups/
- https://stmxcsr.com/micro/bh-cypher-queries.html
- https://github.com/CompassSecurity/BloodHoundQueries
Powered by WPeMatico