Skip to content

Commit

Permalink
fix: Fix bug with 'Base' labels in neo4j databases
Browse files Browse the repository at this point in the history
* fix: bug with LABEL

* feat: better formating

* feat: cleaning

* feat: better syntax
  • Loading branch information
k4amos committed Dec 13, 2023
1 parent 2aa5309 commit 82541be
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 44 deletions.
9 changes: 4 additions & 5 deletions ad_miner/__main__.py
Expand Up @@ -10,7 +10,7 @@
import sys

# Local library imports
from ad_miner.sources.modules import logger, main_page, utils
from ad_miner.sources.modules import logger, main_page, utils, generic_formating
from ad_miner.sources.modules.computers import Computers
from ad_miner.sources.modules.domains import Domains
from ad_miner.sources.modules.neo4j_class import Neo4j, pre_request
Expand Down Expand Up @@ -153,11 +153,10 @@ def main() -> None:
string_information_database = ""

for type_label in total_objects:
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')
type_label_2 = generic_formating.clean_label(type_label['labels(x)'])

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

string_information_database += f"Relations : {number_relations}"
logger.print_magenta(string_information_database)
Expand Down
42 changes: 35 additions & 7 deletions ad_miner/sources/modules/generic_formating.py
@@ -1,17 +1,45 @@
from ad_miner.sources.modules.utils import grid_data_stringify
from urllib.parse import quote


def clean_label(label_list):
if 'Base' in label_list:
label_list.remove('Base')

if 'AZBase' in label_list:
label_list.remove('AZBase')

if len(label_list) == 0:
return ""
else:
return label_list[0]

def clean_data_type(data, list_type_to_clean):
for k in range(len(data)):
for type_name in list_type_to_clean:
data[k][type_name] = clean_label(data[k][type_name])
return data


def get_label_icon_dictionary():
return {
"User":"<i class='bi bi-person-fill'></i>",
"Computer": "<i class='bi bi-pc-display'></i>",
"Group": "<i class='bi bi-people-fill'></i>",
"OU": "<i class='bi bi-building'></i>",
"Container": "<i class='bi bi-box'></i>",
"Domain": "<i class='bi bi-globe'></i>",
"GPO": "<i class='bi bi-journal-text'></i>",
"User":"<i class='bi bi-person-fill' title='User'></i>",
"Computer": "<i class='bi bi-pc-display' title='Computer'></i>",
"Group": "<i class='bi bi-people-fill' title='Group'></i>",
"OU": "<i class='bi bi-building' title='OU'></i>",
"Container": "<i class='bi bi-box' title='Container'></i>",
"Domain": "<i class='bi bi-globe' title='Domain'></i>",
"GPO": "<i class='bi bi-journal-text' title='GPO'></i>",
"Unknown": "<i class='bi bi-question-circle-fill' title='Unknown'></i>"
}

def get_label_icon(name):
if name in get_label_icon_dictionary():
return get_label_icon_dictionary()[name]
else:
return get_label_icon_dictionary()["Unknown"]



# format data for grid components format: list of dicts [{key1:value1}, {key2:value2}]
def formatGridValues2Columns(data, headers, prefix, icon="", icon2=""):
Expand Down
1 change: 0 additions & 1 deletion ad_miner/sources/modules/neo4j_class.py
Expand Up @@ -93,7 +93,6 @@ def pre_request(arguments):
boolean_azure = bool(record.data()["n"])

driver.close()
print("number relation : ", number_relations)

return extract_date, total_objects, number_relations, boolean_azure

Expand Down
14 changes: 7 additions & 7 deletions ad_miner/sources/modules/requests.json
Expand Up @@ -446,9 +446,9 @@
},
"objects_admincount": {
"name": "N objects have AdminSDHolder",
"request": "MATCH (n{enabled:True, admincount:True}) RETURN n.domain as domain, labels(n)[1] as type, n.name as name ",
"request": "MATCH (n{enabled:True, admincount:True}) RETURN n.domain as domain, labels(n) as type, n.name as name ",
"output_type": "dict",
"_comment": "FIXME: the UNION is messing up the AS and breaks the grid 'UNION MATCH (n{admincount:true}) RETURN n.domain as domain, labels(n)[1] as type, n.name as name',"
"_comment": "FIXME: the UNION is messing up the AS and breaks the grid 'UNION MATCH (n{admincount:true}) RETURN n.domain as domain, labels(n) as type, n.name as name',"
},
"user_password_never_expires": {
"name": "Password never expired",
Expand Down Expand Up @@ -637,7 +637,7 @@
},
"has_sid_history": {
"name": "Objects that have a SID History",
"request": "MATCH (a)-[r:HasSIDHistory]->(b) RETURN a.name AS `Has SID History`, LABELS(a)[0] AS `Type_a`, b.name AS `Target`, LABELS(b)[0] AS `Type_b`",
"request": "MATCH (a)-[r:HasSIDHistory]->(b) RETURN a.name AS `Has SID History`, LABELS(a) AS `Type_a`, b.name AS `Target`, LABELS(b) AS `Type_b`",
"output_type": "dict"
},
"unpriv_users_to_GPO_init": {
Expand All @@ -657,7 +657,7 @@
},
"unpriv_users_to_GPO_user_not_enforced": {
"name": "Compromisable GPOs to users (not enforced)",
"request": "MATCH (n:User{enabled:true}) WHERE n.name IS NOT NULL WITH n ORDER BY n.name SKIP PARAM1 LIMIT PARAM2 MATCH p = (g:GPO{dangerous_inbound:true})-[r1:GPLink{enforced:false}]->(container1)-[r2:Contains*1..]->(n) WHERE NONE(x in NODES(p) WHERE x.blocksinheritance = true AND LABELS(x) = \"OU\") RETURN p",
"request": "MATCH (n:User{enabled:true}) WHERE n.name IS NOT NULL WITH n ORDER BY n.name SKIP PARAM1 LIMIT PARAM2 MATCH p = (g:GPO{dangerous_inbound:true})-[r1:GPLink{enforced:false}]->(container1)-[r2:Contains*1..]->(n) WHERE NONE(x in NODES(p) WHERE x.blocksinheritance = true AND (x:OU)) RETURN p",
"output_type": "Graph",
"scope_query": "MATCH (n:User{enabled:true}) WHERE n.name IS NOT NULL RETURN count(n)",
"_comment": "This is a request for the --gpo_low option"
Expand All @@ -671,14 +671,14 @@
},
"unpriv_users_to_GPO_computer_not_enforced": {
"name": "Compromisable GPOs to computers (not enforced)",
"request": "MATCH (n:Computer) WITH n ORDER BY n.name WITH n SKIP PARAM1 LIMIT PARAM2 MATCH p = (g:GPO{dangerous_inbound:true})-[r1:GPLink{enforced:false}]->(container1)-[r2:Contains*1..]->(n) WHERE NONE(x in NODES(p) WHERE x.blocksinheritance = true AND LABELS(x) = \"OU\") RETURN p",
"request": "MATCH (n:Computer) WITH n ORDER BY n.name WITH n SKIP PARAM1 LIMIT PARAM2 MATCH p = (g:GPO{dangerous_inbound:true})-[r1:GPLink{enforced:false}]->(container1)-[r2:Contains*1..]->(n) WHERE NONE(x in NODES(p) WHERE x.blocksinheritance = true AND (x:OU)) RETURN p",
"output_type": "Graph",
"scope_query": "MATCH (n:Computer) RETURN count(n)",
"_comment": "This is a request for the --gpo_low option"
},
"unpriv_users_to_GPO": {
"name": "Non privileged users to GPO",
"request": "MATCH (g:GPO) WITH g ORDER BY g.name SKIP PARAM1 LIMIT PARAM2 OPTIONAL MATCH (g)-[r1:GPLink {enforced:false}]->(container1) WITH g,container1 OPTIONAL MATCH (g)-[r2:GPLink {enforced:true}]->(container2) WITH g,container1,container2 OPTIONAL MATCH p = (g)-[r1:GPLink]->(container1)-[r2:Contains*1..8]->(n1:Computer) WHERE NONE(x in NODES(p) WHERE x.blocksinheritance = true AND LABELS(x) = \"OU\") WITH g,p,container2,n1 OPTIONAL MATCH p2 = (g)-[r1:GPLink]->(container2)-[r2:Contains*1..8]->(n2:Computer) RETURN p",
"request": "MATCH (g:GPO) WITH g ORDER BY g.name SKIP PARAM1 LIMIT PARAM2 OPTIONAL MATCH (g)-[r1:GPLink {enforced:false}]->(container1) WITH g,container1 OPTIONAL MATCH (g)-[r2:GPLink {enforced:true}]->(container2) WITH g,container1,container2 OPTIONAL MATCH p = (g)-[r1:GPLink]->(container1)-[r2:Contains*1..8]->(n1:Computer) WHERE NONE(x in NODES(p) WHERE x.blocksinheritance = true AND (x:OU)) WITH g,p,container2,n1 OPTIONAL MATCH p2 = (g)-[r1:GPLink]->(container2)-[r2:Contains*1..8]->(n2:Computer) RETURN p",
"output_type": "Graph",
"scope_query": "MATCH (g:GPO) RETURN COUNT(g)",
"_comment": "this is the normal version of the GPO request"
Expand All @@ -700,7 +700,7 @@
},
"pre_windows_2000_compatible_access_group": {
"name": "Pre-Windows 2000 Compatible Access contains unauthenticated users",
"request": "MATCH (n:Group) WHERE n.name STARTS WITH \"PRE-WINDOWS 2000 COMPATIBLE ACCESS@\" MATCH (m)-[r:MemberOf]->(n) WHERE NOT m.objectid ENDS WITH \"-S-1-5-11\" return m.domain, m.name, m.objectid, labels(m)[0] as type",
"request": "MATCH (n:Group) WHERE n.name STARTS WITH \"PRE-WINDOWS 2000 COMPATIBLE ACCESS@\" MATCH (m)-[r:MemberOf]->(n) WHERE NOT m.objectid ENDS WITH \"-S-1-5-11\" return m.domain, m.name, m.objectid, labels(m) as type",
"output_type": "list"
},
"guest_accounts": {
Expand Down
41 changes: 17 additions & 24 deletions ad_miner/sources/modules/users.py
Expand Up @@ -878,7 +878,8 @@ def genSDHolderUsersPage(self):
)
grid = Grid("Objects having AdminSDHolder")
grid.setheaders(["domain", "type", "name"])
grid.setData(self.objects_admincount_enabled)

grid.setData(generic_formating.clean_data_type(self.objects_admincount_enabled, ["type"]))
page.addComponent(grid)
page.render()

Expand Down Expand Up @@ -1471,7 +1472,8 @@ def genGroupAnomalyAcl(self, domain):

for k in range(len(self.anomaly_acl)):

label = [i for i in self.anomaly_acl[k]['LABELS(g)'] if "Base" not in i][0]
label = generic_formating.clean_label(self.anomaly_acl[k]['LABELS(g)'])

name_label_instance = f"{self.anomaly_acl[k]['g.name']}{label}"

if formated_data.get(name_label_instance) and formated_data[name_label_instance]["type"] == self.anomaly_acl[k]["type(r2)"] and formated_data[name_label_instance]["label"] == label:
Expand Down Expand Up @@ -1586,27 +1588,19 @@ def genHasSIDHistory(self):
row['admin of'] = u['List of computers']
target_count = int(u['List of computers'][u['List of computers'].find("'>", 55)+2:u['List of computers'].find('Computer')].strip())


# add user icons
if row['Type_a'] == "User":
row['Has SID History'] = "<i class='bi bi-person-fill' title='User'></i> " + row['Has SID History']
elif row['Type_a'] == "Group":
row['Has SID History'] = "<i class='bi bi-people-fill' title='Group'></i> " + row['Has SID History']
else:
row['Has SID History'] = "<i class='bi bi-question-circle-fill' title='Unknown'></i> " + row['Has SID History']
if row['Type_b'] == "User":
row['Target'] = "<i class='bi bi-person-fill' title='User'></i> " + row['Target']
elif row['Type_b'] == "Group":
row['Target'] = "<i class='bi bi-people-fill' title='Group'></i> " + row['Target']
else:
row['Target'] = "<i class='bi bi-question-circle-fill' title='Unknown'></i> " + row['Target']
type_label_a = generic_formating.clean_label(row['Type_a'])
row['Has SID History'] = f"{generic_formating.get_label_icon(type_label_a)} {row['Has SID History']}"

type_label_b = generic_formating.clean_label(row['Type_b'])
row['Target'] = f"{generic_formating.get_label_icon(type_label_b)} {row['Target']}"

# add star icon
if target_count > origin_count:
row['Has SID History'] = star_icon + " " + row['Has SID History']
row['Target'] = star_icon + " " + row['Target']



grid.setheaders(headers)
grid.setData(self.has_sid_history)

Expand Down Expand Up @@ -1882,15 +1876,14 @@ def genPreWin2000(self):
]

data = []
for domain, account_name, objectid, type in sorted_list:

for domain, account_name, objectid, type_list in sorted_list:
tmp_data = {"Domain": '<i class="bi bi-globe2"></i> ' + domain}
tmp_data["Name"] = (
'<i class="bi bi-person-fill"></i> ' + account_name
if "User" in type
else '<i class="bi bi-pc-display"></i> ' + account_name
if "Computer" in type
else '<i class="bi bi-people-fill"></i> ' + account_name
)

type_clean = generic_formating.clean_label(type_list)

tmp_data["Name"] = f"{generic_formating.get_label_icon(type_clean)} {account_name}"

tmp_data["Rating"] = (
'<i class="bi bi-star-fill" style="color: orange"></i><i class="bi bi-star-fill" style="color: orange"></i><i class="bi bi-star" style="color: orange"></i>'
if "1-5-7" not in objectid
Expand Down

0 comments on commit 82541be

Please sign in to comment.