@@ -103,6 +103,52 @@ def is_sid(value: str) -> bool:
103103 return bool (re .match (pattern , value .strip ()))
104104
105105
106+ def get_domain_sid_prefix (sid : str ) -> Optional [str ]:
107+ """
108+ Extract domain SID prefix from a full SID.
109+
110+ Domain SIDs have the format: S-1-5-21-{domain1}-{domain2}-{domain3}-{RID}
111+ The domain prefix is S-1-5-21-{domain1}-{domain2}-{domain3} (without RID).
112+
113+ Args:
114+ sid: Full SID string (e.g., "S-1-5-21-123-456-789-1001")
115+
116+ Returns:
117+ Domain prefix (e.g., "S-1-5-21-123-456-789") or None if not a domain SID
118+ """
119+ if not sid or not sid .startswith ("S-1-5-21-" ):
120+ return None
121+
122+ parts = sid .split ("-" )
123+ # Domain SID: S-1-5-21-{d1}-{d2}-{d3}-{RID} = 8 parts minimum
124+ if len (parts ) < 8 :
125+ return None
126+
127+ # Return everything except the RID (last part)
128+ return "-" .join (parts [:- 1 ])
129+
130+
131+ def is_foreign_domain_sid (sid : str , local_domain_sid_prefix : Optional [str ]) -> bool :
132+ """
133+ Check if a SID belongs to a foreign (trusted) domain.
134+
135+ Args:
136+ sid: SID to check
137+ local_domain_sid_prefix: Known local domain prefix (e.g., "S-1-5-21-123-456-789")
138+
139+ Returns:
140+ True if SID is from a different domain than local_domain_sid_prefix
141+ """
142+ if not local_domain_sid_prefix :
143+ return False # Can't determine without local domain info
144+
145+ sid_prefix = get_domain_sid_prefix (sid )
146+ if not sid_prefix :
147+ return False # Not a domain SID (built-in, well-known, etc.)
148+
149+ return sid_prefix != local_domain_sid_prefix
150+
151+
106152def sid_to_binary (sid_string : str ) -> Optional [bytes ]:
107153 """
108154 Convert a SID string (S-1-5-21-...) to binary format for LDAP queries.
@@ -651,6 +697,7 @@ def resolve_sid(
651697 ldap_user : Optional [str ] = None ,
652698 ldap_password : Optional [str ] = None ,
653699 ldap_hashes : Optional [str ] = None ,
700+ local_domain_sid_prefix : Optional [str ] = None ,
654701) -> Tuple [str , Optional [str ]]:
655702 """
656703 Comprehensive SID resolution with 4-tier fallback chain.
@@ -659,6 +706,7 @@ def resolve_sid(
659706 1. BloodHound offline data (JSON file from --bloodhound flag)
660707 2. BloodHound live API (if bh_connector provided and has active connection)
661708 3. SMB/LSARPC via existing connection (uses target's LSA to resolve SIDs)
709+ - SKIPPED for foreign domain SIDs (different domain prefix)
662710 4. LDAP queries to domain controller (if credentials provided and not disabled)
663711
664712 Args:
@@ -677,6 +725,7 @@ def resolve_sid(
677725 ldap_user: Separate LDAP username (for local admin case)
678726 ldap_password: Separate LDAP password (for local admin case - plaintext only)
679727 ldap_hashes: Separate LDAP NTLM hashes (for local admin case - use instead of ldap_password)
728+ local_domain_sid_prefix: Known local domain SID prefix for foreign domain detection
680729
681730 Returns:
682731 Tuple of (display_name, resolved_username)
@@ -725,9 +774,15 @@ def _cache_success(resolved_name):
725774 _cache_success (resolved )
726775 return f"{ resolved } ({ sid } )" , resolved
727776
728- # Tier 3: Try SMB/LSARPC if connection available
777+ # Check for foreign domain SID before attempting LSARPC
778+ # Foreign SIDs cannot be resolved via local domain's LSARPC (returns STATUS_NONE_MAPPED)
779+ is_foreign = is_foreign_domain_sid (sid , local_domain_sid_prefix )
780+ if is_foreign :
781+ debug (f"SID { sid } is from foreign domain (prefix mismatch with { local_domain_sid_prefix } ), skipping LSARPC" )
782+
783+ # Tier 3: Try SMB/LSARPC if connection available (skip for foreign domain SIDs)
729784 # This is very useful when using Kerberos CIFS tickets where LDAP auth might fail
730- if smb_connection :
785+ if smb_connection and not is_foreign :
731786 resolved = resolve_sid_via_smb (sid , smb_connection )
732787 if resolved :
733788 debug (f"SID { sid } resolved via SMB/LSARPC: { resolved } " )
@@ -772,9 +827,12 @@ def _cache_success(resolved_name):
772827 elif not ldap_auth_domain or not ldap_auth_user :
773828 debug (f"SID { sid } not resolved: insufficient authentication information" )
774829 return f"{ sid } (SID - insufficient auth for LDAP resolution)" , None
830+ elif is_foreign :
831+ debug (f"SID { sid } not resolved: foreign domain SID (cross-trust)" )
832+ return f"{ sid } (SID - foreign domain/trust)" , None
775833 else :
776834 debug (f"SID { sid } not resolved: could not find in BloodHound offline, BloodHound API, SMB, or LDAP" )
777- return f"{ sid } (SID - could not resolve: deleted user, cross-domain, or access denied)" , None
835+ return f"{ sid } (SID - could not resolve: deleted user or access denied)" , None
778836
779837
780838def batch_get_user_attributes (
@@ -1035,6 +1093,7 @@ def format_runas_with_sid_resolution(
10351093 ldap_user : Optional [str ] = None ,
10361094 ldap_password : Optional [str ] = None ,
10371095 ldap_hashes : Optional [str ] = None ,
1096+ local_domain_sid_prefix : Optional [str ] = None ,
10381097) -> Tuple [str , Optional [str ]]:
10391098 """
10401099 Format RunAs field with SID resolution if needed.
@@ -1055,6 +1114,7 @@ def format_runas_with_sid_resolution(
10551114 ldap_user: Separate LDAP username (for local admin case)
10561115 ldap_password: Separate LDAP password (for local admin case - plaintext only)
10571116 ldap_hashes: Separate LDAP NTLM hashes (for local admin case - use instead of ldap_password)
1117+ local_domain_sid_prefix: Known local domain SID prefix for foreign domain detection
10581118
10591119 Returns:
10601120 Tuple of (display_runas, resolved_username)
@@ -1082,14 +1142,14 @@ def format_runas_with_sid_resolution(
10821142 ldap_user ,
10831143 ldap_password ,
10841144 ldap_hashes ,
1145+ local_domain_sid_prefix ,
10851146 )
10861147 else :
10871148 # Regular username, return as-is
10881149 return runas , None
10891150
10901151
10911152
1092-
10931153# Well-known privileged group RIDs (relative to domain SID)
10941154# These are the primary Tier-0 groups that grant domain-wide administrative access
10951155TIER0_GROUP_RIDS = {
0 commit comments