Skip to content

Commit

Permalink
feat: Kerberos unconstrained delegation control rework
Browse files Browse the repository at this point in the history
Co-authored-by: snowpeacock <74353652+snowpeacock@users.noreply.github.com>
  • Loading branch information
dreamkinn and snowpeacock committed Dec 8, 2023
1 parent 7d09fcb commit 0eef50f
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 251 deletions.
9 changes: 4 additions & 5 deletions ad_miner/__main__.py
Expand Up @@ -153,12 +153,11 @@ def main() -> None:
string_information_database = ""

for type_label in total_objects:
if 'Base' in type_label['labels(x)'][0]:
instance_type_label = 1
else:
instance_type_label = 0
if 'Base' in type_label['labels(x)']: type_label['labels(x)'].remove('Base')
if 'AZBase' in type_label['labels(x)']: type_label['labels(x)'].remove('AZBase')

string_information_database += f"{type_label['labels(x)'][instance_type_label]} : {type_label['number_type']} | "
if len(type_label['labels(x)']) != 0:
string_information_database += f"{type_label['labels(x)'][0]} : {type_label['number_type']} | "

string_information_database += f"Relations : {number_relations}"
logger.print_magenta(string_information_database)
Expand Down
23 changes: 0 additions & 23 deletions ad_miner/sources/modules/computers.py
Expand Up @@ -93,7 +93,6 @@ def __init__(self, arguments, neo4j, domain):
self.generateComputersListPage()
self.generateADCSListPage()
self.genObsoleteOSPage()
self.genNonDCWithUnconstrainedPage()
self.genUsersConstrainedPage()
self.genComputersAdminOfPages()
self.genComputersWithMostAdminsPage()
Expand Down Expand Up @@ -232,28 +231,6 @@ def genObsoleteOSPage(self):
page.render()
self.list_computers_os_obsolete = cleaned_data

# Non DC computers and users with unconstrained delegations
def genNonDCWithUnconstrainedPage(self):
if self.list_computers_unconstrained_delegations is None:
return
page = Page(
self.arguments.cache_prefix,
"non-dc_with_unconstrained_delegations",
"Non-DC with unconstrained delegations",
"non-dc_with_unconstrained_delegations",
)
grid = Grid("Non-DC with unconstrained delegations")
grid.setheaders(["domain", "name"])
for d in self.computers_non_dc_unconstrained_delegations:
d["domain"] = '<i class="bi bi-globe2"></i> ' + d["domain"]
d["name"] = '<i class="bi bi-pc-display"></i> ' + d["name"]
for d in self.users_non_dc_unconstrained_delegations:
d["domain"] = '<i class="bi bi-globe2"></i> ' + d["domain"]
d["name"] = '<i class="bi bi-person-fill"></i> ' + d["name"]
grid.setData(self.computers_non_dc_unconstrained_delegations + self.users_non_dc_unconstrained_delegations)
page.addComponent(grid)
page.render()


# Users with constrained delegations
def genUsersConstrainedPage(self):
Expand Down
4 changes: 3 additions & 1 deletion ad_miner/sources/modules/config.json
Expand Up @@ -125,6 +125,8 @@
"azure_vm": "true",
"azure_users_paths_high_target": "true",
"azure_ms_graph_controllers": "true",
"azure_aadconnect_users": "true"
"azure_aadconnect_users": "true",
"kud":"true",
"set_target_kud":"true"
}
}
6 changes: 3 additions & 3 deletions ad_miner/sources/modules/description.json
Expand Up @@ -220,9 +220,9 @@
},

"non-dc_with_unconstrained_delegations": {
"title": "Accounts with unconstrained delegations",
"description": "These accounts are allowed to connect to any service with the identity of another user who connected to them.",
"risk": "These accounts can impersonate any domain and eventually lead to full compromise of the infrastructure. Optimally, this list should be empty as delegation should be set up with constrained delegation.",
"title": "Objects with unconstrained delegations",
"description": "These objects are allowed to connect to any service with the identity of another user who connected to them.",
"risk": "These objects can impersonate any domain and eventually lead to full compromise of the infrastructure. Optimally, this list should be empty as delegation should be set up with constrained delegation.",
"poa": "Unless necessary, switch to constrained delegation for a safer infrastructure."
},

Expand Down
266 changes: 51 additions & 215 deletions ad_miner/sources/modules/domains.py
Expand Up @@ -9,6 +9,7 @@
from ad_miner.sources.modules.grid_class import Grid
from ad_miner.sources.modules.histogram_class import Histogram
from ad_miner.sources.modules.node_neo4j import Node
from ad_miner.sources.modules.path_neo4j import Path
from ad_miner.sources.modules.page_class import Page
from ad_miner.sources.modules.utils import (days_format, grid_data_stringify,
timer_format)
Expand Down Expand Up @@ -159,6 +160,7 @@ def __init__(self, arguments, neo4j):
self.empty_groups = neo4j.all_requests["get_empty_groups"]["result"]
self.empty_ous = neo4j.all_requests["get_empty_ous"]["result"]

self.kud = neo4j.all_requests["kud"]["result"]

self.computers_to_domain_admin = {}
self.users_to_domain_admin = {}
Expand Down Expand Up @@ -196,6 +198,8 @@ def __init__(self, arguments, neo4j):
# init variables : var[domain] = list

self.paths_to_ou_handlers = {}
self.kud_graphs = {}
self.kud_list = []

for domain in self.domains:

Expand Down Expand Up @@ -239,10 +243,7 @@ def __init__(self, arguments, neo4j):
self.generatePathToDa()

# self.generatePathToDcsync()

self.generatePathToUnconstrainedDelegation()

self.generatePathToUnconstrainedDelegation_2()
self.generatePathToKUD()

self.generateDomainMapTrust()

Expand Down Expand Up @@ -1025,229 +1026,64 @@ def generateDomainMapTrust(self):
self.domain_map_trust,
)

def generatePathToUnconstrainedDelegation(self):
if self.objects_to_unconstrained_delegation is None:
def generatePathToKUD(self):
if self.kud is None:
return
logger.print_debug("Generate paths to unconstrained delegations")
for path in self.objects_to_unconstrained_delegation:

if "User" in path.nodes[0].labels:
self.users_to_unconstrained_delegation[path.nodes[-1].domain].append(
path
)
elif "Computer" in path.nodes[0].labels:
self.computers_to_unconstrained_delegation[path.nodes[-1].domain].append(
path
)
elif "Group" in path.nodes[0].labels:
self.groups_to_unconstrained_delegation[path.nodes[-1].domain].append(
path
)
elif "OU" in path.nodes[0].labels:
self.ou_to_unconstrained_delegation[path.nodes[-1].domain].append(
path)
elif "GPO" in path.nodes[0].labels:
self.gpo_to_unconstrained_delegation[path.nodes[-1].domain].append(
path)

for domain in self.domains:
domain = domain[0]
logger.print_debug("Doing domain " + domain)
if len(self.users_to_unconstrained_delegation[domain]):
logger.print_debug("... from users")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_users_to_unconstrained_delegation",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.users_to_unconstrained_delegation[domain],
)
if len(self.computers_to_unconstrained_delegation[domain]):
logger.print_debug("... from Computers")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_computers_to_unconstrained_delegation",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.computers_to_unconstrained_delegation[domain],
)
if len(self.groups_to_unconstrained_delegation[domain]):
logger.print_debug("... from Groups")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_groups_to_unconstrained_delegation",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.groups_to_unconstrained_delegation[domain],
)
if len(self.ou_to_unconstrained_delegation[domain]):
logger.print_debug("... from OUs")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_OU_to_unconstrained_delegation",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.ou_to_unconstrained_delegation[domain],
)
if len(self.gpo_to_unconstrained_delegation[domain]):
logger.print_debug("... from GPOs")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_GPO_to_unconstrained_delegation",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.gpo_to_unconstrained_delegation[domain],
)
logger.print_debug("Generate paths to Kerberos Unconstrained Delegations")

# generating graph object to unconstrained delegation grid
for path in self.kud:
if not self.kud_graphs.get(path.nodes[-1].name):
self.kud_graphs[path.nodes[-1].name] = [path]
else:
self.kud_graphs[path.nodes[-1].name].append(path)

page = Page(
self.arguments.cache_prefix,
"graph_path_objects_to_unconstrained_delegation",
"Path to unconstrained delegation",
"graph_path_objects_to_unconstrained_delegation",
"non-dc_with_unconstrained_delegations",
"Path to Unconstrained Delegations",
"non-dc_with_unconstrained_delegations",
)
grid = Grid("Numbers of path to domain admin per domain and objects")
grid = Grid("Numbers of path to domain admin using Kerberos Unconstrained Delegations")
grid_data = []
for domain in self.domains:
domain = domain[0]
tmp_data = {}
tmp_data["Domain"] = domain
tmp_data["Users"] = {
"value": len(self.users_to_unconstrained_delegation[domain]),
"link": "%s_users_to_unconstrained_delegation.html" % quote(str(domain)),
}
tmp_data["Computers"] = {
"value": len(self.computers_to_unconstrained_delegation[domain]),
"link": "%s_computers_to_unconstrained_delegation.html" % quote(str(domain)),
}
tmp_data["Groups"] = {
"value": len(self.groups_to_unconstrained_delegation[domain]),
"link": "%s_groups_to_unconstrained_delegation.html" % quote(str(domain)),
}
tmp_data["Ou"] = {
"value": len(self.ou_to_unconstrained_delegation[domain]),
"link": "%s_OU_to_unconstrained_delegation.html" % quote(str(domain)),
}
tmp_data["GPO"] = {
"value": len(self.gpo_to_unconstrained_delegation[domain]),
"link": "%s_GPO_to_unconstrained_delegation.html" % quote(str(domain)),
}
grid_data.append(tmp_data)
headers = ["Domain", "Users", "Computers", "Groups", "Ou", "GPO"]
grid.setheaders(headers)
grid.setData(grid_data)
page.addComponent(grid)
page.render()

def generatePathToUnconstrainedDelegation_2(self):
if self.objects_to_unconstrained_delegation_2 is None:
return
logger.print_debug("Generating path to unconstrained 2nd phase ????")
for path in self.objects_to_unconstrained_delegation_2:

if "User" in path.nodes[0].labels:
self.users_to_unconstrained_delegation_2[path.nodes[-1].domain].append(
path
)
elif "Computer" in path.nodes[0].labels:
self.computers_to_unconstrained_delegation_2[
path.nodes[-1].domain
].append(path)
elif "Group" in path.nodes[0].labels:
self.groups_to_unconstrained_delegation_2[path.nodes[-1].domain].append(
path
)
elif "OU" in path.nodes[0].labels:
self.ou_to_unconstrained_delegation_2[path.nodes[-1].domain].append(
path)
elif "GPO" in path.nodes[0].labels:
self.gpo_to_unconstrained_delegation_2[path.nodes[-1].domain].append(
path
)

self.kud_list = self.kud_graphs.keys()

for end_node in self.kud_list :
# if len(self.kud_graphs[end_node]):
node = self.kud_graphs[end_node][0].nodes[-1]
node.relation_type = "UnconstrainedDelegations"
domain = node.domain
end = Node(
id=42424243, labels="Domain", name=domain, domain="end", relation_type="UnconstrainedDelegations"
)
path = Path([self.kud_graphs[end_node][0].nodes[-1], end])
self.kud_graphs[end_node].append(path)

for domain in self.domains:
domain = domain[0]
if len(self.users_to_unconstrained_delegation_2[domain]):
logger.print_debug("... from users")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_users_to_unconstrained_delegation_users",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.users_to_unconstrained_delegation_2[domain],
)
if len(self.computers_to_unconstrained_delegation_2[domain]):
logger.print_debug("... from Computers")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_computers_to_unconstrained_delegation_users",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.computers_to_unconstrained_delegation_2[domain],
)
if len(self.groups_to_unconstrained_delegation_2[domain]):
logger.print_debug("... from Groups")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_groups_to_unconstrained_delegation_users",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.groups_to_unconstrained_delegation_2[domain],
)
if len(self.ou_to_unconstrained_delegation_2[domain]):
logger.print_debug("... from OUs")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_OU_to_unconstrained_delegation_users",
"Paths to unconstrained delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.ou_to_unconstrained_delegation_2[domain],
)
if len(self.gpo_to_unconstrained_delegation_2[domain]):
logger.print_debug("... from GPOs")
self.createGraphPage(
self.arguments.cache_prefix,
domain + "_GPO_to_unconstrained_delegation_users",
"Paths to unconstrained delegations",
self.createGraphPage(
self.arguments.cache_prefix,
end_node + "_kud_graph",
"Paths to Unconstrained Delegations",
"graph_path_objects_to_unconstrained_delegation_users",
self.gpo_to_unconstrained_delegation_2[domain],
self.kud_graphs[end_node]
)

# generating graph object to unconstrained delegation grid
page = Page(
self.arguments.cache_prefix,
"graph_path_objects_to_unconstrained_delegation_users",
"Path to unconstrained delegation",
"graph_path_objects_to_unconstrained_delegation_users",
)
grid = Grid("Numbers of path to domain admin per domain and objects")
grid_data = []
for domain in self.domains:
domain = domain[0]
tmp_data = {}
tmp_data["Domain"] = domain
tmp_data["Users"] = {
"value": len(self.users_to_unconstrained_delegation_2[domain]),
"link": "%s_users_to_unconstrained_delegation_users.html" % quote(str(domain)),
}
tmp_data["Computers"] = {
"value": len(self.computers_to_unconstrained_delegation_2[domain]),
"link": "%s_computers_to_unconstrained_delegation_users.html" % quote(str(domain)),
}
tmp_data["Groups"] = {
"value": len(self.groups_to_unconstrained_delegation_2[domain]),
"link": "%s_groups_to_unconstrained_delegation_users.html" % quote(str(domain)),
}
tmp_data["Ou"] = {
"value": len(self.ou_to_unconstrained_delegation_2[domain]),
"link": "%s_OU_to_unconstrained_delegation_users.html" % quote(str(domain)),
}
tmp_data["GPO"] = {
"value": len(self.gpo_to_unconstrained_delegation_2[domain]),
"link": "%s_GPO_to_unconstrained_delegation_users.html" % quote(str(domain)),
}

if node.labels == "User":
pretty_name = f'<i class="bi bi-person-fill"></i> {end_node}'
elif node.labels == "Computer":
pretty_name = f'<i class="bi bi-pc-display"></i> {end_node}'
else:
pretty_name = end_node

tmp_data["Configured for Kerberos Unconstrained Delegation"] = pretty_name
tmp_data["Compromise Paths"] = grid_data_stringify({
"value": f'{len(self.kud_graphs[end_node])} <i class="bi bi-shuffle 000001"></i>',
"link": "%s_kud_graph.html" % quote(str(end_node)),
})

grid_data.append(tmp_data)
headers = ["Domain", "Users", "Computers", "Groups", "Ou", "GPO"]
headers = ["Configured for Kerberos Unconstrained Delegation", "Compromise Paths"]
grid.setheaders(headers)
grid.setData(grid_data)
page.addComponent(grid)
Expand Down

0 comments on commit 0eef50f

Please sign in to comment.