2022-07-18 23:59:14 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
2023-10-07 08:37:45 +00:00
import paramiko
import re
import uuid
import logging
import time
2023-10-07 15:05:51 +00:00
import base64
2022-07-18 23:59:14 +00:00
2023-05-01 13:56:03 +00:00
from io import StringIO
2023-09-14 21:07:15 +00:00
from nxc . config import process_secret
from nxc . connection import *
from nxc . logger import NXCAdapter
2023-05-02 15:17:59 +00:00
from paramiko . ssh_exception import (
AuthenticationException ,
NoValidConnectionsError ,
SSHException ,
)
2017-07-10 05:44:58 +00:00
class ssh ( connection ) :
2023-04-26 15:43:49 +00:00
def __init__ ( self , args , db , host ) :
2023-04-30 21:23:29 +00:00
self . protocol = " SSH "
2023-10-07 07:36:00 +00:00
self . remote_version = " Unknown SSH Version "
2023-09-18 08:26:23 +00:00
self . server_os_platform = " Linux "
self . user_principal = " root "
2023-04-30 21:23:29 +00:00
super ( ) . __init__ ( args , db , host )
2023-09-18 08:26:23 +00:00
def proto_flow ( self ) :
2023-10-07 08:37:45 +00:00
self . logger . debug ( f " Kicking off proto_flow " )
2023-09-18 08:26:23 +00:00
self . proto_logger ( )
if self . create_conn_obj ( ) :
self . enum_host_info ( )
self . print_host_info ( )
2023-10-07 07:36:00 +00:00
if self . remote_version == " Unknown SSH Version " :
2023-09-18 08:26:23 +00:00
self . conn . close ( )
return
if self . login ( ) :
if hasattr ( self . args , " module " ) and self . args . module :
self . call_modules ( )
else :
self . call_cmd_args ( )
self . conn . close ( )
2023-04-26 15:43:49 +00:00
2017-07-10 05:44:58 +00:00
def proto_logger ( self ) :
2023-10-07 08:37:45 +00:00
logging . getLogger ( " paramiko " ) . disabled = True
logging . getLogger ( " paramiko.transport " ) . disabled = True
2023-09-14 21:07:15 +00:00
self . logger = NXCAdapter (
2023-03-30 03:59:22 +00:00
extra = {
2023-04-25 23:45:56 +00:00
" protocol " : " SSH " ,
" host " : self . host ,
" port " : self . args . port ,
2023-05-02 15:17:59 +00:00
" hostname " : self . hostname ,
2023-03-30 03:59:22 +00:00
}
)
2017-07-10 05:44:58 +00:00
def print_host_info ( self ) :
2023-10-07 07:36:00 +00:00
self . logger . display ( self . remote_version if self . remote_version != " Unknown SSH Version " else f " { self . remote_version } , skipping... " )
2021-11-17 12:37:14 +00:00
return True
2017-07-10 05:44:58 +00:00
def enum_host_info ( self ) :
2023-10-07 07:36:00 +00:00
if self . conn . _transport . remote_version :
self . remote_version = self . conn . _transport . remote_version
self . logger . debug ( f ' Remote version: { self . remote_version } ' )
self . db . add_host ( self . host , self . args . port , self . remote_version )
2017-07-10 05:44:58 +00:00
def create_conn_obj ( self ) :
self . conn = paramiko . SSHClient ( )
self . conn . set_missing_host_key_policy ( paramiko . AutoAddPolicy ( ) )
try :
2023-09-18 08:26:23 +00:00
self . conn . connect ( self . host , port = self . args . port , timeout = self . args . ssh_timeout )
2017-07-10 05:44:58 +00:00
except AuthenticationException :
return True
except SSHException :
return True
except NoValidConnectionsError :
return False
2017-10-21 23:24:09 +00:00
except socket . error :
return False
2017-07-10 05:44:58 +00:00
def check_if_admin ( self ) :
2023-09-18 08:26:23 +00:00
self . admin_privs = False
if self . args . sudo_check :
self . check_if_admin_sudo ( )
return
2023-04-25 23:45:56 +00:00
# we could add in another method to check by piping in the password to sudo
# but that might be too much of an opsec concern - maybe add in a flag to do more checks?
2023-10-07 07:36:00 +00:00
self . logger . info ( f " Determined user is root via `id ; sudo -ln` command " )
stdin , stdout , stderr = self . conn . exec_command ( " id ; sudo -ln 2>&1 " )
2023-09-18 08:26:23 +00:00
stdout = stdout . read ( ) . decode ( " utf-8 " , errors = " ignore " )
2023-10-07 07:36:00 +00:00
admin_flag = {
2023-09-18 08:26:23 +00:00
" (root) " : [ True , None ] ,
" NOPASSWD: ALL " : [ True , None ] ,
" (ALL : ALL) ALL " : [ True , None ] ,
2023-10-07 08:37:45 +00:00
" (sudo) " : [ False , f ' Current user: " { self . username } " was in " sudo " group, please try " --sudo-check " to check if user can run sudo shell ' ]
2023-09-18 08:26:23 +00:00
}
2023-10-07 07:36:00 +00:00
for keyword in admin_flag . keys ( ) :
2023-09-18 08:26:23 +00:00
match = re . findall ( re . escape ( keyword ) , stdout )
if match :
self . logger . info ( f ' User: " { self . username } " matched keyword: { match [ 0 ] } ' )
2023-10-07 07:36:00 +00:00
self . admin_privs = admin_flag [ match [ 0 ] ] [ 0 ]
2023-10-07 08:37:45 +00:00
if not self . admin_privs :
2023-10-07 07:36:00 +00:00
tips = admin_flag [ match [ 0 ] ] [ 1 ]
2023-09-18 08:26:23 +00:00
continue
2023-10-07 08:37:45 +00:00
else :
break
2023-09-18 08:26:23 +00:00
if not self . admin_privs and " tips " in locals ( ) :
self . logger . display ( tips )
return
def check_if_admin_sudo ( self ) :
if not self . password :
self . logger . error ( " Check admin with sudo not support private key. " )
return
if self . args . sudo_check_method :
method = self . args . sudo_check_method
self . logger . info ( f " Doing sudo check with method: { method } " )
if method == " sudo-stdin " :
stdin , stdout , stderr = self . conn . exec_command ( " sudo --help " )
stdout = stdout . read ( ) . decode ( " utf-8 " , errors = " ignore " )
if " stdin " in stdout :
shadow_Backup = f ' /tmp/ { uuid . uuid4 ( ) } '
# sudo support stdin password
stdin , stdout , stderr = self . conn . exec_command ( f " echo { self . password } | sudo -S cp /etc/shadow { shadow_Backup } >/dev/null 2>&1 & " )
stdin , stdout , stderr = self . conn . exec_command ( f " echo { self . password } | sudo -S chmod 777 { shadow_Backup } >/dev/null 2>&1 & " )
tries = 1
while True :
self . logger . info ( f " Checking { shadow_Backup } if it existed " )
stdin , stdout , stderr = self . conn . exec_command ( f ' ls { shadow_Backup } ' )
if tries > = self . args . get_output_tries :
self . logger . info ( f ' { shadow_Backup } not existed, maybe the pipe has been hanged over, please increase the number of tries with the option " --get-output-tries " or change other method with " --sudo-check-method " . If it \' s still failing maybe sudo shell is not working with current user ' )
break
if stderr . read ( ) . decode ( ' utf-8 ' ) :
time . sleep ( 2 )
tries + = 1
else :
self . logger . info ( f " { shadow_Backup } existed " )
self . admin_privs = True
break
self . logger . info ( f " Remove up temporary files " )
stdin , stdout , stderr = self . conn . exec_command ( f " rm -rf { shadow_Backup } " )
else :
self . logger . error ( " Command: ' sudo ' not support stdin mode, running command with ' sudo ' failed " )
return
else :
stdin , stdout , stderr = self . conn . exec_command ( " mkfifo --help " )
stdout = stdout . read ( ) . decode ( " utf-8 " , errors = " ignore " )
# check if user can execute mkfifo
if " Create named pipes " in stdout :
self . logger . info ( " Command: ' mkfifo ' available " )
pipe_stdin = f ' /tmp/systemd- { uuid . uuid4 ( ) } '
pipe_stdout = f ' /tmp/systemd- { uuid . uuid4 ( ) } '
shadow_Backup = f ' /tmp/ { uuid . uuid4 ( ) } '
stdin , stdout , stderr = self . conn . exec_command ( f " mkfifo { pipe_stdin } ; tail -f { pipe_stdin } | /bin/sh 2>&1 > { pipe_stdout } >/dev/null 2>&1 & " )
# 'script -qc /bin/sh /dev/null' means "upgrade" the shell, like reverse shell from netcat
stdin , stdout , stderr = self . conn . exec_command ( f " echo ' script -qc /bin/sh /dev/null ' > { pipe_stdin } " )
stdin , stdout , stderr = self . conn . exec_command ( f " echo ' sudo -s ' > { pipe_stdin } && echo ' { self . password } ' > { pipe_stdin } " )
# Sometime the pipe will hanging(only happen with paramiko)
# Can't get "whoami" or "id" result in pipe_stdout, maybe something wrong using pipe with paramiko
# But one thing I can confirm, is the command was executed even can't get result from pipe_stdout
tries = 1
self . logger . info ( f " Copy /etc/shadow to { shadow_Backup } if pass the sudo auth " )
while True :
self . logger . info ( f " Checking { shadow_Backup } if it existed " )
stdin , stdout , stderr = self . conn . exec_command ( f ' ls { shadow_Backup } ' )
if tries > = self . args . get_output_tries :
self . logger . info ( f ' { shadow_Backup } not existed, maybe the pipe has been hanged over, please increase the number of tries with the option " --get-output-tries " or change other method with " --sudo-check-method " . If it \' s still failing maybe sudo shell is not working with current user ' )
break
if stderr . read ( ) . decode ( ' utf-8 ' ) :
time . sleep ( 2 )
stdin , stdout , stderr = self . conn . exec_command ( f " echo ' cp /etc/shadow { shadow_Backup } && chmod 777 { shadow_Backup } ' > { pipe_stdin } " )
tries + = 1
else :
self . logger . info ( f " { shadow_Backup } existed " )
self . admin_privs = True
break
self . logger . info ( f " Remove up temporary files " )
stdin , stdout , stderr = self . conn . exec_command ( f " rm -rf { shadow_Backup } { pipe_stdin } { pipe_stdout } " )
else :
self . logger . error ( " Command: ' mkfifo ' unavailable, running command with ' sudo ' failed " )
return
2017-07-10 05:44:58 +00:00
2023-04-30 21:23:29 +00:00
def plaintext_login ( self , username , password , private_key = None ) :
2023-09-18 08:26:23 +00:00
self . username = username
self . password = password
2023-10-07 15:35:36 +00:00
private_key = " "
2023-09-18 08:26:23 +00:00
stdout = None
stderr = None
2017-07-10 05:44:58 +00:00
try :
2023-05-01 01:49:45 +00:00
if self . args . key_file or private_key :
2023-09-18 08:26:23 +00:00
self . logger . debug ( f " Logging in with key " )
2023-10-07 15:05:51 +00:00
if self . args . key_file :
with open ( self . args . key_file , ' r ' ) as f :
private_key = f . read ( )
pkey = paramiko . RSAKey . from_private_key ( StringIO ( private_key ) , password )
self . conn . _transport . auth_publickey ( username , pkey )
cred_id = self . db . add_credential (
" key " ,
username ,
password if password != " " else " " ,
key = private_key ,
)
2023-04-26 15:43:49 +00:00
2019-11-18 17:39:17 +00:00
else :
2023-09-18 08:26:23 +00:00
self . logger . debug ( f " Logging { self . host } with username: { self . username } , password: { self . password } " )
self . conn . _transport . auth_password ( username , password , fallback = True )
2023-10-07 08:37:45 +00:00
cred_id = self . db . add_credential ( " plaintext " , username , password )
2023-04-26 15:43:49 +00:00
2023-09-18 08:26:23 +00:00
# Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey
stdin , stdout , stderr = self . conn . exec_command ( " id " )
stdout = stdout . read ( ) . decode ( " utf-8 " , errors = " ignore " )
except Exception as e :
2023-10-07 15:35:36 +00:00
if self . args . key_file :
password = f " { process_secret ( password ) } (keyfile: { self . args . key_file } ) "
self . logger . fail ( f " { username } : { password } { e } " )
2023-09-18 08:26:23 +00:00
self . conn . close ( )
return False
else :
2023-04-27 00:36:36 +00:00
shell_access = False
2023-04-26 15:43:49 +00:00
host_id = self . db . get_hosts ( self . host ) [ 0 ] . id
2017-07-10 05:44:58 +00:00
2023-09-18 08:26:23 +00:00
if not stdout :
stdin , stdout , stderr = self . conn . exec_command ( " whoami /priv " )
stdout = stdout . read ( ) . decode ( " utf-8 " , errors = " ignore " )
self . server_os_platform = " Windows "
self . user_principal = " admin "
if " SeDebugPrivilege " in stdout :
self . admin_privs = True
elif " SeUndockPrivilege " in stdout :
self . admin_privs = True
self . user_principal = " admin (UAC) "
2023-04-26 15:43:49 +00:00
else :
2023-09-18 08:26:23 +00:00
# non admin (low priv)
self . user_principal = " admin (low priv) "
if not stdout :
self . logger . debug ( f " User: { self . username } can ' t get a basic shell " )
self . server_os_platform = " Network Devices "
shell_access = False
2023-04-27 00:36:36 +00:00
else :
2023-09-18 08:26:23 +00:00
shell_access = True
2023-10-07 08:37:45 +00:00
self . db . add_loggedin_relation ( cred_id , host_id , shell = shell_access )
2023-09-18 08:26:23 +00:00
if shell_access and self . server_os_platform == " Linux " :
self . check_if_admin ( )
if self . admin_privs :
self . logger . debug ( f " User { username } logged in successfully and is root! " )
if self . args . key_file :
self . db . add_admin_user ( " key " , username , password , host_id = host_id , cred_id = cred_id )
else :
self . db . add_admin_user (
" plaintext " ,
username ,
password ,
host_id = host_id ,
cred_id = cred_id ,
)
2023-04-26 15:43:49 +00:00
if self . args . key_file :
2023-10-07 15:35:36 +00:00
password = f " { process_secret ( password ) } (keyfile: { self . args . key_file } ) "
2023-04-26 15:43:49 +00:00
2023-10-07 08:37:45 +00:00
display_shell_access = " - {} {} {} " . format (
f " ( { self . user_principal } ) " if self . admin_privs else f " (non { self . user_principal } ) " ,
self . server_os_platform ,
' - Shell access! ' if shell_access else ' '
)
# Force show pwn3d label
self . admin_privs = True
2023-10-07 15:35:36 +00:00
self . logger . success ( f " { username } : { password } { self . mark_pwned ( ) } { highlight ( display_shell_access ) } " )
2023-09-18 08:26:23 +00:00
2023-04-23 11:45:16 +00:00
return True
2023-09-18 08:26:23 +00:00
def execute ( self , payload = None , get_output = False ) :
if not payload and self . args . execute :
payload = self . args . execute
if not self . args . no_output :
get_output = True
2020-04-28 10:11:16 +00:00
try :
2023-09-18 08:26:23 +00:00
stdin , stdout , stderr = self . conn . exec_command ( f " { payload } 2>&1 " )
2020-04-28 10:11:16 +00:00
except AttributeError :
2023-04-12 04:25:38 +00:00
return " "
2023-09-18 08:26:23 +00:00
if get_output :
2023-04-26 15:43:49 +00:00
self . logger . success ( " Executed command " )
2023-09-18 08:26:23 +00:00
if get_output :
for line in stdout :
self . logger . highlight ( line . strip ( ) )
2023-10-07 15:05:51 +00:00
return stdout