Exemplo n.º 1
0
  def _do_key_exchange(self):
    # Generate and send our side of the kex exchange handshake
    client_kex_init = _generate_client_kex_init()
    self.send(client_kex_init)

    # Receive the server's side of the key exchange handshake
    server_kex_init = self.read()
    data_ptr, ssh_msg_type = parse_byte(server_kex_init, 0)
    assert ssh_msg_type == SSH_MSG_NUMS['SSH_MSG_KEXINIT']

    # Read the cookie from the server
    cookie = server_kex_init[data_ptr:data_ptr + COOKIE_LEN]
    data_ptr += COOKIE_LEN
    print colors.cyan('Cookie: %s' % repr(cookie))

    # Read the algorithm lists from the server
    data_ptr, kex_algorithms = parse_name_list(server_kex_init, data_ptr)
    data_ptr, server_host_key_algorithms = parse_name_list(server_kex_init, data_ptr)
    data_ptr, encryption_algorithms_client_to_server = parse_name_list(server_kex_init, data_ptr)
    data_ptr, encryption_algorithms_server_to_client = parse_name_list(server_kex_init, data_ptr)
    data_ptr, mac_algorithms_client_to_server = parse_name_list(server_kex_init, data_ptr)
    data_ptr, mac_algorithms_server_to_client = parse_name_list(server_kex_init, data_ptr)
    data_ptr, compression_algorithms_client_to_server = parse_name_list(server_kex_init, data_ptr)
    data_ptr, compression_algorithms_server_to_client = parse_name_list(server_kex_init, data_ptr)
    data_ptr, _ = parse_name_list(server_kex_init, data_ptr)
    data_ptr, _ = parse_name_list(server_kex_init, data_ptr)

    # Check that the server did not try to predict the key exchange protocol we'd be using
    data_ptr, first_kex_packet_follows = parse_byte(server_kex_init, data_ptr)
    assert first_kex_packet_follows == 0, 'Additional data in key exchange packet'

    # Check that the reserved bytes are also present in the message
    assert len(server_kex_init) == data_ptr + KEX_RESERVED_BYTES_LEN, \
      'Wrong amount of data left in packet'

    # Check that we'll be able to talk to this server correctly
    assert KEX_ALGORITHM in kex_algorithms
    assert SERVER_HOST_KEY_ALGORITHM in server_host_key_algorithms
    assert ENCRYPTION_ALGORITHM in encryption_algorithms_client_to_server
    assert ENCRYPTION_ALGORITHM in encryption_algorithms_server_to_client
    assert MAC_ALGORITHM in mac_algorithms_client_to_server
    assert MAC_ALGORITHM in mac_algorithms_server_to_client
    assert COMPRESSION_ALGORITHM in compression_algorithms_client_to_server
    assert COMPRESSION_ALGORITHM in compression_algorithms_server_to_client

    # Derive Diffie Hellman shared keys
    self._run_diffie_hellman_group14_sha1_key_exchange(server_kex_init, client_kex_init)

    # Swap to using those keys
    self.send(generate_byte(SSH_MSG_NUMS['SSH_MSG_NEWKEYS']))
    response = self.read()
    index, response_type = parse_byte(response, 0)
    assert response_type == SSH_MSG_NUMS['SSH_MSG_NEWKEYS'], \
      'Unknown SSH message type: %d' % response_type
    assert index == len(response), 'Additional data in response'

    self._encryption_negotiated = True

    print colors.cyan('Successfully exchanged keys!')
Exemplo n.º 2
0
def _generate_client_kex_init():
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_KEXINIT']))
    msg.append(random_bytes(COOKIE_LEN))

    msg.append(generate_name_list([KEX_ALGORITHM]))
    msg.append(generate_name_list([SERVER_HOST_KEY_ALGORITHM]))
    msg.append(generate_name_list([ENCRYPTION_ALGORITHM]))
    msg.append(generate_name_list([ENCRYPTION_ALGORITHM]))
    msg.append(generate_name_list([MAC_ALGORITHM]))
    msg.append(generate_name_list([MAC_ALGORITHM]))
    msg.append(generate_name_list([COMPRESSION_ALGORITHM]))
    msg.append(generate_name_list([COMPRESSION_ALGORITHM]))
    msg.append(generate_name_list([]))
    msg.append(generate_name_list([]))

    msg.append(generate_byte(0))  # Additional data being sent = False
    msg.append('\x00\x00\x00\x00')  # Reserved bytes

    return ''.join(msg)
Exemplo n.º 3
0
def _generate_client_kex_init():
  msg = []
  msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_KEXINIT']))
  msg.append(random_bytes(COOKIE_LEN))

  msg.append(generate_name_list([KEX_ALGORITHM]))
  msg.append(generate_name_list([SERVER_HOST_KEY_ALGORITHM]))
  msg.append(generate_name_list([ENCRYPTION_ALGORITHM]))
  msg.append(generate_name_list([ENCRYPTION_ALGORITHM]))
  msg.append(generate_name_list([MAC_ALGORITHM]))
  msg.append(generate_name_list([MAC_ALGORITHM]))
  msg.append(generate_name_list([COMPRESSION_ALGORITHM]))
  msg.append(generate_name_list([COMPRESSION_ALGORITHM]))
  msg.append(generate_name_list([]))
  msg.append(generate_name_list([]))

  msg.append(generate_byte(0)) # Additional data being sent = False
  msg.append('\x00\x00\x00\x00') # Reserved bytes

  return ''.join(msg)
Exemplo n.º 4
0
  def disconnect(self):
    '''Close the connection to the remote server.'''

    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_DISCONNECT']))
    msg.append(generate_uint32(11)) # SSH_DISCONNECT_BY_APPLICATION
    msg.append(generate_string('Closed by client'))
    msg.append(generate_string(''))
    self.send(''.join(msg))

    self._socket.close()

    print colors.cyan('Disconnected!')
Exemplo n.º 5
0
    def disconnect(self):
        '''Close the connection to the remote server.'''

        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_DISCONNECT']))
        msg.append(generate_uint32(11))  # SSH_DISCONNECT_BY_APPLICATION
        msg.append(generate_string('Closed by client'))
        msg.append(generate_string(''))
        self.send(''.join(msg))

        self._socket.close()

        print colors.cyan('Disconnected!')
Exemplo n.º 6
0
    def disconnect(self):
        '''Cleanly close the connection to the remote server.'''

        # Send our exit status
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_REQUEST']))
        msg.append(generate_uint32(self._remote_channel_number))
        msg.append(generate_string('exit-status'))
        msg.append(generate_byte(0))  # False
        msg.append(generate_uint32(0))  # Exit status = 0
        self._ssh_transport_connection.send(''.join(msg))

        # Then close the channel
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_CLOSE']))
        msg.append(generate_uint32(self._remote_channel_number))
        self._ssh_transport_connection.send(''.join(msg))

        # Read back the remote side's exit status
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        index, recipient_channel = parse_uint32(data, index)
        index, request_type = parse_string(data, index)
        index, want_reply_byte = parse_byte(data, index)
        want_reply = want_reply_byte != 0
        index, exit_status = parse_uint32(data, index)

        assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_REQUEST']
        assert recipient_channel == self._local_channel_number
        assert request_type == 'exit-status'
        assert not want_reply

        # Disconnect at the transport layer
        self._ssh_transport_connection.disconnect()

        return exit_status
Exemplo n.º 7
0
    def send(self, payload):
        '''Send data to the remote server.

    This data will be encrypted, and its authenticity guaranteed (both client-to-server and
    server-to-client).

    Args:
      payload (string): the data to be sent to the remote server.
    '''

        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_DATA']))
        msg.append(generate_uint32(self._remote_channel_number))
        msg.append(generate_string(payload))
        self._ssh_transport_connection.send(''.join(msg))
Exemplo n.º 8
0
  def disconnect(self):
    '''Cleanly close the connection to the remote server.'''

    # Send our exit status
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_REQUEST']))
    msg.append(generate_uint32(self._remote_channel_number))
    msg.append(generate_string('exit-status'))
    msg.append(generate_byte(0)) # False
    msg.append(generate_uint32(0)) # Exit status = 0
    self._ssh_transport_connection.send(''.join(msg))

    # Then close the channel
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_CLOSE']))
    msg.append(generate_uint32(self._remote_channel_number))
    self._ssh_transport_connection.send(''.join(msg))

    # Read back the remote side's exit status
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    index, recipient_channel = parse_uint32(data, index)
    index, request_type = parse_string(data, index)
    index, want_reply_byte = parse_byte(data, index)
    want_reply = want_reply_byte != 0
    index, exit_status = parse_uint32(data, index)

    assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_REQUEST']
    assert recipient_channel == self._local_channel_number
    assert request_type == 'exit-status'
    assert not want_reply

    # Disconnect at the transport layer
    self._ssh_transport_connection.disconnect()

    return exit_status
Exemplo n.º 9
0
  def send(self, payload):
    '''Send data to the remote server.

    This data will be encrypted, and its authenticity guaranteed (both client-to-server and
    server-to-client).

    Args:
      payload (string): the data to be sent to the remote server.
    '''

    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_DATA']))
    msg.append(generate_uint32(self._remote_channel_number))
    msg.append(generate_string(payload))
    self._ssh_transport_connection.send(''.join(msg))
Exemplo n.º 10
0
  def send(self, payload):
    '''Send a packet to the remote server.

    Assuming the initial connection has completed (i.e. #connect has been called, and returned),
    this data will be encrypted, and its authenticity guaranteed.

    Args:
      payload (string): The data to send to the remote server.
    '''

    # This maths is horrific, but essentially we're calculating how much padding we need to add,
    # given that we must have at least 4 bytes, and that the total message length must be a multiple
    # of the AES block length
    padding_len = MIN_PADDING_LEN
    padding_len += AES_BLOCK_LEN - ((4 + 1 + len(payload) + padding_len) % AES_BLOCK_LEN)
    packet_len = 1 + len(payload) + padding_len

    msg_parts = []
    msg_parts.append(generate_uint32(packet_len))
    msg_parts.append(generate_byte(padding_len))
    msg_parts.append(payload)
    msg_parts.append(random_bytes(padding_len))

    msg = ''.join(msg_parts)
    # If the packet is encrypted, add the MAC and encrypt the message. The weird order of operations
    # here is because SSH is encrypt-and-mac (that is, the mac is on the plaintext, which is also
    # encrypted), rather than encrypt-then-mac or mac-then-encrypt
    if self._encryption_negotiated:
      mac = hmac.new(
          self._integrity_key_client_to_server,
          generate_uint32(self._packets_sent_counter) + msg,
          hashlib.sha1
      ).digest()

      msg = self._aes_client_to_server.encrypt(msg)
      msg += mac

    self._packets_sent_counter += 1
    print colors.magenta('> Sending: %s' % repr(''.join(msg)))
    self._socket.send(msg)
Exemplo n.º 11
0
    def send(self, payload):
        '''Send a packet to the remote server.

    Assuming the initial connection has completed (i.e. #connect has been called, and returned),
    this data will be encrypted, and its authenticity guaranteed.

    Args:
      payload (string): The data to send to the remote server.
    '''

        # This maths is horrific, but essentially we're calculating how much padding we need to add,
        # given that we must have at least 4 bytes, and that the total message length must be a multiple
        # of the AES block length
        padding_len = MIN_PADDING_LEN
        padding_len += AES_BLOCK_LEN - (
            (4 + 1 + len(payload) + padding_len) % AES_BLOCK_LEN)
        packet_len = 1 + len(payload) + padding_len

        msg_parts = []
        msg_parts.append(generate_uint32(packet_len))
        msg_parts.append(generate_byte(padding_len))
        msg_parts.append(payload)
        msg_parts.append(random_bytes(padding_len))

        msg = ''.join(msg_parts)
        # If the packet is encrypted, add the MAC and encrypt the message. The weird order of operations
        # here is because SSH is encrypt-and-mac (that is, the mac is on the plaintext, which is also
        # encrypted), rather than encrypt-then-mac or mac-then-encrypt
        if self._encryption_negotiated:
            mac = hmac.new(self._integrity_key_client_to_server,
                           generate_uint32(self._packets_sent_counter) + msg,
                           hashlib.sha1).digest()

            msg = self._aes_client_to_server.encrypt(msg)
            msg += mac

        self._packets_sent_counter += 1
        print colors.magenta('> Sending: %s' % repr(''.join(msg)))
        self._socket.send(msg)
Exemplo n.º 12
0
  def _run_diffie_hellman_group14_sha1_key_exchange(self, server_kex_init, client_kex_init):
    # q, g, and p from https://tools.ietf.org/html/rfc3526#section-3
    q = 2 ** 2048
    g = 2
    p = int('''
      0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E34
      04DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F4
      06B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8
      FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E
      462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2
      261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF
    '''.replace(' ', '').replace('\n', ''), 16)

    x = random.SystemRandom().randint(2, q - 1)
    e = pow(g, x, p)

    # Send public key to server
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_KEXDH_INIT']))
    msg.append(generate_mpint(e))
    self.send(''.join(msg))

    # Receive (K_S || f || s) from the server
    # i.e. host key blob, f, and the signature, from the server
    msg = self.read()
    index, ssh_msg_type = parse_byte(msg, 0)
    assert ssh_msg_type == SSH_MSG_NUMS['SSH_MSG_KEXDH_REPLY']

    index, host_key_blob = parse_string(msg, index)
    index, f = parse_mpint(msg, index)
    index, signature = parse_string(msg, index)

    # Calculate a verifying key from the host key blob
    verifying_key = self._get_verifying_key(host_key_blob)

    # Also calculate the shared key, exchange hash, and session ID (this is the same as the exchange
    # hash)
    shared_key = pow(f, x, p)
    hashed_data = \
      generate_string(self._client_id_string.strip('\r\n')) + \
      generate_string(self._server_id_string.strip('\r\n')) + \
      generate_string(client_kex_init) + \
      generate_string(server_kex_init) + \
      generate_string(host_key_blob) + \
      generate_mpint(e) + \
      generate_mpint(f) + \
      generate_mpint(shared_key)
    exchange_hash = hashlib.sha1(hashed_data).digest()
    self.session_id = exchange_hash

    # Pull out the signature blob from the message
    index, ecdsa_identifier = parse_string(signature, 0)
    assert ecdsa_identifier == SERVER_HOST_KEY_ALGORITHM, \
      'Unknown signature type: %s' % ecdsa_identifier
    index, signature_blob = parse_string(signature, index)

    index, r = parse_mpint(signature_blob, 0)
    index, s = parse_mpint(signature_blob, index)

    # Verify that the signature on the message is correct
    assert verifying_key.verify(
        get_32_byte_repr(r) + get_32_byte_repr(s),
        exchange_hash,
        hashfunc=hashlib.sha256,
        sigdecode=ecdsa.util.sigdecode_string
    )
    print colors.cyan('Signature validated correctly! OMG!')

    # Derive *all* the keys!
    key_derivation_options = {
        'shared_key': shared_key,
        'exchange_hash': exchange_hash,
        'session_id': self.session_id,
    }

    # Client to server keys (these hard-coded ASCII letters brought to you by the RFC's key
    # derivation function: https://tools.ietf.org/html/rfc4253#section-7.2)
    initial_iv_client_to_server = _derive_encryption_key(
        key_derivation_options, 'A', AES_BLOCK_LEN)
    ctr = Counter.new(
        AES_BLOCK_LEN * 8,
        initial_value=int(initial_iv_client_to_server.encode('hex'), AES_BLOCK_LEN))
    encryption_key_client_to_server = _derive_encryption_key(
        key_derivation_options, 'C', AES_BLOCK_LEN)
    self._aes_client_to_server = AES.new(encryption_key_client_to_server, AES.MODE_CTR, counter=ctr)
    self._integrity_key_client_to_server = _derive_encryption_key(key_derivation_options, 'E')

    # Server to client keys
    initial_iv_server_to_client = _derive_encryption_key(
        key_derivation_options, 'B', AES_BLOCK_LEN)
    ctr = Counter.new(
        AES_BLOCK_LEN * 8,
        initial_value=int(initial_iv_server_to_client.encode('hex'), AES_BLOCK_LEN))
    encryption_key_server_to_client = _derive_encryption_key(
        key_derivation_options, 'D', AES_BLOCK_LEN)
    self._aes_server_to_client = AES.new(encryption_key_server_to_client, AES.MODE_CTR, counter=ctr)
    self._integrity_key_server_to_client = _derive_encryption_key(key_derivation_options, 'F')
Exemplo n.º 13
0
  def _create_ssh_connection(self):
    # Read the global request that SSH sends us - this is trying to let us know all host keys, but
    # it's OpenSSH-specific, and we don't need it
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    index, request_name = parse_string(data, index)
    index, want_reply_byte = parse_byte(data, index)
    want_reply = want_reply_byte != 0

    assert msg_type == SSH_MSG_NUMS['SSH_MSG_GLOBAL_REQUEST']
    assert request_name == '*****@*****.**'
    assert not want_reply

    # Reply to let OpenSSH know that we don't know what they're talking about
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_REQUEST_FAILURE']))
    self._ssh_transport_connection.send(''.join(msg))

    # Actually get started with opening a channel for SSH communication
    window_size = 1048576
    maximum_packet_size = 16384

    # Request to open a session channel
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_OPEN']))
    msg.append(generate_string('session'))
    msg.append(generate_uint32(self._local_channel_number))
    msg.append(generate_uint32(window_size))
    msg.append(generate_uint32(maximum_packet_size))
    self._ssh_transport_connection.send(''.join(msg))

    # Check that a channel was opened successfully
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    index, recipient_channel = parse_uint32(data, index)
    index, self._remote_channel_number = parse_uint32(data, index)
    index, initial_window_size = parse_uint32(data, index)
    index, maximum_packet_size = parse_uint32(data, index)

    print colors.cyan('Message type: %d' % msg_type)
    assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_OPEN_CONFIRMATION']
    assert recipient_channel == self._local_channel_number
    print colors.cyan('Remote channel number: %d' % self._remote_channel_number)
    print colors.cyan('Initial window size: %d' % initial_window_size)
    print colors.cyan('Maximum window size: %d' % maximum_packet_size)

    # Ask to turn that session channel into a shell
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_REQUEST']))
    msg.append(generate_uint32(self._remote_channel_number))
    msg.append(generate_string('shell'))
    msg.append(generate_byte(1)) # True, we do want a reply here
    self._ssh_transport_connection.send(''.join(msg))

    # OpenSSH then asks to increase their window size, that's fine, do it
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    index, recipient_channel = parse_uint32(data, index)
    index, bytes_to_add = parse_uint32(data, index)
    assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_WINDOW_ADJUST']
    initial_window_size += bytes_to_add

    # Check that they tell us they've opened a channel successfully
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)

    assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_SUCCESS']
    assert recipient_channel == self._local_channel_number

    print colors.cyan('Successfully opened shell!')
Exemplo n.º 14
0
    def _do_user_auth(self):
        # Ask the server whether it supports doing SSH user auth
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_SERVICE_REQUEST']))
        msg.append(generate_string(SSH_USERAUTH_STRING))
        self._ssh_transport_connection.send(''.join(msg))

        # Check that it says yes
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        assert msg_type == SSH_MSG_NUMS['SSH_MSG_SERVICE_ACCEPT'], \
          'Unknown message type received: %d' % msg_type
        index, service_name = parse_string(data, index)
        assert service_name == SSH_USERAUTH_STRING

        print colors.cyan("Let's do ssh-userauth!")

        # Ask the server which authentication methods it supports
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_USERAUTH_REQUEST']))
        msg.append(generate_string(self.username.encode('utf-8')))
        msg.append(generate_string('ssh-connection'))
        msg.append(generate_string('none'))
        self._ssh_transport_connection.send(''.join(msg))

        # Check that publickey is one of them
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        index, supported_auth_methods = parse_name_list(data, index)
        index, partial_success_byte = parse_byte(data, index)
        partial_success = partial_success_byte != 0

        assert msg_type == SSH_MSG_NUMS['SSH_MSG_USERAUTH_FAILURE'], \
          'Unknown message type: %d' % msg_type
        assert 'publickey' in supported_auth_methods, \
          'Server does not support public key authentication'
        assert not partial_success

        # Try to public key auth
        rsa_key = RSA.importKey(open(self.keyfile))
        pkcs_key = PKCS1_v1_5.new(rsa_key)

        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_USERAUTH_REQUEST']))
        msg.append(generate_string(self.username.encode('utf-8')))
        msg.append(generate_string('ssh-connection'))
        msg.append(generate_string('publickey'))
        msg.append(generate_byte(1))  # True: we really do want to authenticate
        msg.append(generate_string('ssh-rsa'))
        msg.append(
            generate_string(
                generate_string('ssh-rsa') + generate_mpint(rsa_key.e) +
                generate_mpint(rsa_key.n)))

        # Desperately try to figure out how signing works in this silly encapsulating protocol
        signed_data = generate_string(
            self._ssh_transport_connection.session_id) + ''.join(msg)
        # OMG Pycrypto, did it have to be *your* SHA1 implementation?
        signature = pkcs_key.sign(SHA.new(signed_data))
        msg.append(
            generate_string(
                generate_string('ssh-rsa') + generate_string(signature)))

        # Send the public key auth message to the server
        self._ssh_transport_connection.send(''.join(msg))

        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        assert msg_type == SSH_MSG_NUMS['SSH_MSG_USERAUTH_SUCCESS'], \
          'Unknown message type: %d' % msg_type

        print colors.cyan('Successfully user authed!')
Exemplo n.º 15
0
    def _create_ssh_connection(self):
        # Read the global request that SSH sends us - this is trying to let us know all host keys, but
        # it's OpenSSH-specific, and we don't need it
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        index, request_name = parse_string(data, index)
        index, want_reply_byte = parse_byte(data, index)
        want_reply = want_reply_byte != 0

        assert msg_type == SSH_MSG_NUMS['SSH_MSG_GLOBAL_REQUEST']
        assert request_name == '*****@*****.**'
        assert not want_reply

        # Reply to let OpenSSH know that we don't know what they're talking about
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_REQUEST_FAILURE']))
        self._ssh_transport_connection.send(''.join(msg))

        # Actually get started with opening a channel for SSH communication
        window_size = 1048576
        maximum_packet_size = 16384

        # Request to open a session channel
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_OPEN']))
        msg.append(generate_string('session'))
        msg.append(generate_uint32(self._local_channel_number))
        msg.append(generate_uint32(window_size))
        msg.append(generate_uint32(maximum_packet_size))
        self._ssh_transport_connection.send(''.join(msg))

        # Check that a channel was opened successfully
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        index, recipient_channel = parse_uint32(data, index)
        index, self._remote_channel_number = parse_uint32(data, index)
        index, initial_window_size = parse_uint32(data, index)
        index, maximum_packet_size = parse_uint32(data, index)

        print colors.cyan('Message type: %d' % msg_type)
        assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_OPEN_CONFIRMATION']
        assert recipient_channel == self._local_channel_number
        print colors.cyan('Remote channel number: %d' %
                          self._remote_channel_number)
        print colors.cyan('Initial window size: %d' % initial_window_size)
        print colors.cyan('Maximum window size: %d' % maximum_packet_size)

        # Ask to turn that session channel into a shell
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_CHANNEL_REQUEST']))
        msg.append(generate_uint32(self._remote_channel_number))
        msg.append(generate_string('shell'))
        msg.append(generate_byte(1))  # True, we do want a reply here
        self._ssh_transport_connection.send(''.join(msg))

        # OpenSSH then asks to increase their window size, that's fine, do it
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)
        index, recipient_channel = parse_uint32(data, index)
        index, bytes_to_add = parse_uint32(data, index)
        assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_WINDOW_ADJUST']
        initial_window_size += bytes_to_add

        # Check that they tell us they've opened a channel successfully
        data = self._ssh_transport_connection.read()
        index, msg_type = parse_byte(data, 0)

        assert msg_type == SSH_MSG_NUMS['SSH_MSG_CHANNEL_SUCCESS']
        assert recipient_channel == self._local_channel_number

        print colors.cyan('Successfully opened shell!')
Exemplo n.º 16
0
    def _run_diffie_hellman_group14_sha1_key_exchange(self, server_kex_init,
                                                      client_kex_init):
        # q, g, and p from https://tools.ietf.org/html/rfc3526#section-3
        q = 2**2048
        g = 2
        p = int(
            '''
      0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E34
      04DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F4
      06B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8
      FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E
      462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2
      261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF
    '''.replace(' ', '').replace('\n', ''), 16)

        x = random.SystemRandom().randint(2, q - 1)
        e = pow(g, x, p)

        # Send public key to server
        msg = []
        msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_KEXDH_INIT']))
        msg.append(generate_mpint(e))
        self.send(''.join(msg))

        # Receive (K_S || f || s) from the server
        # i.e. host key blob, f, and the signature, from the server
        msg = self.read()
        index, ssh_msg_type = parse_byte(msg, 0)
        assert ssh_msg_type == SSH_MSG_NUMS['SSH_MSG_KEXDH_REPLY']

        index, host_key_blob = parse_string(msg, index)
        index, f = parse_mpint(msg, index)
        index, signature = parse_string(msg, index)

        # Calculate a verifying key from the host key blob
        verifying_key = self._get_verifying_key(host_key_blob)

        # Also calculate the shared key, exchange hash, and session ID (this is the same as the exchange
        # hash)
        shared_key = pow(f, x, p)
        hashed_data = \
          generate_string(self._client_id_string.strip('\r\n')) + \
          generate_string(self._server_id_string.strip('\r\n')) + \
          generate_string(client_kex_init) + \
          generate_string(server_kex_init) + \
          generate_string(host_key_blob) + \
          generate_mpint(e) + \
          generate_mpint(f) + \
          generate_mpint(shared_key)
        exchange_hash = hashlib.sha1(hashed_data).digest()
        self.session_id = exchange_hash

        # Pull out the signature blob from the message
        index, ecdsa_identifier = parse_string(signature, 0)
        assert ecdsa_identifier == SERVER_HOST_KEY_ALGORITHM, \
          'Unknown signature type: %s' % ecdsa_identifier
        index, signature_blob = parse_string(signature, index)

        index, r = parse_mpint(signature_blob, 0)
        index, s = parse_mpint(signature_blob, index)

        # Verify that the signature on the message is correct
        assert verifying_key.verify(get_32_byte_repr(r) + get_32_byte_repr(s),
                                    exchange_hash,
                                    hashfunc=hashlib.sha256,
                                    sigdecode=ecdsa.util.sigdecode_string)
        print colors.cyan('Signature validated correctly! OMG!')

        # Derive *all* the keys!
        key_derivation_options = {
            'shared_key': shared_key,
            'exchange_hash': exchange_hash,
            'session_id': self.session_id,
        }

        # Client to server keys (these hard-coded ASCII letters brought to you by the RFC's key
        # derivation function: https://tools.ietf.org/html/rfc4253#section-7.2)
        initial_iv_client_to_server = _derive_encryption_key(
            key_derivation_options, 'A', AES_BLOCK_LEN)
        ctr = Counter.new(AES_BLOCK_LEN * 8,
                          initial_value=int(
                              initial_iv_client_to_server.encode('hex'),
                              AES_BLOCK_LEN))
        encryption_key_client_to_server = _derive_encryption_key(
            key_derivation_options, 'C', AES_BLOCK_LEN)
        self._aes_client_to_server = AES.new(encryption_key_client_to_server,
                                             AES.MODE_CTR,
                                             counter=ctr)
        self._integrity_key_client_to_server = _derive_encryption_key(
            key_derivation_options, 'E')

        # Server to client keys
        initial_iv_server_to_client = _derive_encryption_key(
            key_derivation_options, 'B', AES_BLOCK_LEN)
        ctr = Counter.new(AES_BLOCK_LEN * 8,
                          initial_value=int(
                              initial_iv_server_to_client.encode('hex'),
                              AES_BLOCK_LEN))
        encryption_key_server_to_client = _derive_encryption_key(
            key_derivation_options, 'D', AES_BLOCK_LEN)
        self._aes_server_to_client = AES.new(encryption_key_server_to_client,
                                             AES.MODE_CTR,
                                             counter=ctr)
        self._integrity_key_server_to_client = _derive_encryption_key(
            key_derivation_options, 'F')
Exemplo n.º 17
0
    def _do_key_exchange(self):
        # Generate and send our side of the kex exchange handshake
        client_kex_init = _generate_client_kex_init()
        self.send(client_kex_init)

        # Receive the server's side of the key exchange handshake
        server_kex_init = self.read()
        data_ptr, ssh_msg_type = parse_byte(server_kex_init, 0)
        assert ssh_msg_type == SSH_MSG_NUMS['SSH_MSG_KEXINIT']

        # Read the cookie from the server
        cookie = server_kex_init[data_ptr:data_ptr + COOKIE_LEN]
        data_ptr += COOKIE_LEN
        print colors.cyan('Cookie: %s' % repr(cookie))

        # Read the algorithm lists from the server
        data_ptr, kex_algorithms = parse_name_list(server_kex_init, data_ptr)
        data_ptr, server_host_key_algorithms = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, encryption_algorithms_client_to_server = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, encryption_algorithms_server_to_client = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, mac_algorithms_client_to_server = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, mac_algorithms_server_to_client = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, compression_algorithms_client_to_server = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, compression_algorithms_server_to_client = parse_name_list(
            server_kex_init, data_ptr)
        data_ptr, _ = parse_name_list(server_kex_init, data_ptr)
        data_ptr, _ = parse_name_list(server_kex_init, data_ptr)

        # Check that the server did not try to predict the key exchange protocol we'd be using
        data_ptr, first_kex_packet_follows = parse_byte(
            server_kex_init, data_ptr)
        assert first_kex_packet_follows == 0, 'Additional data in key exchange packet'

        # Check that the reserved bytes are also present in the message
        assert len(server_kex_init) == data_ptr + KEX_RESERVED_BYTES_LEN, \
          'Wrong amount of data left in packet'

        # Check that we'll be able to talk to this server correctly
        assert KEX_ALGORITHM in kex_algorithms
        assert SERVER_HOST_KEY_ALGORITHM in server_host_key_algorithms
        assert ENCRYPTION_ALGORITHM in encryption_algorithms_client_to_server
        assert ENCRYPTION_ALGORITHM in encryption_algorithms_server_to_client
        assert MAC_ALGORITHM in mac_algorithms_client_to_server
        assert MAC_ALGORITHM in mac_algorithms_server_to_client
        assert COMPRESSION_ALGORITHM in compression_algorithms_client_to_server
        assert COMPRESSION_ALGORITHM in compression_algorithms_server_to_client

        # Derive Diffie Hellman shared keys
        self._run_diffie_hellman_group14_sha1_key_exchange(
            server_kex_init, client_kex_init)

        # Swap to using those keys
        self.send(generate_byte(SSH_MSG_NUMS['SSH_MSG_NEWKEYS']))
        response = self.read()
        index, response_type = parse_byte(response, 0)
        assert response_type == SSH_MSG_NUMS['SSH_MSG_NEWKEYS'], \
          'Unknown SSH message type: %d' % response_type
        assert index == len(response), 'Additional data in response'

        self._encryption_negotiated = True

        print colors.cyan('Successfully exchanged keys!')
Exemplo n.º 18
0
  def _do_user_auth(self):
    # Ask the server whether it supports doing SSH user auth
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_SERVICE_REQUEST']))
    msg.append(generate_string(SSH_USERAUTH_STRING))
    self._ssh_transport_connection.send(''.join(msg))

    # Check that it says yes
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    assert msg_type == SSH_MSG_NUMS['SSH_MSG_SERVICE_ACCEPT'], \
      'Unknown message type received: %d' % msg_type
    index, service_name = parse_string(data, index)
    assert service_name == SSH_USERAUTH_STRING

    print colors.cyan("Let's do ssh-userauth!")

    # Ask the server which authentication methods it supports
    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_USERAUTH_REQUEST']))
    msg.append(generate_string(self.username.encode('utf-8')))
    msg.append(generate_string('ssh-connection'))
    msg.append(generate_string('none'))
    self._ssh_transport_connection.send(''.join(msg))

    # Check that publickey is one of them
    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    index, supported_auth_methods = parse_name_list(data, index)
    index, partial_success_byte = parse_byte(data, index)
    partial_success = partial_success_byte != 0

    assert msg_type == SSH_MSG_NUMS['SSH_MSG_USERAUTH_FAILURE'], \
      'Unknown message type: %d' % msg_type
    assert 'publickey' in supported_auth_methods, \
      'Server does not support public key authentication'
    assert not partial_success

    # Try to public key auth
    rsa_key = RSA.importKey(open(self.keyfile))
    pkcs_key = PKCS1_v1_5.new(rsa_key)

    msg = []
    msg.append(generate_byte(SSH_MSG_NUMS['SSH_MSG_USERAUTH_REQUEST']))
    msg.append(generate_string(self.username.encode('utf-8')))
    msg.append(generate_string('ssh-connection'))
    msg.append(generate_string('publickey'))
    msg.append(generate_byte(1)) # True: we really do want to authenticate
    msg.append(generate_string('ssh-rsa'))
    msg.append(generate_string(
        generate_string('ssh-rsa') + generate_mpint(rsa_key.e) + generate_mpint(rsa_key.n)
    ))

    # Desperately try to figure out how signing works in this silly encapsulating protocol
    signed_data = generate_string(self._ssh_transport_connection.session_id) + ''.join(msg)
    # OMG Pycrypto, did it have to be *your* SHA1 implementation?
    signature = pkcs_key.sign(SHA.new(signed_data))
    msg.append(generate_string(generate_string('ssh-rsa') + generate_string(signature)))

    # Send the public key auth message to the server
    self._ssh_transport_connection.send(''.join(msg))

    data = self._ssh_transport_connection.read()
    index, msg_type = parse_byte(data, 0)
    assert msg_type == SSH_MSG_NUMS['SSH_MSG_USERAUTH_SUCCESS'], \
      'Unknown message type: %d' % msg_type

    print colors.cyan('Successfully user authed!')