Skip to content

Commit 7a98330

Browse files
authored
Update database.py
Signed-off-by: lapinou <lapinousexy@gmail.com>
1 parent 92dc819 commit 7a98330

1 file changed

Lines changed: 123 additions & 7 deletions

File tree

nxc/protocols/ldap/database.py

Lines changed: 123 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
import sys
22

3-
from sqlalchemy import Table
3+
from sqlalchemy import func, Table, select
4+
from sqlalchemy.dialects.sqlite import Insert # used for upsert
45
from sqlalchemy.exc import (
56
NoInspectionAvailable,
67
NoSuchTableError,
78
)
89

910
from nxc.database import BaseDB
10-
11+
from nxc.logger import nxc_logger
1112

1213
class database(BaseDB):
1314
def __init__(self, db_engine):
14-
self.CredentialsTable = None
15+
self.UsersTable = None
1516
self.HostsTable = None
1617

1718
super().__init__(db_engine)
1819

1920
@staticmethod
2021
def db_schema(db_conn):
2122
db_conn.execute(
22-
"""CREATE TABLE "credentials" (
23+
"""CREATE TABLE "users" (
2324
"id" integer PRIMARY KEY,
25+
"domain" text,
2426
"username" text,
25-
"password" text
27+
"password" text,
28+
"credtype" text,
29+
"pillaged_from_hostid" integer,
30+
FOREIGN KEY(pillaged_from_hostid) REFERENCES hosts(id)
2631
)"""
2732
)
2833

@@ -31,14 +36,15 @@ def db_schema(db_conn):
3136
"id" integer PRIMARY KEY,
3237
"ip" text,
3338
"hostname" text,
34-
"port" integer
39+
"domain" text,
40+
"os" text
3541
)"""
3642
)
3743

3844
def reflect_tables(self):
3945
with self.db_engine.connect():
4046
try:
41-
self.CredentialsTable = Table("credentials", self.metadata, autoload_with=self.db_engine)
47+
self.UsersTable = Table("users", self.metadata, autoload_with=self.db_engine)
4248
self.HostsTable = Table("hosts", self.metadata, autoload_with=self.db_engine)
4349
except (NoInspectionAvailable, NoSuchTableError):
4450
print(
@@ -49,3 +55,113 @@ def reflect_tables(self):
4955
[-] Then remove the nxc {self.protocol} DB (`rm -f {self.db_path}`) and run nxc to initialize the new DB"""
5056
)
5157
sys.exit()
58+
59+
def add_host(
60+
self,
61+
ip,
62+
hostname,
63+
domain,
64+
os
65+
):
66+
"""Check if this host has already been added to the database, if not, add it in."""
67+
hosts = []
68+
updated_ids = []
69+
70+
q = select(self.HostsTable).filter(self.HostsTable.c.ip == ip)
71+
results = self.db_execute(q).all()
72+
73+
# create new host
74+
if not results:
75+
new_host = {
76+
"ip": ip,
77+
"hostname": hostname,
78+
"domain": domain,
79+
"os": os
80+
}
81+
hosts = [new_host]
82+
# update existing hosts data
83+
else:
84+
for host in results:
85+
host_data = host._asdict()
86+
# only update column if it is being passed in
87+
if ip is not None:
88+
host_data["ip"] = ip
89+
if hostname is not None:
90+
host_data["hostname"] = hostname
91+
if domain is not None:
92+
host_data["domain"] = domain
93+
# only add host to be updated if it has changed
94+
if host_data not in hosts:
95+
hosts.append(host_data)
96+
updated_ids.append(host_data["id"])
97+
nxc_logger.debug(f"Update Hosts: {hosts}")
98+
99+
# TODO: find a way to abstract this away to a single Upsert call
100+
q = Insert(self.HostsTable) # .returning(self.HostsTable.c.id)
101+
update_columns = {col.name: col for col in q.excluded if col.name not in "id"}
102+
q = q.on_conflict_do_update(index_elements=self.HostsTable.primary_key, set_=update_columns)
103+
104+
self.db_execute(q, hosts) # .scalar()
105+
# we only return updated IDs for now - when RETURNING clause is allowed we can return inserted
106+
if updated_ids:
107+
nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
108+
return updated_ids
109+
110+
def add_credential(self, credtype, domain, username, password, pillaged_from=None):
111+
"""Check if this credential has already been added to the database, if not add it in."""
112+
credentials = []
113+
groups = []
114+
115+
if pillaged_from and not self.is_host_valid(pillaged_from):
116+
nxc_logger.debug("Invalid host")
117+
return
118+
119+
q = select(self.UsersTable).filter(
120+
func.lower(self.UsersTable.c.domain) == func.lower(domain),
121+
func.lower(self.UsersTable.c.username) == func.lower(username),
122+
func.lower(self.UsersTable.c.credtype) == func.lower(credtype),
123+
)
124+
results = self.db_execute(q).all()
125+
126+
# add new credential
127+
if not results:
128+
new_cred = {
129+
"credtype": credtype,
130+
"domain": domain,
131+
"username": username,
132+
"password": password,
133+
"pillaged_from": pillaged_from,
134+
}
135+
credentials = [new_cred]
136+
# update existing cred data
137+
else:
138+
for creds in results:
139+
# this will include the id, so we don't touch it
140+
cred_data = creds._asdict()
141+
# only update column if it is being passed in
142+
if credtype is not None:
143+
cred_data["credtype"] = credtype
144+
if domain is not None:
145+
cred_data["domain"] = domain
146+
if username is not None:
147+
cred_data["username"] = username
148+
if password is not None:
149+
cred_data["password"] = password
150+
if pillaged_from is not None:
151+
cred_data["pillaged_from"] = pillaged_from
152+
# only add cred to be updated if it has changed
153+
if cred_data not in credentials:
154+
credentials.append(cred_data)
155+
156+
# TODO: find a way to abstract this away to a single Upsert call
157+
q_users = Insert(self.UsersTable) # .returning(self.UsersTable.c.id)
158+
update_columns_users = {col.name: col for col in q_users.excluded if col.name not in "id"}
159+
q_users = q_users.on_conflict_do_update(index_elements=self.UsersTable.primary_key, set_=update_columns_users)
160+
nxc_logger.debug(f"Adding credentials: {credentials}")
161+
162+
self.db_execute(q_users, credentials) # .scalar()
163+
164+
if groups:
165+
q_groups = Insert(self.GroupRelationsTable)
166+
167+
self.db_execute(q_groups, groups)

0 commit comments

Comments
 (0)