Beispiel #1
0
    def reconnect(s: 'SSH_Socket', gex_alg: str) -> bool:
        if s.is_connected():
            return True

        err = s.connect()
        if err is not None:
            return False

        unused = None  # pylint: disable=unused-variable
        unused2 = None  # pylint: disable=unused-variable
        unused, unused2, err = s.get_banner()
        if err is not None:
            s.close()
            return False

        # Parse the server's initial KEX.
        packet_type = 0  # pylint: disable=unused-variable
        packet_type, payload = s.read_packet(2)
        kex = SSH2_Kex.parse(payload)

        # Send our KEX using the specified group-exchange and most of the
        # server's own values.
        client_kex = SSH2_Kex(os.urandom(16), [gex_alg], kex.key_algorithms,
                              kex.client, kex.server, False, 0)
        s.write_byte(Protocol.MSG_KEXINIT)
        client_kex.write(s)
        s.send_packet()
        return True
Beispiel #2
0
    def send_algorithms(self) -> None:
        '''Sends the list of supported host keys, key exchanges, ciphers, and MACs.  Emulates OpenSSH v8.2.'''

        key_exchanges = [
            'curve25519-sha256', '*****@*****.**',
            'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521',
            'diffie-hellman-group-exchange-sha256',
            'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512',
            'diffie-hellman-group14-sha256'
        ]
        hostkeys = [
            'rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa', 'ecdsa-sha2-nistp256',
            'ssh-ed25519'
        ]
        ciphers = [
            '*****@*****.**', 'aes128-ctr', 'aes192-ctr',
            'aes256-ctr', '*****@*****.**', '*****@*****.**'
        ]
        macs = [
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', 'hmac-sha2-256', 'hmac-sha2-512',
            'hmac-sha1'
        ]
        compressions = ['none', '*****@*****.**']
        languages = ['']

        kexparty = SSH2_KexParty(ciphers, macs, compressions, languages)
        kex = SSH2_Kex(os.urandom(16), key_exchanges, hostkeys, kexparty,
                       kexparty, False, 0)

        self.write_byte(Protocol.MSG_KEXINIT)
        kex.write(self)
        self.send_packet()
def kex(ssh_audit):
    kex_algs, key_algs = [], []
    enc, mac, compression, languages = [], [], ['none'], []
    cli = SSH2_KexParty(enc, mac, compression, languages)
    enc, mac, compression, languages = [], [], ['none'], []
    srv = SSH2_KexParty(enc, mac, compression, languages)
    cookie = os.urandom(16)
    kex = SSH2_Kex(cookie, kex_algs, key_algs, cli, srv, 0)
    return kex
Beispiel #4
0
    def reconnect(out: 'OutputBuffer', s: 'SSH_Socket', kex: 'SSH2_Kex', gex_alg: str) -> bool:
        if s.is_connected():
            return True

        err = s.connect()
        if err is not None:
            out.v(err, write_now=True)
            return False

        _, _, err = s.get_banner()
        if err is not None:
            out.v(err, write_now=True)
            s.close()
            return False

        # Send our KEX using the specified group-exchange and most of the
        # server's own values.
        s.send_kexinit(key_exchanges=[gex_alg], hostkeys=kex.key_algorithms, ciphers=kex.server.encryption, macs=kex.server.mac, compressions=kex.server.compression, languages=kex.server.languages)

        # Parse the server's KEX.
        _, payload = s.read_packet(2)
        SSH2_Kex.parse(payload)

        return True
Beispiel #5
0
    def send_kexinit(
        self,
        key_exchanges: List[str] = [
            'curve25519-sha256', '*****@*****.**',
            'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521',
            'diffie-hellman-group-exchange-sha256',
            'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512',
            'diffie-hellman-group14-sha256'
        ],
        hostkeys: List[str] = [
            'rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa', 'ecdsa-sha2-nistp256',
            'ssh-ed25519'
        ],
        ciphers: List[str] = [
            '*****@*****.**', 'aes128-ctr', 'aes192-ctr',
            'aes256-ctr', '*****@*****.**', '*****@*****.**'
        ],
        macs: List[str] = [
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', '*****@*****.**',
            '*****@*****.**', 'hmac-sha2-256', 'hmac-sha2-512',
            'hmac-sha1'
        ],
        compressions: List[str] = ['none', '*****@*****.**'],
        languages: List[str] = ['']
    ) -> None:  # pylint: disable=dangerous-default-value
        '''Sends the list of supported host keys, key exchanges, ciphers, and MACs.  Emulates OpenSSH v8.2.'''

        self.__outputbuffer.d('KEX initialisation...', write_now=True)

        kexparty = SSH2_KexParty(ciphers, macs, compressions, languages)
        kex = SSH2_Kex(os.urandom(16), key_exchanges, hostkeys, kexparty,
                       kexparty, False, 0)

        self.write_byte(Protocol.MSG_KEXINIT)
        kex.write(self)
        self.send_packet()
Beispiel #6
0
def audit(out: OutputBuffer, aconf: AuditConf, sshv: Optional[int] = None, print_target: bool = False) -> int:
    program_retval = exitcodes.GOOD
    out.batch = aconf.batch
    out.verbose = aconf.verbose
    out.debug = aconf.debug
    out.level = aconf.level
    out.use_colors = aconf.colors
    s = SSH_Socket(out, aconf.host, aconf.port, aconf.ip_version_preference, aconf.timeout, aconf.timeout_set)

    if aconf.client_audit:
        out.v("Listening for client connection on port %d..." % aconf.port, write_now=True)
        s.listen_and_accept()
    else:
        out.v("Starting audit of %s:%d..." % ('[%s]' % aconf.host if Utils.is_ipv6_address(aconf.host) else aconf.host, aconf.port), write_now=True)
        err = s.connect()

        if err is not None:
            out.fail(err)

            # If we're running against multiple targets, return a connection error to the calling worker thread.  Otherwise, write the error message to the console and exit.
            if len(aconf.target_list) > 0:
                return exitcodes.CONNECTION_ERROR
            else:
                out.write()
                sys.exit(exitcodes.CONNECTION_ERROR)

    if sshv is None:
        sshv = 2 if aconf.ssh2 else 1
    err = None
    banner, header, err = s.get_banner(sshv)
    if banner is None:
        if err is None:
            err = '[exception] did not receive banner.'
        else:
            err = '[exception] did not receive banner: {}'.format(err)
    if err is None:
        s.send_kexinit()  # Send the algorithms we support (except we don't since this isn't a real SSH connection).

        packet_type, payload = s.read_packet(sshv)
        if packet_type < 0:
            try:
                if len(payload) > 0:
                    payload_txt = payload.decode('utf-8')
                else:
                    payload_txt = 'empty'
            except UnicodeDecodeError:
                payload_txt = '"{}"'.format(repr(payload).lstrip('b')[1:-1])
            if payload_txt == 'Protocol major versions differ.':
                if sshv == 2 and aconf.ssh1:
                    ret = audit(out, aconf, 1)
                    out.write()
                    return ret
            err = '[exception] error reading packet ({})'.format(payload_txt)
        else:
            err_pair = None
            if sshv == 1 and packet_type != Protocol.SMSG_PUBLIC_KEY:
                err_pair = ('SMSG_PUBLIC_KEY', Protocol.SMSG_PUBLIC_KEY)
            elif sshv == 2 and packet_type != Protocol.MSG_KEXINIT:
                err_pair = ('MSG_KEXINIT', Protocol.MSG_KEXINIT)
            if err_pair is not None:
                fmt = '[exception] did not receive {0} ({1}), ' + \
                      'instead received unknown message ({2})'
                err = fmt.format(err_pair[0], err_pair[1], packet_type)
    if err is not None:
        output(out, aconf, banner, header)
        out.fail(err)
        return exitcodes.CONNECTION_ERROR
    if sshv == 1:
        program_retval = output(out, aconf, banner, header, pkm=SSH1_PublicKeyMessage.parse(payload))
    elif sshv == 2:
        try:
            kex = SSH2_Kex.parse(payload)
        except Exception:
            out.fail("Failed to parse server's kex.  Stack trace:\n%s" % str(traceback.format_exc()))
            return exitcodes.CONNECTION_ERROR

        if aconf.client_audit is False:
            HostKeyTest.run(out, s, kex)
            GEXTest.run(out, s, kex)

        # This is a standard audit scan.
        if (aconf.policy is None) and (aconf.make_policy is False):
            program_retval = output(out, aconf, banner, header, client_host=s.client_host, kex=kex, print_target=print_target)

        # This is a policy test.
        elif (aconf.policy is not None) and (aconf.make_policy is False):
            program_retval = exitcodes.GOOD if evaluate_policy(out, aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE

        # A new policy should be made from this scan.
        elif (aconf.policy is None) and (aconf.make_policy is True):
            make_policy(aconf, banner, kex, s.client_host)

        else:
            raise RuntimeError('Internal error while handling output: %r %r' % (aconf.policy is None, aconf.make_policy))

    return program_retval
Beispiel #7
0
def audit(aconf: AuditConf,
          sshv: Optional[int] = None,
          print_target: bool = False) -> int:
    program_retval = exitcodes.GOOD
    out.batch = aconf.batch
    out.verbose = aconf.verbose
    out.level = aconf.level
    out.use_colors = aconf.colors
    s = SSH_Socket(aconf.host, aconf.port, aconf.ipvo, aconf.timeout,
                   aconf.timeout_set)
    if aconf.client_audit:
        s.listen_and_accept()
    else:
        err = s.connect()
        if err is not None:
            out.fail(err)
            sys.exit(exitcodes.CONNECTION_ERROR)

    if sshv is None:
        sshv = 2 if aconf.ssh2 else 1
    err = None
    banner, header, err = s.get_banner(sshv)
    if banner is None:
        if err is None:
            err = '[exception] did not receive banner.'
        else:
            err = '[exception] did not receive banner: {}'.format(err)
    if err is None:
        s.send_algorithms(
        )  # Send the algorithms we support (except we don't since this isn't a real SSH connection).

        packet_type, payload = s.read_packet(sshv)
        if packet_type < 0:
            try:
                if len(payload) > 0:
                    payload_txt = payload.decode('utf-8')
                else:
                    payload_txt = u'empty'
            except UnicodeDecodeError:
                payload_txt = u'"{}"'.format(repr(payload).lstrip('b')[1:-1])
            if payload_txt == u'Protocol major versions differ.':
                if sshv == 2 and aconf.ssh1:
                    return audit(aconf, 1)
            err = '[exception] error reading packet ({})'.format(payload_txt)
        else:
            err_pair = None
            if sshv == 1 and packet_type != Protocol.SMSG_PUBLIC_KEY:
                err_pair = ('SMSG_PUBLIC_KEY', Protocol.SMSG_PUBLIC_KEY)
            elif sshv == 2 and packet_type != Protocol.MSG_KEXINIT:
                err_pair = ('MSG_KEXINIT', Protocol.MSG_KEXINIT)
            if err_pair is not None:
                fmt = '[exception] did not receive {0} ({1}), ' + \
                      'instead received unknown message ({2})'
                err = fmt.format(err_pair[0], err_pair[1], packet_type)
    if err is not None:
        output(aconf, banner, header)
        out.fail(err)
        return exitcodes.CONNECTION_ERROR
    if sshv == 1:
        program_retval = output(aconf,
                                banner,
                                header,
                                pkm=SSH1_PublicKeyMessage.parse(payload))
    elif sshv == 2:
        kex = SSH2_Kex.parse(payload)
        if aconf.client_audit is False:
            HostKeyTest.run(s, kex)
            GEXTest.run(s, kex)

        # This is a standard audit scan.
        if (aconf.policy is None) and (aconf.make_policy is False):
            program_retval = output(aconf,
                                    banner,
                                    header,
                                    client_host=s.client_host,
                                    kex=kex,
                                    print_target=print_target)

        # This is a policy test.
        elif (aconf.policy is not None) and (aconf.make_policy is False):
            program_retval = exitcodes.GOOD if evaluate_policy(
                aconf, banner, s.client_host, kex=kex) else exitcodes.FAILURE

        # A new policy should be made from this scan.
        elif (aconf.policy is None) and (aconf.make_policy is True):
            make_policy(aconf, banner, kex, s.client_host)

        else:
            raise RuntimeError('Internal error while handling output: %r %r' %
                               (aconf.policy is None, aconf.make_policy))

    return program_retval
Beispiel #8
0
    def perform_test(s: 'SSH_Socket', server_kex: 'SSH2_Kex', kex_str: str,
                     kex_group: 'KexDH',
                     host_key_types: Dict[str, Dict[str, bool]]) -> None:
        hostkey_modulus_size = 0
        ca_modulus_size = 0

        # If the connection still exists, close it so we can test
        # using a clean slate (otherwise it may exist in a non-testable
        # state).
        if s.is_connected():
            s.close()

        # For each host key type...
        for host_key_type in host_key_types:
            # Skip those already handled (i.e.: those in the RSA family, as testing one tests them all).
            if 'parsed' in host_key_types[host_key_type] and host_key_types[
                    host_key_type]['parsed']:
                continue

            # If this host key type is supported by the server, we test it.
            if host_key_type in server_kex.key_algorithms:
                cert = host_key_types[host_key_type]['cert']
                variable_key_len = host_key_types[host_key_type][
                    'variable_key_len']

                # If the connection is closed, re-open it and get the kex again.
                if not s.is_connected():
                    err = s.connect()
                    if err is not None:
                        return

                    _, _, err = s.get_banner()
                    if err is not None:
                        s.close()
                        return

                    # Parse the server's initial KEX.
                    packet_type = 0  # pylint: disable=unused-variable
                    packet_type, payload = s.read_packet()
                    SSH2_Kex.parse(payload)

                # Send the server our KEXINIT message, using only our
                # selected kex and host key type.  Send the server's own
                # list of ciphers and MACs back to it (this doesn't
                # matter, really).
                client_kex = SSH2_Kex(os.urandom(16), [kex_str],
                                      [host_key_type], server_kex.client,
                                      server_kex.server, False, 0)

                s.write_byte(Protocol.MSG_KEXINIT)
                client_kex.write(s)
                s.send_packet()

                # Do the initial DH exchange.  The server responds back
                # with the host key and its length.  Bingo.  We also get back the host key fingerprint.
                kex_group.send_init(s)
                host_key = kex_group.recv_reply(s, variable_key_len)
                if host_key is not None:
                    server_kex.set_host_key(host_key_type, host_key)

                hostkey_modulus_size = kex_group.get_hostkey_size()
                ca_modulus_size = kex_group.get_ca_size()

                # Close the socket, as the connection has
                # been put in a state that later tests can't use.
                s.close()

                # If the host key modulus or CA modulus was successfully parsed, check to see that its a safe size.
                if hostkey_modulus_size > 0 or ca_modulus_size > 0:
                    # Set the hostkey size for all RSA key types since 'ssh-rsa',
                    # 'rsa-sha2-256', etc. are all using the same host key.
                    # Note, however, that this may change in the future.
                    if cert is False and host_key_type in HostKeyTest.RSA_FAMILY:
                        for rsa_type in HostKeyTest.RSA_FAMILY:
                            server_kex.set_rsa_key_size(
                                rsa_type, hostkey_modulus_size)
                    elif cert is True:
                        server_kex.set_rsa_key_size(host_key_type,
                                                    hostkey_modulus_size,
                                                    ca_modulus_size)

                    # Keys smaller than 2048 result in a failure.  Update the database accordingly.
                    if (cert is False) and (hostkey_modulus_size < 2048):
                        for rsa_type in HostKeyTest.RSA_FAMILY:
                            alg_list = SSH2_KexDB.ALGORITHMS['key'][rsa_type]
                            alg_list.append([
                                'using small %d-bit modulus' %
                                hostkey_modulus_size
                            ])
                    elif (cert is True) and (
                        (hostkey_modulus_size < 2048) or
                        (ca_modulus_size > 0 and ca_modulus_size < 2048)):  # pylint: disable=chained-comparison
                        alg_list = SSH2_KexDB.ALGORITHMS['key'][host_key_type]
                        min_modulus = min(hostkey_modulus_size,
                                          ca_modulus_size)
                        min_modulus = min_modulus if min_modulus > 0 else max(
                            hostkey_modulus_size, ca_modulus_size)
                        alg_list.append(
                            ['using small %d-bit modulus' % min_modulus])

                # If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all).
                if host_key_type in HostKeyTest.RSA_FAMILY:
                    for rsa_type in HostKeyTest.RSA_FAMILY:
                        host_key_types[rsa_type]['parsed'] = True
                else:
                    host_key_types[host_key_type]['parsed'] = True
Beispiel #9
0
    def perform_test(out: 'OutputBuffer', s: 'SSH_Socket',
                     server_kex: 'SSH2_Kex', kex_str: str, kex_group: 'KexDH',
                     host_key_types: Dict[str, Dict[str, bool]]) -> None:
        hostkey_modulus_size = 0
        ca_modulus_size = 0

        # If the connection still exists, close it so we can test
        # using a clean slate (otherwise it may exist in a non-testable
        # state).
        if s.is_connected():
            s.close()

        # For each host key type...
        for host_key_type in host_key_types:
            # Skip those already handled (i.e.: those in the RSA family, as testing one tests them all).
            if 'parsed' in host_key_types[host_key_type] and host_key_types[
                    host_key_type]['parsed']:
                continue

            # If this host key type is supported by the server, we test it.
            if host_key_type in server_kex.key_algorithms:
                out.d('Preparing to obtain ' + host_key_type + ' host key...',
                      write_now=True)

                cert = host_key_types[host_key_type]['cert']
                variable_key_len = host_key_types[host_key_type][
                    'variable_key_len']

                # If the connection is closed, re-open it and get the kex again.
                if not s.is_connected():
                    err = s.connect()
                    if err is not None:
                        out.v(err, write_now=True)
                        return

                    _, _, err = s.get_banner()
                    if err is not None:
                        out.v(err, write_now=True)
                        s.close()
                        return

                    # Send our KEX using the specified group-exchange and most of the server's own values.
                    s.send_kexinit(key_exchanges=[kex_str],
                                   hostkeys=[host_key_type],
                                   ciphers=server_kex.server.encryption,
                                   macs=server_kex.server.mac,
                                   compressions=server_kex.server.compression,
                                   languages=server_kex.server.languages)

                    try:
                        # Parse the server's KEX.
                        _, payload = s.read_packet()
                        SSH2_Kex.parse(payload)
                    except Exception:
                        out.v(
                            "Failed to parse server's kex.  Stack trace:\n%s" %
                            str(traceback.format_exc()),
                            write_now=True)
                        return

                # Do the initial DH exchange.  The server responds back
                # with the host key and its length.  Bingo.  We also get back the host key fingerprint.
                kex_group.send_init(s)
                try:
                    host_key = kex_group.recv_reply(s, variable_key_len)
                    if host_key is not None:
                        server_kex.set_host_key(host_key_type, host_key)
                except Exception:
                    pass

                hostkey_modulus_size = kex_group.get_hostkey_size()
                ca_modulus_size = kex_group.get_ca_size()

                # Close the socket, as the connection has
                # been put in a state that later tests can't use.
                s.close()

                # If the host key modulus or CA modulus was successfully parsed, check to see that its a safe size.
                if hostkey_modulus_size > 0 or ca_modulus_size > 0:
                    # Set the hostkey size for all RSA key types since 'ssh-rsa',
                    # 'rsa-sha2-256', etc. are all using the same host key.
                    # Note, however, that this may change in the future.
                    if cert is False and host_key_type in HostKeyTest.RSA_FAMILY:
                        for rsa_type in HostKeyTest.RSA_FAMILY:
                            server_kex.set_rsa_key_size(
                                rsa_type, hostkey_modulus_size)
                    elif cert is True:
                        server_kex.set_rsa_key_size(host_key_type,
                                                    hostkey_modulus_size,
                                                    ca_modulus_size)

                    # Keys smaller than 2048 result in a failure.  Update the database accordingly.
                    if (cert is False) and (hostkey_modulus_size < 2048):
                        for rsa_type in HostKeyTest.RSA_FAMILY:
                            alg_list = SSH2_KexDB.ALGORITHMS['key'][rsa_type]

                            # If no failure list exists, add an empty failure list.
                            if len(alg_list) < 2:
                                alg_list.append([])
                            alg_list[1].append('using small %d-bit modulus' %
                                               hostkey_modulus_size)
                    elif (cert is True) and (
                        (hostkey_modulus_size < 2048) or
                        (ca_modulus_size > 0 and ca_modulus_size < 2048)):  # pylint: disable=chained-comparison
                        alg_list = SSH2_KexDB.ALGORITHMS['key'][host_key_type]
                        min_modulus = min(hostkey_modulus_size,
                                          ca_modulus_size)
                        min_modulus = min_modulus if min_modulus > 0 else max(
                            hostkey_modulus_size, ca_modulus_size)

                        # If no failure list exists, add an empty failure list.
                        if len(alg_list) < 2:
                            alg_list.append([])
                        alg_list[1].append('using small %d-bit modulus' %
                                           min_modulus)

                # If this host key type is in the RSA family, then mark them all as parsed (since results in one are valid for them all).
                if host_key_type in HostKeyTest.RSA_FAMILY:
                    for rsa_type in HostKeyTest.RSA_FAMILY:
                        host_key_types[rsa_type]['parsed'] = True
                else:
                    host_key_types[host_key_type]['parsed'] = True