|
21 | 21 | UF_DONT_REQUIRE_PREAUTH, |
22 | 22 | UF_TRUSTED_FOR_DELEGATION, |
23 | 23 | UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, |
| 24 | + UF_SERVER_TRUST_ACCOUNT, |
24 | 25 | ) |
25 | 26 | from impacket.dcerpc.v5.transport import DCERPCTransportFactory |
26 | 27 | from impacket.krb5 import constants |
27 | 28 | from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError |
28 | 29 | from impacket.krb5.types import Principal, KerberosException |
29 | 30 | from impacket.ldap import ldap as ldap_impacket |
| 31 | +from impacket.ldap import ldaptypes |
30 | 32 | from impacket.ldap import ldapasn1 as ldapasn1_impacket |
31 | 33 | from impacket.ldap.ldap import LDAPFilterSyntaxError |
32 | 34 | from impacket.smb import SMB_DIALECT |
@@ -1084,6 +1086,107 @@ def query(self): |
1084 | 1086 | vals = vals.replace("SetOf: ", "") |
1085 | 1087 | self.logger.highlight(f"{attr:<20} {vals}") |
1086 | 1088 |
|
| 1089 | + def find_delegation(self): |
| 1090 | + def printTable(items, header): |
| 1091 | + colLen = [] |
| 1092 | + |
| 1093 | + # Calculating maximum lenght before parsing CN. |
| 1094 | + for i, col in enumerate(header): |
| 1095 | + rowMaxLen = max(len(row[1].split(",")[0].split("CN=")[-1]) for row in items) if i == 1 else max(len(str(row[i])) for row in items) |
| 1096 | + colLen.append(max(rowMaxLen, len(col))) |
| 1097 | + |
| 1098 | + # Create the format string for each row |
| 1099 | + outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) |
| 1100 | + |
| 1101 | + # Print header |
| 1102 | + self.logger.highlight(outputFormat.format(*header)) |
| 1103 | + self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) |
| 1104 | + |
| 1105 | + # Print rows |
| 1106 | + for row in items: |
| 1107 | + # Get first CN value. |
| 1108 | + if "CN=" in row[1]: |
| 1109 | + row[1] = row[1].split(",")[0].split("CN=")[-1] |
| 1110 | + |
| 1111 | + # Added join for DelegationRightsTo |
| 1112 | + row[3] = ", ".join(str(x) for x in row[3]) if isinstance(row[3], list) else row[3] |
| 1113 | + |
| 1114 | + self.logger.highlight(outputFormat.format(*row)) |
| 1115 | + |
| 1116 | + # Building the search filter |
| 1117 | + search_filter = (f"(&(|(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION})" |
| 1118 | + f"(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_FOR_DELEGATION})" |
| 1119 | + "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" |
| 1120 | + f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))") |
| 1121 | + # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_SERVER_TRUST_ACCOUNT})))") This would filter out RBCD to DCs |
| 1122 | + |
| 1123 | + attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", |
| 1124 | + "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] |
| 1125 | + |
| 1126 | + resp = self.search(search_filter, attributes) |
| 1127 | + answers = [] |
| 1128 | + self.logger.debug(f"Total of records returned {len(resp):d}") |
| 1129 | + resp_parse = parse_result_attributes(resp) |
| 1130 | + |
| 1131 | + for item in resp_parse: |
| 1132 | + sAMAccountName = "" |
| 1133 | + userAccountControl = 0 |
| 1134 | + delegation = "" |
| 1135 | + objectType = "" |
| 1136 | + rightsTo = [] |
| 1137 | + protocolTransition = 0 |
| 1138 | + |
| 1139 | + try: |
| 1140 | + sAMAccountName = item["sAMAccountName"] |
| 1141 | + |
| 1142 | + userAccountControl = int(item["userAccountControl"]) |
| 1143 | + objectType = item.get("objectCategory") |
| 1144 | + |
| 1145 | + # Filter out DCs, unconstrained delegation to DCs is not a useful information |
| 1146 | + if userAccountControl & UF_TRUSTED_FOR_DELEGATION and not userAccountControl & UF_SERVER_TRUST_ACCOUNT: |
| 1147 | + delegation = "Unconstrained" |
| 1148 | + rightsTo.append("N/A") |
| 1149 | + elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: |
| 1150 | + delegation = "Constrained w/ Protocol Transition" |
| 1151 | + protocolTransition = 1 |
| 1152 | + |
| 1153 | + if item.get("msDS-AllowedToDelegateTo") is not None: |
| 1154 | + if protocolTransition == 0: |
| 1155 | + delegation = "Constrained" |
| 1156 | + rightsTo = item.get("msDS-AllowedToDelegateTo") |
| 1157 | + |
| 1158 | + # Not an elif as an object could both have RBCD and another type of delegation |
| 1159 | + if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: |
| 1160 | + databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") |
| 1161 | + rbcdRights = [] |
| 1162 | + rbcdObjType = [] |
| 1163 | + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) |
| 1164 | + if len(sd["Dacl"].aces) > 0: |
| 1165 | + search_filter = "(&(|" |
| 1166 | + for ace in sd["Dacl"].aces: |
| 1167 | + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" |
| 1168 | + search_filter += f")(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))" |
| 1169 | + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"]) |
| 1170 | + delegUserResp_parse = parse_result_attributes(delegUserResp) |
| 1171 | + |
| 1172 | + for rbcd in delegUserResp_parse: |
| 1173 | + rbcdRights.append(str(rbcd.get("sAMAccountName"))) |
| 1174 | + rbcdObjType.append(str(rbcd.get("objectCategory"))) |
| 1175 | + |
| 1176 | + for rights, objType in zip(rbcdRights, rbcdObjType): |
| 1177 | + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) |
| 1178 | + |
| 1179 | + if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: |
| 1180 | + answers.append([sAMAccountName, objectType, delegation, rightsTo]) |
| 1181 | + |
| 1182 | + except Exception as e: |
| 1183 | + self.logger.error(f"Skipping item, cannot process due to error {e}") |
| 1184 | + |
| 1185 | + if answers: |
| 1186 | + printTable(answers, header=["AccountName", "AccountType", "DelegationType", "DelegationRightsTo"]) |
| 1187 | + else: |
| 1188 | + self.logger.fail("No entries found!") |
| 1189 | + |
1087 | 1190 | def trusted_for_delegation(self): |
1088 | 1191 | # Building the search filter |
1089 | 1192 | searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" |
|
0 commit comments