Skip to content

Commit 6b0cfcd

Browse files
committed
Added db_navigator stuff
1 parent 7146824 commit 6b0cfcd

2 files changed

Lines changed: 216 additions & 4 deletions

File tree

nxc/protocols/ldap/database.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import sys
22

3-
from sqlalchemy import func, Table, select
3+
from sqlalchemy import func, Table, select, delete
44
from sqlalchemy.dialects.sqlite import Insert # used for upsert
55
from sqlalchemy.exc import (
66
NoInspectionAvailable,
77
NoSuchTableError,
88
)
99

10-
from nxc.database import BaseDB
10+
from nxc.database import BaseDB, format_host_query
1111
from nxc.logger import nxc_logger
1212

1313
class database(BaseDB):
@@ -166,6 +166,14 @@ def add_credential(self, credtype, domain, username, password, pillaged_from=Non
166166

167167
self.db_execute(q_groups, groups)
168168

169+
def remove_credentials(self, creds_id):
170+
"""Removes a credential ID from the database"""
171+
del_hosts = []
172+
for cred_id in creds_id:
173+
q = delete(self.UsersTable).filter(self.UsersTable.c.id == cred_id)
174+
del_hosts.append(q)
175+
self.db_execute(q)
176+
169177
def is_credential_valid(self, credential_id):
170178
"""Check if this credential ID is valid."""
171179
q = select(self.UsersTable).filter(
@@ -200,4 +208,32 @@ def get_credential(self, cred_type, domain, username, password):
200208
self.UsersTable.c.credtype == cred_type,
201209
)
202210
results = self.db_execute(q).first()
203-
return results.id
211+
return results.id
212+
213+
def get_hosts(self, filter_term=None, domain=None):
214+
"""Return hosts from the database."""
215+
q = select(self.HostsTable)
216+
217+
# if we're returning a single host by ID
218+
if self.is_host_valid(filter_term):
219+
q = q.filter(self.HostsTable.c.id == filter_term)
220+
results = self.db_execute(q).first()
221+
# all() returns a list, so we keep the return format the same so consumers don't have to guess
222+
return [results]
223+
elif filter_term is not None and filter_term.startswith("domain"):
224+
domain = filter_term.split()[1]
225+
like_term = func.lower(f"%{domain}%")
226+
q = q.filter(self.HostsTable.c.domain.like(like_term))
227+
# if we're filtering by ip/hostname
228+
elif filter_term and filter_term != "":
229+
q = format_host_query(q, filter_term, self.HostsTable)
230+
231+
results = self.db_execute(q).all()
232+
nxc_logger.debug(f"ldap hosts() - results: {results}")
233+
return results
234+
235+
def is_host_valid(self, host_id):
236+
"""Check if this host ID is valid."""
237+
q = select(self.HostsTable).filter(self.HostsTable.c.id == host_id)
238+
results = self.db_execute(q).all()
239+
return len(results) > 0

nxc/protocols/ldap/db_navigator.py

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,183 @@
1-
from nxc.nxcdb import DatabaseNavigator, print_help
1+
from nxc.helpers.misc import validate_ntlm
2+
from nxc.nxcdb import DatabaseNavigator, print_table, print_help
23

34

45
class navigator(DatabaseNavigator):
6+
def display_hosts(self, hosts):
7+
data = [
8+
[
9+
"HostID",
10+
"IP",
11+
"Hostname",
12+
"Domain",
13+
"OS"
14+
]
15+
]
16+
17+
for host in hosts:
18+
host_id = host[0]
19+
ip = host[1]
20+
hostname = host[2]
21+
domain = host[3]
22+
23+
try:
24+
os = host[4].decode()
25+
except Exception:
26+
os = host[4]
27+
28+
data.append(
29+
[
30+
host_id,
31+
ip,
32+
hostname,
33+
domain,
34+
os
35+
]
36+
)
37+
print_table(data, title="Hosts")
38+
39+
def do_hosts(self, line):
40+
filter_term = line.strip()
41+
42+
if filter_term == "":
43+
hosts = self.db.get_hosts()
44+
self.display_hosts(hosts)
45+
else:
46+
hosts = self.db.get_hosts(filter_term=filter_term)
47+
48+
if len(hosts) > 1:
49+
self.display_hosts(hosts)
50+
elif len(hosts) == 1:
51+
data = [
52+
[
53+
"HostID",
54+
"IP",
55+
"Hostname",
56+
"Domain",
57+
"OS"
58+
]
59+
]
60+
host_id_list = []
61+
62+
for host in hosts:
63+
host_id = host[0]
64+
host_id_list.append(host_id)
65+
ip = host[1]
66+
hostname = host[2]
67+
domain = host[3]
68+
69+
try:
70+
os = host[4].decode()
71+
except Exception:
72+
os = host[4]
73+
74+
data.append(
75+
[
76+
host_id,
77+
ip,
78+
hostname,
79+
domain,
80+
os
81+
]
82+
)
83+
print_table(data, title="Host")
84+
85+
def help_hosts(self):
86+
help_string = """
87+
hosts [filter_term]
88+
By default prints all hosts
89+
Table format:
90+
| 'HostID', 'IP', 'Hostname', 'Domain', 'OS' |
91+
Subcommands:
92+
filter_term - filters hosts with filter_term
93+
If a single host is returned (e.g. `hosts 15`, it prints the following tables:
94+
Host | 'HostID', 'IP', 'Hostname', 'Domain', 'OS' |
95+
Otherwise, it prints the default host table from a `like` query on the `ip` and `hostname` columns
96+
"""
97+
print_help(help_string)
98+
99+
def display_creds(self, creds):
100+
data = [["CredID", "CredType", "Domain", "UserName", "Password"]]
101+
102+
for cred in creds:
103+
cred_id = cred[0]
104+
domain = cred[1]
105+
username = cred[2]
106+
password = cred[3]
107+
credtype = cred[4]
108+
109+
data.append(
110+
[
111+
cred_id,
112+
credtype,
113+
domain,
114+
username,
115+
password
116+
]
117+
)
118+
print_table(data, title="Credentials")
119+
120+
def do_creds(self, line):
121+
filter_term = line.strip()
122+
123+
if filter_term == "":
124+
creds = self.db.get_credentials()
125+
self.display_creds(creds)
126+
elif filter_term.split()[0].lower() == "add":
127+
args = filter_term.split()[1:]
128+
129+
if len(args) == 3:
130+
domain, username, password = args
131+
if validate_ntlm(password):
132+
self.db.add_credential("hash", domain, username, password)
133+
else:
134+
self.db.add_credential("plaintext", domain, username, password)
135+
else:
136+
print("[!] Format is 'add domain username password")
137+
return
138+
elif filter_term.split()[0].lower() == "remove":
139+
args = filter_term.split()[1:]
140+
141+
if len(args) != 1:
142+
print("[!] Format is 'remove <credID>'")
143+
return
144+
else:
145+
self.db.remove_credentials(args)
146+
elif filter_term.split()[0].lower() == "plaintext":
147+
creds = self.db.get_credentials(cred_type="plaintext")
148+
self.display_creds(creds)
149+
elif filter_term.split()[0].lower() == "hash":
150+
creds = self.db.get_credentials(cred_type="hash")
151+
self.display_creds(creds)
152+
else:
153+
creds = self.db.get_credentials(filter_term=filter_term)
154+
data = [["CredID", "CredType", "Domain", "UserName", "Password"]]
155+
cred_id_list = []
156+
157+
for cred in creds:
158+
cred_id_list.append(cred[0])
159+
data.append([cred[0], cred[1], cred[2], cred[3], cred[4]])
160+
161+
print_table(data, title="Credential(s)")
162+
163+
def help_creds(self):
164+
help_string = """
165+
creds [add|remove|plaintext|hash|filter_term]
166+
By default prints all creds
167+
Table format:
168+
| 'CredID', 'CredType', 'Domain', 'UserName', 'Password' |
169+
Subcommands:
170+
add - format: "add domain username password <notes> <credType> <sid>"
171+
remove - format: "remove <credID>"
172+
plaintext - prints plaintext creds
173+
hash - prints hashed creds
174+
filter_term - filters creds with filter_term
175+
If a single credential is returned (e.g. `creds 15`, it prints the following tables:
176+
Credential(s) | 'CredID', 'CredType', 'Domain', 'UserName', 'Password'
177+
Otherwise, it prints the default credential table from a `like` query on the `username` column
178+
"""
179+
print_help(help_string)
180+
5181
def do_clear_database(self, line):
6182
if input("This will destroy all data in the current database, are you SURE you want to run this? (y/n): ") == "y":
7183
self.db.clear_database()

0 commit comments

Comments
 (0)