@@ -410,32 +410,171 @@ def get_critical_aces(self, source_domain: str, high_value: bool = False,
410410 return []
411411
412412 def get_access_paths (self , source : str , connection : str , target : str , domain : str ) -> List [Dict ]:
413- """Get access paths using CySQL query"""
413+ """Get access paths using CySQL query - adapted from old_main.py """
414414 try :
415- cypher_query = f"""
416- MATCH path = (s)-[*1..10]->(t)
417- WHERE s.name = '{ source } ' AND t.name = '{ target } '
418- RETURN path
419- """
415+ # Determine relationship conditions
416+ if connection .lower () == "all" :
417+ rel_condition = "AND type(r) IN ['AdminTo','CanRDP','CanPSRemote']"
418+ rel_pattern = "[r]->"
419+ else :
420+ rel_condition = ""
421+ rel_pattern = f"[r:{ connection } ]->"
422+
423+ # Case 1: source != "all" and target == "all" - find what source can access
424+ if source .lower () != "all" and target .lower () == "all" :
425+ cypher_query = f"""
426+ MATCH p = (n)-{ rel_pattern } (m)
427+ WHERE toLower(n.samaccountname) = toLower('{ source } ')
428+ AND toLower(n.domain) = toLower('{ domain } ')
429+ AND m.enabled = true
430+ { rel_condition }
431+ RETURN n.samaccountname AS source, m.samaccountname AS target, type(r) AS relation
432+ """
433+
434+ # Case 2: source == "all" and target == "all" - find all access paths in domain
435+ elif source .lower () == "all" and target .lower () == "all" :
436+ cypher_query = f"""
437+ MATCH p = (n)-{ rel_pattern } (m)
438+ WHERE toLower(n.domain) = toLower('{ domain } ')
439+ AND n.enabled = true
440+ AND m.enabled = true
441+ { rel_condition }
442+ RETURN n.samaccountname AS source, m.samaccountname AS target, type(r) AS relation
443+ """
444+
445+ # Case 3: source != "all" and target == "dcs" - find users with DC access
446+ elif source .lower () != "all" and target .lower () == "dcs" :
447+ cypher_query = f"""
448+ MATCH p = (n)-{ rel_pattern } (m)
449+ WHERE toLower(n.samaccountname) = toLower('{ source } ')
450+ AND toLower(n.domain) = toLower('{ domain } ')
451+ AND m.enabled = true
452+ AND (m.operatingsystem CONTAINS 'Windows Server' OR m.operatingsystem CONTAINS 'Domain Controller')
453+ { rel_condition }
454+ RETURN n.samaccountname AS source, m.samaccountname AS target, type(r) AS relation
455+ """
456+
457+ # Case 4: source == "all" and target == "dcs" - find all users with DC access
458+ elif source .lower () == "all" and target .lower () == "dcs" :
459+ cypher_query = f"""
460+ MATCH p = (n)-{ rel_pattern } (m)
461+ WHERE toLower(n.domain) = toLower('{ domain } ')
462+ AND n.enabled = true
463+ AND m.enabled = true
464+ AND (m.operatingsystem CONTAINS 'Windows Server' OR m.operatingsystem CONTAINS 'Domain Controller')
465+ { rel_condition }
466+ RETURN n.samaccountname AS source, m.samaccountname AS target, type(r) AS relation
467+ """
468+
469+ # Case 5: specific source to specific target
470+ else :
471+ cypher_query = f"""
472+ MATCH p = (n)-{ rel_pattern } (m)
473+ WHERE toLower(n.samaccountname) = toLower('{ source } ')
474+ AND toLower(n.domain) = toLower('{ domain } ')
475+ AND toLower(m.samaccountname) = toLower('{ target } ')
476+ AND m.enabled = true
477+ { rel_condition }
478+ RETURN n.samaccountname AS source, m.samaccountname AS target, type(r) AS relation
479+ """
420480
421481 result = self .execute_query (cypher_query )
422482 paths = []
423483
424484 if result and isinstance (result , list ):
425- for path_data in result :
426- # Process path data - this might need adjustment based on actual CySQL response format
427- if isinstance (path_data , dict ):
485+ for record in result :
486+ source_name = record .get ('source' , '' )
487+ target_name = record .get ('target' , '' )
488+ relation = record .get ('relation' , '' )
489+
490+ if source_name and target_name :
491+ # Extract just the username part (before @) if it's in UPN format
492+ if "@" in source_name :
493+ source_name = source_name .split ("@" )[0 ]
494+ if "@" in target_name :
495+ target_name = target_name .split ("@" )[0 ]
496+
428497 paths .append ({
429- "source" : source ,
430- "target" : target ,
431- "path" : path_data
498+ "source" : source_name ,
499+ "target" : target_name ,
500+ "relation" : relation ,
501+ "path" : f"{ source_name } -> { target_name } ({ relation } )"
432502 })
433503
434504 return paths
435505
436506 except Exception :
437507 return []
438508
509+ def get_users_with_dc_access (self , domain : str ) -> List [Dict ]:
510+ """Get users who have access to Domain Controllers"""
511+ try :
512+ # First try to find actual DCs
513+ cypher_query = f"""
514+ MATCH (u:User)-[r]->(dc:Computer)
515+ WHERE u.enabled = true AND toUpper(u.domain) = '{ domain .upper ()} '
516+ AND dc.enabled = true AND toUpper(dc.domain) = '{ domain .upper ()} '
517+ AND (dc.operatingsystem CONTAINS 'Windows Server' OR dc.operatingsystem CONTAINS 'Domain Controller')
518+ RETURN u.samaccountname AS user, dc.name AS dc, type(r) AS relation
519+ """
520+
521+ result = self .execute_query (cypher_query )
522+ users_with_access = []
523+
524+ if result and isinstance (result , list ):
525+ for record in result :
526+ user = record .get ('user' , '' )
527+ dc = record .get ('dc' , '' )
528+ relation = record .get ('relation' , '' )
529+
530+ if user and dc :
531+ # Extract just the username part (before @) if it's in UPN format
532+ if "@" in user :
533+ user = user .split ("@" )[0 ]
534+ if "@" in dc :
535+ dc = dc .split ("@" )[0 ]
536+
537+ users_with_access .append ({
538+ "source" : user ,
539+ "target" : dc ,
540+ "path" : f"{ user } -> { dc } ({ relation } )"
541+ })
542+
543+ # If no DCs found, try to find any user-computer relationships
544+ if not users_with_access :
545+ fallback_query = f"""
546+ MATCH (u:User)-[r]->(c:Computer)
547+ WHERE u.enabled = true AND toUpper(u.domain) = '{ domain .upper ()} '
548+ AND c.enabled = true AND toUpper(c.domain) = '{ domain .upper ()} '
549+ RETURN u.samaccountname AS user, c.name AS computer, type(r) AS relation
550+ """
551+
552+ result = self .execute_query (fallback_query )
553+
554+ if result and isinstance (result , list ):
555+ for record in result :
556+ user = record .get ('user' , '' )
557+ computer = record .get ('computer' , '' )
558+ relation = record .get ('relation' , '' )
559+
560+ if user and computer :
561+ # Extract just the username part (before @) if it's in UPN format
562+ if "@" in user :
563+ user = user .split ("@" )[0 ]
564+ if "@" in computer :
565+ computer = computer .split ("@" )[0 ]
566+
567+ users_with_access .append ({
568+ "source" : user ,
569+ "target" : computer ,
570+ "path" : f"{ user } -> { computer } ({ relation } )"
571+ })
572+
573+ return users_with_access
574+
575+ except Exception :
576+ return []
577+
439578 def get_critical_aces_by_domain (self , domain : str , blacklist : List [str ],
440579 high_value : bool = False ) -> List [Dict ]:
441580 """Get critical ACEs by domain using CySQL query"""
0 commit comments