-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathdatabase.py
132 lines (93 loc) · 6.12 KB
/
database.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import itertools, string
from datetime import datetime
from random import shuffle
import zxcvbn
from tinydb import TinyDB, Query
from tinydb.middlewares import CachingMiddleware
from tinydb_serialization import Serializer, SerializationMiddleware
class DateTimeSerializer(Serializer):
OBJ_CLASS = datetime
def encode(self, obj):
return obj.strftime("%Y-%m-%dT%H:%M:%S")
def decode(self, s):
return datetime.strptime(s, "%Y-%m-%dT%H:%M:%S")
class DomainDoesntExist(ValueError):
def __init__(self, message, tables):
self.message = message
self.tables = tables
class HashDatabase:
BLANK_NTLMHASH = "31d6cfe0d16ae931b73c59d7e0c089c0"
def __init__(self, db_name, domain, raise_if_table_doesnt_exist=True, only_enabled=False, only_users=False):
self.db = None
self.table = None
self.only_enabled = (Query().enabled.exists() if only_enabled else Query().ntlmhash.exists()) & ( Query().enabled == True if only_enabled else Query().ntlmhash.exists())
self.only_users = (Query().username.exists() if only_users else Query().ntlmhash.exists()) & (Query().username.test(lambda v: not v.endswith("$")) if only_users else Query().ntlmhash.exists())
serialization = SerializationMiddleware()
serialization.register_serializer(DateTimeSerializer(), "datetime")
self.db = TinyDB(db_name, storage=CachingMiddleware(serialization))
tables = list(self.db.tables())
if raise_if_table_doesnt_exist and domain not in tables:
raise DomainDoesntExist("Hashes for domain '{}' do not exist in database.".format(domain), tables)
self.table = self.db.table(domain)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.db.close()
@property
def counts(self):
total = self.table.count(self.only_enabled & self.only_users)
local_users = self.table.count((~ Query().historic.exists()) & (Query().username.test(lambda v: "\\" not in v and not v.endswith("$"))) & self.only_users)
domain_users = self.table.count((~ Query().historic.exists()) & (Query().username.test(lambda v: "\\" in v and not v.endswith("$"))) & self.only_users)
computers = self.table.count(Query().username.test(lambda v: v.endswith("$")))
return total, local_users, domain_users, computers
@property
def user_counts(self):
enabled_users = self.table.search((Query().enabled == True) & (Query().username.test(lambda v: not v.endswith("$"))))
disabled_users = self.table.search((Query().enabled == False) & (Query().username.test(lambda v: not v.endswith("$"))))
return len(enabled_users), len(disabled_users)
@property
def password_stats(self):
cracked = self.table.count((Query().password.exists()) & (Query().password != "") & self.only_users & self.only_enabled)
blank = self.table.count(Query().ntlmhash == HashDatabase.BLANK_NTLMHASH)
historic = self.table.count((Query().historic.exists()) & self.only_enabled & self.only_users)
return cracked, blank, historic
@property
def all_passwords(self):
results = self.table.search((Query().password.exists()) & (Query().password != "") & self.only_users & self.only_enabled)
return [(result["password"], zxcvbn.password_strength(result["password"])["score"]) for result in results]
@property
def password_composition_stats(self):
alphanum = string.ascii_letters + string.digits
only_alpha = self.table.count(Query().password.test(lambda p: p != "" and all(c in alphanum for c in p)))
with_special = self.table.count(Query().password.test(lambda p: p != "" and any(c not in alphanum for c in p)))
only_digits = self.table.count(Query().password.test(lambda p: p != "" and all(c in string.digits for c in p)))
return only_alpha, with_special, only_digits
def get_historic_passwords(self, limit=10):
results = sorted(self.table.search((Query().password.exists()) & (Query().password != "") & (Query().historic.exists()) & (Query().username.exists()) & self.only_enabled), key=lambda r: r["username"])
passwords = ((user, len(list(count))) for user, count in itertools.groupby(results, lambda r: r["username"]))
return sorted(list((user, self.__get_passwords_for_user(user))
for user, count in passwords), key=lambda (user, passwords): len(passwords), reverse=True)[:limit]
def get_passwords(self, sortby, reverse=True, limit=10):
results = sorted(self.table.search((Query().password.exists()) & self.only_users & self.only_enabled), key=lambda r: r["password"])
passwords = ((password, len(list(count))) for password, count in itertools.groupby(results, lambda r: r["password"]))
return sorted(list(
(password, count, zxcvbn.password_strength(password)["score"], self.__get_users_with_password(password))
for password, count in passwords), key=sortby, reverse=reverse)[:limit]
def get_passwords_where(self, where):
return self.table.search((Query().password.exists()) & (Query().password.test(where)) & self.only_users & self.only_enabled)
def update_hash_password(self, hash, password):
self.table.update({"ntlmhash": hash, "password": password, "updated": datetime.now()}, Query().ntlmhash == hash)
def insert(self, record):
record["created"] = datetime.now()
self.table.insert(record)
def __get_users_with_password(self, password):
users = self.table.search(
(Query().password.exists()) & (Query().username.exists()) & (Query().password == password)
& self.only_users & self.only_enabled
)
shuffle(users)
return users
def __get_passwords_for_user(self, user):
passwords = sorted(self.table.search((Query().password.exists()) & (Query().password != "") & (Query().username.exists()) & (Query().username == user) & self.only_enabled), key=lambda r: r["password"])
grouped_passwords = ((password, users) for password, users in itertools.groupby(passwords, lambda r: r["password"]))
return list(grouped_passwords)