Seguridad

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

Equipo o computadora usados indistintamente…
 
Enumerar todas las propiedades (en la consola)

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:

Powered by WPeMatico

Gustavo Genez

Informático de corazón y apasionado por la tecnología. La misión de este blog es llegar a los usuarios y profesionales con información y trucos acerca de la Seguridad Informática.