Skip to content

Commit f9ce149

Browse files
authored
Merge pull request Pennyw0rth#531 from Pennyw0rth/neff-refactor-ssh
2 parents fb96e78 + 23b4a23 commit f9ce149

1 file changed

Lines changed: 114 additions & 102 deletions

File tree

nxc/protocols/ssh.py

Lines changed: 114 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def __init__(self, args, db, host):
2020
self.protocol = "SSH"
2121
self.remote_version = "Unknown SSH Version"
2222
self.server_os_platform = "Linux"
23+
self.shell_access = False
24+
self.admin_privs = False
2325
self.uac = ""
2426
super().__init__(args, db, host)
2527

@@ -77,11 +79,118 @@ def create_conn_obj(self):
7779
except OSError:
7880
return False
7981

80-
def check_if_admin(self):
81-
self.admin_privs = False
82+
def plaintext_login(self, username, password, private_key=""):
83+
self.username = username
84+
self.password = password
85+
try:
86+
if self.args.key_file or private_key:
87+
self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}")
88+
self.conn.connect(
89+
self.host,
90+
port=self.port,
91+
username=username,
92+
passphrase=password if password != "" else None,
93+
pkey=private_key,
94+
key_filename=self.args.key_file,
95+
timeout=self.args.ssh_timeout,
96+
look_for_keys=False,
97+
allow_agent=False,
98+
banner_timeout=self.args.ssh_timeout,
99+
)
100+
# If we get the private key from the file, we need to load it into the database
101+
if self.args.key_file:
102+
with open(self.args.key_file) as f:
103+
private_key = f.read().rstrip("\n")
104+
cred_id = self.db.add_credential("key", username, password, key=private_key)
105+
else:
106+
self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}")
107+
self.conn.connect(
108+
self.host,
109+
port=self.port,
110+
username=username,
111+
password=password,
112+
timeout=self.args.ssh_timeout,
113+
look_for_keys=False,
114+
allow_agent=False,
115+
banner_timeout=self.args.ssh_timeout,
116+
)
117+
cred_id = self.db.add_credential("plaintext", username, password)
82118

119+
self.check_shell(cred_id)
120+
121+
secret = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})"
122+
display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if self.shell_access else ''}"
123+
self.logger.success(f"{self.username}:{process_secret(secret)} {self.mark_pwned()} {highlight(display_shell_access)}")
124+
return True
125+
except AuthenticationException as e:
126+
if "Private key file is encrypted" in str(e):
127+
self.logger.fail(f"{username}:{process_secret(password)} Could not load private key, error: {e}")
128+
else:
129+
self.logger.fail(f"{username}:{process_secret(password)}")
130+
except SSHException as e:
131+
if "Invalid key" in str(e):
132+
self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, invalid password")
133+
elif "Error reading SSH protocol banner" in str(e):
134+
self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}")
135+
else:
136+
self.logger.exception(e)
137+
except Exception as e:
138+
self.logger.exception(e)
139+
self.conn.close()
140+
return False
141+
142+
def check_shell(self, cred_id):
143+
host_id = self.db.get_hosts(self.host)[0].id
144+
145+
# Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey
146+
# Check Linux
147+
stdout = self.conn.exec_command("id")[1].read().decode(self.args.codec, errors="ignore")
148+
if stdout:
149+
self.server_os_platform = "Linux"
150+
self.logger.debug(f"Linux detected for user: {stdout}")
151+
self.shell_access = True
152+
self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access)
153+
self.check_linux_priv()
154+
if self.admin_privs:
155+
self.logger.debug(f"User {self.username} logged in successfully and is root!")
156+
if self.args.key_file:
157+
self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id)
158+
else:
159+
self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id)
160+
return
161+
162+
# Check Windows
163+
stdout = self.conn.exec_command("whoami /priv")[1].read().decode(self.args.codec, errors="ignore")
164+
if stdout:
165+
self.server_os_platform = "Windows"
166+
self.logger.debug("Windows detected")
167+
self.shell_access = True
168+
self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access)
169+
self.check_windows_priv(stdout)
170+
if self.admin_privs:
171+
self.logger.debug(f"User {self.username} logged in successfully and is admin!")
172+
if self.args.key_file:
173+
self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id)
174+
else:
175+
self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id)
176+
return
177+
178+
# No shell access
179+
self.shell_access = False
180+
self.logger.debug(f"User: {self.username} can't get a basic shell")
181+
self.server_os_platform = "Network Devices"
182+
self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access)
183+
184+
def check_windows_priv(self, stdout):
185+
if "SeDebugPrivilege" in stdout:
186+
self.admin_privs = True
187+
elif "SeUndockPrivilege" in stdout:
188+
self.admin_privs = True
189+
self.uac = "with UAC - "
190+
191+
def check_linux_priv(self):
83192
if self.args.sudo_check:
84-
self.check_if_admin_sudo()
193+
self.check_linux_priv_sudo()
85194
return
86195

87196
# we could add in another method to check by piping in the password to sudo
@@ -108,7 +217,7 @@ def check_if_admin(self):
108217
self.logger.display(tips)
109218
return
110219

111-
def check_if_admin_sudo(self):
220+
def check_linux_priv_sudo(self):
112221
if not self.password:
113222
self.logger.error("Check admin with sudo does not support using a private key")
114223
return
@@ -184,103 +293,6 @@ def check_if_admin_sudo(self):
184293
self.logger.error("Command: 'mkfifo' unavailable, running command with 'sudo' failed")
185294
return
186295

187-
def plaintext_login(self, username, password, private_key=""):
188-
self.username = username
189-
self.password = password
190-
stdout = None
191-
try:
192-
if self.args.key_file or private_key:
193-
self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}")
194-
195-
self.conn.connect(
196-
self.host,
197-
port=self.port,
198-
username=username,
199-
passphrase=password if password != "" else None,
200-
key_filename=private_key if private_key else self.args.key_file,
201-
timeout=self.args.ssh_timeout,
202-
look_for_keys=False,
203-
allow_agent=False,
204-
banner_timeout=self.args.ssh_timeout,
205-
)
206-
207-
cred_id = self.db.add_credential(
208-
"key",
209-
username,
210-
password if password != "" else "",
211-
key=private_key,
212-
)
213-
214-
else:
215-
self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}")
216-
self.conn.connect(
217-
self.host,
218-
port=self.port,
219-
username=username,
220-
password=password,
221-
timeout=self.args.ssh_timeout,
222-
look_for_keys=False,
223-
allow_agent=False,
224-
banner_timeout=self.args.ssh_timeout,
225-
)
226-
cred_id = self.db.add_credential("plaintext", username, password)
227-
228-
# Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey
229-
_, stdout, _ = self.conn.exec_command("id")
230-
stdout = stdout.read().decode(self.args.codec, errors="ignore")
231-
except AuthenticationException:
232-
self.logger.fail(f"{username}:{process_secret(password)}")
233-
except SSHException as e:
234-
if "Invalid key" in str(e):
235-
self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, error: {e}")
236-
if "Error reading SSH protocol banner" in str(e):
237-
self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}")
238-
else:
239-
self.logger.exception(e)
240-
except Exception as e:
241-
self.logger.exception(e)
242-
self.conn.close()
243-
return False
244-
else:
245-
shell_access = False
246-
host_id = self.db.get_hosts(self.host)[0].id
247-
248-
if not stdout:
249-
_, stdout, _ = self.conn.exec_command("whoami /priv")
250-
stdout = stdout.read().decode(self.args.codec, errors="ignore")
251-
self.server_os_platform = "Windows"
252-
if "SeDebugPrivilege" in stdout:
253-
self.admin_privs = True
254-
elif "SeUndockPrivilege" in stdout:
255-
self.admin_privs = True
256-
self.uac = "with UAC - "
257-
258-
if not stdout:
259-
self.logger.debug(f"User: {self.username} can't get a basic shell")
260-
self.server_os_platform = "Network Devices"
261-
shell_access = False
262-
else:
263-
shell_access = True
264-
265-
self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access)
266-
267-
if shell_access and self.server_os_platform == "Linux":
268-
self.check_if_admin()
269-
if self.admin_privs:
270-
self.logger.debug(f"User {username} logged in successfully and is root!")
271-
if self.args.key_file:
272-
self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id)
273-
else:
274-
self.db.add_admin_user("plaintext", username, password, host_id=host_id, cred_id=cred_id)
275-
276-
if self.args.key_file:
277-
password = f"{process_secret(password)} (keyfile: {self.args.key_file})"
278-
279-
display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}"
280-
self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()} {highlight(display_shell_access)}")
281-
282-
return True
283-
284296
def put_file_single(self, sftp_conn, src, dst):
285297
self.logger.display(f'Copying "{src}" to "{dst}"')
286298
try:
@@ -325,6 +337,6 @@ def execute(self, payload=None, get_output=False):
325337
else:
326338
self.logger.success("Executed command")
327339
if get_output:
328-
for line in stdout.split("\n"):
340+
for line in stdout.replace("\r\n", "\n").rstrip("\n").split("\n"):
329341
self.logger.highlight(line.strip("\n"))
330342
return stdout

0 commit comments

Comments
 (0)