Beispiel #1
0
class CompressionPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="CompressionPlugin",
                                            description="")
    interface.add_command(
        command="compression",
        help="Tests the server(s) for Zlib compression support.")

    def process_task(self, server_info, command, options_dict=None):
        ssl_connection = server_info.get_preconfigured_ssl_connection()

        # Make sure OpenSSL was built with support for compression to avoid false negatives
        if 'zlib compression' not in ssl_connection.get_available_compression_methods(
        ):
            raise RuntimeError(
                'OpenSSL was not built with support for zlib / compression. '
                'Did you build nassl yourself ?')

        try:  # Perform the SSL handshake
            ssl_connection.connect()
            compression_name = ssl_connection.get_current_compression_method()
        except ClientCertificateRequested:
            # The server asked for a client cert
            compression_name = ssl_connection.get_current_compression_method()
        finally:
            ssl_connection.close()

        return CompressionResult(server_info, command, options_dict,
                                 compression_name)
Beispiel #2
0
class ShellshockTesterPlugin(plugin_base.PluginBase):
    """class ShellshockTesterPlugin
	
	This class inherited from abstract class PluginBase. Instance of this class tests server for vulnerability CVE-2014-6271 and makes decision if the server is vulnerable.
	"""
    interface = plugin_base.PluginInterface(
        "ShellshockTesterPlugin",
        "Tests the server(s) for Shellshock vulnerability (CVE-2014-6271)")
    interface.add_command(command="shellshock",
                          help="Tests server for vulnerability CVE-2014-6271")
    interface.add_option(
        option="http_path",
        help=
        "Specify path which following the domain name or ip address. Default is set to /",
        dest="path")

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     option_dict=None):
        if option_dict and 'path' in option_dict.keys():
            path = str(option_dict['path'])
        else:
            path = '/'
        if server_connectivity_info.port == 80:
            conn = httplib.HTTPConnection(server_connectivity_info.ip_address,
                                          server_connectivity_info.port)
        elif server_connectivity_info.port == 443:
            conn = httplib.HTTPSConnection(
                server_connectivity_info.ip_address,
                server_connectivity_info.port,
                context=ssl._create_unverified_context())
        else:
            raise ValueError(
                "ShellshockTesterPlugin: Can\'t make test for this port {0}".
                format(server_connectivity_info.port))

        try:
            conn.connect()
        except Exception as e:
            raise ValueError(
                "ShellshockTesterPlugin: Connection error for port {0}. {1}".
                format(server_connectivity_info.port, str(e)))
        else:
            conn.request("GET", path, "", {
                'User-Agent':
                '() { :; }; echo; echo Vulnerable to CVE-2014-6271'
            })
            response = conn.getresponse()

        return ShellshockTesterResult(server_connectivity_info,
                                      plugin_command, option_dict,
                                      self.is_vulnerable(response))

    def is_vulnerable(self, message):
        """Returns true if value in parameter message contains string 'Vulnerable to CVE-2014-6271', otherwise returns false
		"""
        msg = message.read()
        return 'Vulnerable to CVE-2014-6271' in msg
Beispiel #3
0
class HttpHeadersPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="HttpHeadersPlugin",
                                            description='')
    interface.add_command(
        command="http_headers",
        help=
        "Checks for the HTTP Strict Transport Security (HSTS) and HTTP Public Key Pinning (HPKP) HTTP headers "
        "within the response sent back by the server(s). Also computes the HPKP pins for the server(s)' current "
        "certificate chain.")

    def process_task(self, server_info, command, options_dict=None):

        if server_info.tls_wrapped_protocol not in [
                TlsWrappedProtocolEnum.PLAIN_TLS, TlsWrappedProtocolEnum.HTTPS
        ]:
            raise ValueError(
                'Cannot test for HTTP headers on a StartTLS connection.')

        hsts_header, hpkp_header, hpkp_report_only, certificate_chain = self._get_security_headers(
            server_info)
        return HttpHeadersResult(server_info, command, options_dict,
                                 hsts_header, hpkp_header, hpkp_report_only,
                                 certificate_chain)

    @classmethod
    def _get_security_headers(cls, server_info):
        hpkp_report_only = False

        # Perform the SSL handshake
        ssl_connection = server_info.get_preconfigured_ssl_connection()
        ssl_connection.connect()
        certificate_chain = ssl_connection.get_peer_cert_chain()

        # Send an HTTP GET request to the server
        ssl_connection.write(
            HttpRequestGenerator.get_request(host=server_info.hostname))
        http_resp = HttpResponseParser.parse(ssl_connection)
        ssl_connection.close()

        if http_resp.version == 9:
            # HTTP 0.9 => Probably not an HTTP response
            raise ValueError('Server did not return an HTTP response')
        else:
            hsts_header = http_resp.getheader('strict-transport-security',
                                              None)
            hpkp_header = http_resp.getheader('public-key-pins', None)
            if hpkp_header is None:
                hpkp_report_only = True
                hpkp_header = http_resp.getheader(
                    'public-key-pins-report-only', None)

        # We do not follow redirections because the security headers must be set on the first page according to
        # https://hstspreload.appspot.com/:
        # "If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS
        # header (rather than the page it redirects to)."

        return hsts_header, hpkp_header, hpkp_report_only, certificate_chain
Beispiel #4
0
class CRIMEVulnerabilityTesterPlugin(plugin_base.PluginBase):
    """class CRIMEVulnerabilityTesterPlugin
    
    This class inherited from abstract class PluginBase. Instance of this class tests server for vulnerability CVE-2012-4929 and makes decision if the server is vulnerable.
    """
    interface = plugin_base.PluginInterface(
        "CRIMEVulnerabilityTesterPlugin",
        "Scans the server(s) and checks if requirements for CRIME attack are satisfied."
    )
    interface.add_command(command="crime",
                          help="Tests server for CVE-2012-4929 vulnerability.")

    VERBOSE_LINE = '  Compression for {protocol:<10}{status:<20}'.format

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     options_dict=None):
        if options_dict and 'verbose' in options_dict.keys():
            verbose_mode = options_dict['verbose']
        else:
            verbose_mode = False
        is_vulnerable = False
        protocols = {'SSLv3', 'SSLv2', 'TLSv1', 'TLSv1.1', 'TLSv1.2'}
        if verbose_mode:
            print '  VERBOSE MODE PRINT'
            print '  ------------------'
        for proto in protocols:
            ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(
                override_ssl_version=PROTOCOL_VERSION[proto])
            try:
                ssl_connection.connect()
                compression = True if ssl_connection.get_current_compression_method(
                ) is not None else False
                is_vulnerable |= compression
            except SSLHandshakeRejected as e:
                if verbose_mode:
                    print self.VERBOSE_LINE(
                        protocol=proto,
                        status='Connect error: {error}'.format(error=str(e)))
                pass
            else:
                if verbose_mode:
                    print self.VERBOSE_LINE(protocol=proto,
                                            status=str(compression))
            finally:
                ssl_connection.close()
        if verbose_mode:
            print '  ----------------------'
            print '  END VERBOSE MODE PRINT'
            print '  ----------------------'

        return CRIMEVulnerabilityTesterResult(server_connectivity_info,
                                              plugin_command, options_dict,
                                              is_vulnerable)
Beispiel #5
0
class FallbackScsvPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="FallbackScsvPlugin",
                                            description="")
    interface.add_command(
        command="fallback",
        help=
        "Checks support for the TLS_FALLBACK_SCSV cipher suite to prevent downgrade attacks."
    )

    def process_task(self, server_info, plugin_command, plugin_options=None):
        if server_info.highest_ssl_version_supported <= SSLV3:
            raise ValueError(
                'Server only supports SSLv3; no downgrade attacks are possible'
            )

        # Try to connect using a lower TLS version with the fallback cipher suite enabled
        ssl_version_downgrade = server_info.highest_ssl_version_supported - 1
        ssl_connection = server_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version_downgrade)
        ssl_connection.set_mode(SSL_MODE_SEND_FALLBACK_SCSV)

        supports_fallback_scsv = False
        try:
            # Perform the SSL handshake
            ssl_connection.connect()

        except _nassl.OpenSSLError as e:
            # This is the right, specific alert the server should return
            if 'tlsv1 alert inappropriate fallback' in str(e.args):
                supports_fallback_scsv = True
            else:
                raise

        except SSLHandshakeRejected:
            # If the handshake is rejected, we assume downgrade attacks are prevented (this is how F5 balancers do it)
            # although it could also be because the server does not support this version of TLS
            # https://github.com/nabla-c0d3/sslyze/issues/119
            supports_fallback_scsv = True

        finally:
            ssl_connection.close()

        return FallbackScsvResult(server_info, plugin_command, plugin_options,
                                  supports_fallback_scsv)
Beispiel #6
0
class HeartbleedPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface("HeartbleedPlugin", "")
    interface.add_command(
        command="heartbleed",
        help=
        "Tests the server(s) for the OpenSSL Heartbleed vulnerability (experimental)."
    )

    def process_task(self, server_info, command, options_dict=None):
        ssl_connection = server_info.get_preconfigured_ssl_connection()
        ssl_connection.ssl_version = server_info.highest_ssl_version_supported  # Needed by the heartbleed payload

        # Awful hack #1: replace nassl.sslClient.do_handshake() with a heartbleed
        # checking SSL handshake so that all the SSLyze options
        # (startTLS, proxy, etc.) still work
        ssl_connection.do_handshake = types.MethodType(
            do_handshake_with_heartbleed, ssl_connection)

        heartbleed = None
        try:  # Perform the SSL handshake
            ssl_connection.connect()
        except HeartbleedSent:
            # Awful hack #2: directly read the underlying network socket
            heartbleed = ssl_connection._sock.recv(16381)
        finally:
            ssl_connection.close()

        # Text output
        is_vulnerable_to_heartbleed = False
        if heartbleed is None:
            raise ValueError("Error: connection failed.")
        elif '\x01\x01\x01\x01\x01\x01\x01\x01\x01' in heartbleed:
            # Server replied with our hearbeat payload
            is_vulnerable_to_heartbleed = True

        return HeartbleedResult(server_info, command, options_dict,
                                is_vulnerable_to_heartbleed)
Beispiel #7
0
class FallbackScsvPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="FallbackScsvPlugin",
                                            description="")
    interface.add_command(
        command="fallback",
        help=
        "Checks support for the TLS_FALLBACK_SCSV cipher suite to prevent downgrade attacks."
    )

    def process_task(self, server_info, plugin_command, plugin_options=None):
        if server_info.highest_ssl_version_supported <= SSLV3:
            raise ValueError(
                'Server only supports SSLv3; no downgrade attacks are possible'
            )

        # Try to connect using a lower TLS version with the fallback cipher suite enabled
        ssl_version_downgrade = server_info.highest_ssl_version_supported - 1
        ssl_connection = server_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version_downgrade)
        ssl_connection.set_mode(SSL_MODE_SEND_FALLBACK_SCSV)

        supports_fallback_scsv = False
        try:
            # Perform the SSL handshake
            ssl_connection.connect()

        except _nassl.OpenSSLError as e:
            if 'tlsv1 alert inappropriate fallback' in str(e.args):
                supports_fallback_scsv = True
            else:
                raise

        finally:
            ssl_connection.close()

        return FallbackScsvResult(server_info, plugin_command, plugin_options,
                                  supports_fallback_scsv)
Beispiel #8
0
class CertificateInfoPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="CertificateInfoPlugin",
                                            description='')
    interface.add_command(
        command="certinfo_basic",
        help=
        "Verifies the validity of the server(s) certificate(s) against various trust stores, checks for support "
        "for OCSP stapling, and prints relevant fields of the certificate.")
    interface.add_command(
        command="certinfo_full",
        help=
        "Same as --certinfo_basic but also prints the full server certificate."
    )
    interface.add_option(
        option="ca_file",
        help="Local Certificate Authority file (in PEM format), to verify the "
        "validity of the server(s) certificate(s) against.",
        dest="ca_file")

    def process_task(self, server_info, command, options_dict=None):

        if command == 'certinfo_basic':
            result_class = CertInfoBasicResult
        elif command == 'certinfo_full':
            result_class = CertInfoFullResult
        else:
            raise ValueError("PluginCertInfo: Unknown command.")

        final_trust_store_list = list(DEFAULT_TRUST_STORE_LIST)
        if options_dict and 'ca_file' in options_dict.keys():
            final_trust_store_list.append(
                TrustStore(options_dict['ca_file'], 'Custom --ca_file', 'N/A'))

        thread_pool = ThreadPool()
        for trust_store in final_trust_store_list:
            # Try to connect with each trust store
            thread_pool.add_job(
                (self._get_certificate_chain, (server_info, trust_store)))

        # Start processing the jobs; one thread per trust
        thread_pool.start(len(final_trust_store_list))

        # Store the results as they come
        certificate_chain = []
        path_validation_result_list = []
        path_validation_error_list = []
        ocsp_response = None

        for (job, result) in thread_pool.get_result():
            (_, (_, trust_store)) = job
            certificate_chain, validation_result, ocsp_response = result
            # Store the returned verify string for each trust store
            path_validation_result_list.append(
                PathValidationResult(trust_store, validation_result))

        # Store thread pool errors
        last_exception = None
        for (job, exception) in thread_pool.get_error():
            (_, (_, trust_store)) = job
            path_validation_error_list.append(
                PathValidationError(trust_store, exception))
            last_exception = exception

        thread_pool.join()

        if len(path_validation_error_list) == len(final_trust_store_list):
            # All connections failed unexpectedly; raise an exception instead of returning a result
            raise RuntimeError(
                'Could not connect to the server; last error: {}'.format(
                    last_exception))

        # All done
        return result_class(server_info, command, options_dict,
                            certificate_chain, path_validation_result_list,
                            path_validation_error_list, ocsp_response)

    def _get_certificate_chain(self, server_info, trust_store):
        """Connects to the target server and uses the supplied trust store to validate the server's certificate.
        Returns the server's certificate and OCSP response.
        """
        ssl_connection = server_info.get_preconfigured_ssl_connection(
            ssl_verify_locations=trust_store.path)

        # Enable OCSP stapling
        ssl_connection.set_tlsext_status_ocsp()

        try:  # Perform the SSL handshake
            ssl_connection.connect()

            ocsp_response = ssl_connection.get_tlsext_status_ocsp_resp()
            x509_cert_chain = ssl_connection.get_peer_cert_chain()
            (_, verify_str
             ) = ssl_connection.get_certificate_chain_verify_result()

        except ClientCertificateRequested:  # The server asked for a client cert
            # We can get the server cert anyway
            ocsp_response = ssl_connection.get_tlsext_status_ocsp_resp()
            x509_cert_chain = ssl_connection.get_peer_cert_chain()
            (_, verify_str
             ) = ssl_connection.get_certificate_chain_verify_result()

        finally:
            ssl_connection.close()

        return x509_cert_chain, verify_str, ocsp_response
Beispiel #9
0
class HstsPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="HstsPlugin", description='')
    interface.add_command(
        command="hsts",
        help="Checks support for HTTP Strict Transport Security (HSTS) by collecting any Strict-Transport-Security "
             "field present in the HTTP response sent back by the server(s)."
    )


    def process_task(self, server_info, command, options_dict=None):

        if server_info.tls_wrapped_protocol not in [TlsWrappedProtocolEnum.PLAIN_TLS, TlsWrappedProtocolEnum.HTTPS]:
            raise ValueError('Cannot test for HSTS on a StartTLS connection.')

        hsts_header = self._get_hsts_header(server_info)
        return HstsResult(server_info, command, options_dict, hsts_header)


    MAX_REDIRECT = 5

    def _get_hsts_header(self, server_info):

        hsts_header = None
        nb_redirect = 0
        http_get_format = 'GET {0} HTTP/1.0\r\nHost: {1}\r\n{2}Connection: close\r\n\r\n'.format
        http_path = '/'
        http_append = ''

        while nb_redirect < self.MAX_REDIRECT:
            # Always use a new connection as some servers always close the connection after sending back an HTTP
            # response
            ssl_connection = server_info.get_preconfigured_ssl_connection()

            # Perform the SSL handshake
            ssl_connection.connect()

            ssl_connection.write(http_get_format(http_path, server_info.hostname, http_append))
            http_resp = parse_http_response(ssl_connection)
            ssl_connection.close()
            
            if http_resp.version == 9 :
                # HTTP 0.9 => Probably not an HTTP response
                raise ValueError('Server did not return an HTTP response')
            else:
                hsts_header = http_resp.getheader('strict-transport-security', None)

            # If there was no HSTS header, check if the server returned a redirection
            if hsts_header is None and 300 <= http_resp.status < 400:
                redirect_header = http_resp.getheader('Location', None)
                cookie_header = http_resp.getheader('Set-Cookie', None)
                
                if redirect_header is None:
                    break
                o = urlparse(redirect_header)
                
                # Handle absolute redirection URL but only allow redirections to the same domain and port
                if o.hostname and o.hostname != server_info.hostname:
                    break
                else:
                    http_path = o.path
                    if o.scheme == 'http':
                        # We would have to use urllib for http: URLs
                        break

                # Handle cookies
                if cookie_header:
                    cookie = Cookie.SimpleCookie(cookie_header)
                    if cookie:
                        http_append = 'Cookie:' + cookie.output(attrs=[], header='', sep=';') + '\r\n'

                nb_redirect+=1
            else:
                # If the server did not return a redirection just give up
                break

        return hsts_header
class OpenSslCipherSuitesPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(
        "OpenSslCipherSuitesPlugin",
        "Scans the server(s) for supported OpenSSL cipher suites.")
    interface.add_command(
        command="sslv2",
        help=
        "Lists the SSL 2.0 OpenSSL cipher suites supported by the server(s).",
        aggressive=False)
    interface.add_command(
        command="sslv3",
        help=
        "Lists the SSL 3.0 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_command(
        command="tlsv1",
        help=
        "Lists the TLS 1.0 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_command(
        command="tlsv1_1",
        help=
        "Lists the TLS 1.1 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_command(
        command="tlsv1_2",
        help=
        "Lists the TLS 1.2 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_option(
        option='http_get',
        help="Option - For each cipher suite, sends an HTTP GET request after "
        "completing the SSL handshake and returns the HTTP status code.")
    interface.add_option(
        option='hide_rejected_ciphers',
        help=
        "Option - Hides the (usually long) list of cipher suites that were rejected by the server(s)."
    )

    MAX_THREADS = 15
    SSL_VERSIONS_MAPPING = {
        'sslv2': SSLV2,
        'sslv3': SSLV3,
        'tlsv1': TLSV1,
        'tlsv1_1': TLSV1_1,
        'tlsv1_2': TLSV1_2
    }

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     options_dict=None):
        ssl_version = self.SSL_VERSIONS_MAPPING[plugin_command]

        # Get the list of available cipher suites for the given ssl version
        ssl_client = SslClient(ssl_version=ssl_version)
        ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL')
        cipher_list = ssl_client.get_cipher_list()

        # Scan for every available cipher suite
        thread_pool = ThreadPool()
        for cipher in cipher_list:
            thread_pool.add_job(
                (self._test_cipher_suite, (server_connectivity_info,
                                           ssl_version, cipher)))

        # Start processing the jobs; One thread per cipher
        thread_pool.start(nb_threads=min(len(cipher_list), self.MAX_THREADS))

        accepted_cipher_list = []
        rejected_cipher_list = []
        errored_cipher_list = []

        # Store the results as they come
        for completed_job in thread_pool.get_result():
            (job, cipher_result) = completed_job
            if isinstance(cipher_result, AcceptedCipherSuite):
                accepted_cipher_list.append(cipher_result)
            elif isinstance(cipher_result, RejectedCipherSuite):
                rejected_cipher_list.append(cipher_result)
            elif isinstance(cipher_result, ErroredCipherSuite):
                errored_cipher_list.append(cipher_result)
            else:
                raise ValueError('Unexpected result')

        # Store thread pool errors; only something completely unexpected would trigger an error
        for failed_job in thread_pool.get_error():
            (_, exception) = failed_job
            raise exception

        thread_pool.join()

        # Test for the cipher suite preference
        preferred_cipher = self._get_preferred_cipher_suite(
            server_connectivity_info, ssl_version, accepted_cipher_list)

        # Generate the results
        plugin_result = OpenSSLCipherSuitesResult(server_connectivity_info,
                                                  plugin_command, options_dict,
                                                  preferred_cipher,
                                                  accepted_cipher_list,
                                                  rejected_cipher_list,
                                                  errored_cipher_list)
        return plugin_result

    @staticmethod
    def _test_cipher_suite(server_connectivity_info, ssl_version,
                           openssl_cipher_name):
        """Initiates a SSL handshake with the server using the SSL version and the cipher suite specified.
        """
        ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version)
        ssl_connection.set_cipher_list(openssl_cipher_name)
        if len(ssl_connection.get_cipher_list()) != 1:
            raise ValueError(
                'Passed an OpenSSL string for multiple cipher suites: "{}"'.
                format(openssl_cipher_name))

        try:
            # Perform the SSL handshake
            ssl_connection.connect()
            cipher_result = AcceptedCipherSuite.from_ongoing_ssl_connection(
                ssl_connection, ssl_version)

        except SSLHandshakeRejected as e:
            cipher_result = RejectedCipherSuite(openssl_cipher_name,
                                                ssl_version, str(e))

        except Exception as e:
            cipher_result = ErroredCipherSuite(openssl_cipher_name,
                                               ssl_version, e)

        finally:
            ssl_connection.close()

        return cipher_result

    def _get_preferred_cipher_suite(self, server_connectivity_info,
                                    ssl_version, accepted_cipher_list):
        """Try to detect the server's preferred cipher suite among all cipher suites supported by SSLyze.
        """
        if len(accepted_cipher_list) < 2:
            return None

        first_cipher_string = ', '.join(
            [cipher.openssl_name for cipher in accepted_cipher_list])
        # Swap the first two ciphers in the list to see if the server always picks the client's first cipher
        second_cipher_string = ', '.join([
            accepted_cipher_list[1].openssl_name,
            accepted_cipher_list[0].openssl_name
        ] + [cipher.openssl_name for cipher in accepted_cipher_list[2:]])

        first_cipher = self._get_selected_cipher_suite(
            server_connectivity_info, ssl_version, first_cipher_string)
        second_cipher = self._get_selected_cipher_suite(
            server_connectivity_info, ssl_version, second_cipher_string)

        if first_cipher.name == second_cipher.name:
            # The server has its own preference for picking a cipher suite
            return first_cipher
        else:
            # The server has no preferred cipher suite as it follows the client's preference for picking a cipher suite
            return None

    @staticmethod
    def _get_selected_cipher_suite(server_connectivity_info, ssl_version,
                                   openssl_cipher_string):
        """Given an OpenSSL cipher string (which may specify multiple cipher suites), return the cipher suite that was
            selected by the server during the SSL handshake.
        """
        ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version)
        ssl_connection.set_cipher_list(openssl_cipher_string)

        # Perform the SSL handshake
        ssl_connection.connect()
        selected_cipher = AcceptedCipherSuite.from_ongoing_ssl_connection(
            ssl_connection, ssl_version)
        ssl_connection.close()
        return selected_cipher
class SessionResumptionPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(
        title="SessionResumptionPlugin",
        description="Analyzes the target server's SSL session resumption capabilities."
    )
    interface.add_command(
        command="resum",
        help="Tests the server(s) for session resumption support using session IDs and TLS session tickets (RFC 5077)."
    )
    interface.add_command(
        command="resum_rate",
        help="Performs 100 session resumptions with the server(s), in order to estimate the session resumption rate.",
        aggressive=True
    )


    MAX_THREADS_NB = 20

    def process_task(self, server_info, command, options_dict=None):

        if command == 'resum':
            result = self._command_resum(server_info)
        elif command == 'resum_rate':
            successful_resumptions_nb, errored_resumptions_list = self._test_session_resumption_rate(server_info, 100)
            result = ResumptionRateResult(server_info, command, options_dict, 100, successful_resumptions_nb,
                                          errored_resumptions_list)
        else:
            raise ValueError("PluginSessionResumption: Unknown command.")

        return result


    def _command_resum(self, server_info):
        """Tests the server for session resumption support using session IDs and TLS session tickets (RFC 5077).
        """
        # Test Session ID support
        successful_resumptions_nb, errored_resumptions_list = self._test_session_resumption_rate(server_info, 5)

        # Test TLS tickets support
        ticket_exception = None
        ticket_reason = None
        ticket_supported = False
        try:
            (ticket_supported, ticket_reason) = self._resume_with_session_ticket(server_info)
        except Exception as e:
            ticket_exception = e

        return ResumptionResult(server_info, 'resum', {}, 5,  successful_resumptions_nb, errored_resumptions_list,
                                ticket_supported, ticket_reason, ticket_exception)


    def _test_session_resumption_rate(self, server_info, resumption_attempts_nb):
        """Attempts several session ID resumption with the server."""
        thread_pool = ThreadPool()

        for _ in xrange(resumption_attempts_nb):
            thread_pool.add_job((self._resume_with_session_id, (server_info, )))
        thread_pool.start(nb_threads=min(resumption_attempts_nb, self.MAX_THREADS_NB))

        # Count successful/failed resumptions
        successful_resumptions_nb = 0
        for completed_job in thread_pool.get_result():
            (job, was_resumption_successful) = completed_job
            if was_resumption_successful:
                successful_resumptions_nb += 1

        # Count errors and store error messages
        errored_resumptions_list = []
        for failed_job in thread_pool.get_error():
            (job, exception) = failed_job
            error_msg = '{} - {}'.format(str(exception.__class__.__name__), str(exception))
            errored_resumptions_list.append(error_msg)

        thread_pool.join()
        return successful_resumptions_nb, errored_resumptions_list


    def _resume_with_session_id(self, server_info):
        """Performs one session resumption using Session IDs.
        """
        session1 = self._resume_ssl_session(server_info)
        try:
            # Recover the session ID
            session1_id = self._extract_session_id(session1)
        except IndexError:
            # Session ID not assigned
            return False

        if session1_id == '':
            # Session ID empty
            return False

        # Try to resume that SSL session
        session2 = self._resume_ssl_session(server_info, session1)
        try: # Recover the session ID
            session2_id = self._extract_session_id(session2)
        except IndexError:
            # Session ID not assigned
            return False

        # Finally, compare the two Session IDs
        if session1_id != session2_id:
            # Session ID assigned but not accepted
            return False

        return True


    def _resume_with_session_ticket(self, server_info):
        """Performs one session resumption using TLS Session Tickets.
        """
        # Connect to the server and keep the SSL session
        session1 = self._resume_ssl_session(server_info, should_enable_tls_ticket=True)
        try:
            # Recover the TLS ticket
            session1_tls_ticket = self._extract_tls_session_ticket(session1)
        except IndexError:
            return False, 'TLS ticket not assigned'

        # Try to resume that session using the TLS ticket
        session2 = self._resume_ssl_session(server_info, session1, should_enable_tls_ticket=True)
        try:
            # Recover the TLS ticket
            session2_tls_ticket = self._extract_tls_session_ticket(session2)
        except IndexError:
            return False, 'TLS ticket not assigned'

        # Finally, compare the two TLS Tickets
        if session1_tls_ticket != session2_tls_ticket:
            return False, 'TLS ticket assigned but not accepted'

        return True, ''


    @staticmethod
    def _extract_session_id(ssl_session):
        """Extracts the SSL session ID from a SSL session object or raises IndexError if the session ID was not set.
        """
        session_string = ( (ssl_session.as_text()).split("Session-ID:") )[1]
        session_id = ( session_string.split("Session-ID-ctx:") )[0].strip()
        return session_id


    @staticmethod
    def _extract_tls_session_ticket(ssl_session):
        """Extracts the TLS session ticket from a SSL session object or raises IndexError if the ticket was not set.
        """
        session_string = ((ssl_session.as_text()).split("TLS session ticket:"))[1]
        session_tls_ticket = (session_string.split("Compression:"))[0]
        return session_tls_ticket


    def _resume_ssl_session(self, server_info, ssl_session=None, should_enable_tls_ticket=False):
        """Connects to the server and returns the session object that was assigned for that connection.
        If ssl_session is given, tries to resume that session.
        """
        ssl_connection = server_info.get_preconfigured_ssl_connection()
        if not should_enable_tls_ticket:
        # Need to disable TLS tickets to test session IDs, according to rfc5077:
        # If a ticket is presented by the client, the server MUST NOT attempt
        # to use the Session ID in the ClientHello for stateful session resumption
            ssl_connection.set_options(SSL_OP_NO_TICKET) # Turning off TLS tickets.

        if ssl_session:
            ssl_connection.set_session(ssl_session)

        try: # Perform the SSL handshake
            ssl_connection.connect()
            new_session = ssl_connection.get_session() # Get session data
        finally:
            ssl_connection.close()

        return new_session
class OpenSslCcsInjectionPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface("OpenSslCcsInjectionPlugin", "")
    interface.add_command(
        command="openssl_ccs",
        help=
        "Tests the server(s) for the OpenSSL CCS injection vulnerability (experimental)."
    )

    def srecv(self):
        r = self._sock.recv(4096)
        self._inbuffer += r
        return r != ''

    def process_task(self, server_info, plugin_command, options_dict=None):

        ssl_connection = server_info.get_preconfigured_ssl_connection()
        self._ssl_version = server_info.highest_ssl_version_supported
        is_vulnerable = False
        self._inbuffer = ""
        ssl_connection.do_pre_handshake()

        # H4ck to directly send the CCS payload
        self._sock = ssl_connection._sock

        # Send hello and wait for server hello & cert
        serverhello, servercert = False, False
        self._sock.send(self.make_hello())
        while not serverhello:  #  or not servercert
            try:
                if not self.srecv(): break
            except:
                break
            rs = self.parse_records()
            for record in rs:
                if record['type'] == 22:
                    for p in record['proto']:
                        if p['type'] == 2:
                            serverhello = True
                        if p['type'] == 11:
                            servercert = True

        # Send the CCS
        if serverhello:  # and servercert:
            is_vulnerable, stop = True, False
            self._sock.send(self.make_ccs())
            while not stop:
                try:
                    if not self.srecv(): break
                except socket.timeout:
                    break
                except:
                    is_vulnerable = False
                    stop = True

                rs = self.parse_records()
                for record in rs:
                    if record['type'] == 21:
                        for p in record['proto']:
                            if p['level'] == 2 or (p['level'] == 1
                                                   and p['desc'] == 0):
                                is_vulnerable = False
                                stop = True

            # If we receive no alert message check whether it is really is_vulnerable
            if is_vulnerable:
                self._sock.send('\x15' + self.ssl_tokens[self._ssl_version] +
                                '\x00\x02\x01\x00')

                try:
                    if not self.srecv():
                        is_vulnerable = False
                except:
                    is_vulnerable = False

        self._sock.close()
        return OpenSslCcsInjectionResult(server_info, plugin_command,
                                         options_dict, is_vulnerable)

    ssl_tokens = {
        SSLV3: "\x03\x00",
        TLSV1: "\x03\x01",
        TLSV1_1: "\x03\x02",
        TLSV1_2: "\x03\x03",
    }

    ssl3_cipher = [
        '\x00\x00', '\x00\x01', '\x00\x02', '\x00\x03', '\x00\x04', '\x00\x05',
        '\x00\x06', '\x00\x07', '\x00\x08', '\x00\x09', '\x00\x0a', '\x00\x0b',
        '\x00\x0c', '\x00\x0d', '\x00\x0e', '\x00\x0f', '\x00\x10', '\x00\x11',
        '\x00\x12', '\x00\x13', '\x00\x14', '\x00\x15', '\x00\x16', '\x00\x17',
        '\x00\x18', '\x00\x19', '\x00\x1a', '\x00\x1b', '\x00\x1c', '\x00\x1d',
        '\x00\x1e', '\x00\x1F', '\x00\x20', '\x00\x21', '\x00\x22', '\x00\x23',
        '\x00\x24', '\x00\x25', '\x00\x26', '\x00\x27', '\x00\x28', '\x00\x29',
        '\x00\x2A', '\x00\x2B', '\x00\x2C', '\x00\x2D', '\x00\x2E', '\x00\x2F',
        '\x00\x30', '\x00\x31', '\x00\x32', '\x00\x33', '\x00\x34', '\x00\x35',
        '\x00\x36', '\x00\x37', '\x00\x38', '\x00\x39', '\x00\x3A', '\x00\x3B',
        '\x00\x3C', '\x00\x3D', '\x00\x3E', '\x00\x3F', '\x00\x40', '\x00\x41',
        '\x00\x42', '\x00\x43', '\x00\x44', '\x00\x45', '\x00\x46', '\x00\x60',
        '\x00\x61', '\x00\x62', '\x00\x63', '\x00\x64', '\x00\x65', '\x00\x66',
        '\x00\x67', '\x00\x68', '\x00\x69', '\x00\x6A', '\x00\x6B', '\x00\x6C',
        '\x00\x6D', '\x00\x80', '\x00\x81', '\x00\x82', '\x00\x83', '\x00\x84',
        '\x00\x85', '\x00\x86', '\x00\x87', '\x00\x88', '\x00\x89', '\x00\x8A',
        '\x00\x8B', '\x00\x8C', '\x00\x8D', '\x00\x8E', '\x00\x8F', '\x00\x90',
        '\x00\x91', '\x00\x92', '\x00\x93', '\x00\x94', '\x00\x95', '\x00\x96',
        '\x00\x97', '\x00\x98', '\x00\x99', '\x00\x9A', '\x00\x9B', '\x00\x9C',
        '\x00\x9D', '\x00\x9E', '\x00\x9F', '\x00\xA0', '\x00\xA1', '\x00\xA2',
        '\x00\xA3', '\x00\xA4', '\x00\xA5', '\x00\xA6', '\x00\xA7', '\x00\xA8',
        '\x00\xA9', '\x00\xAA', '\x00\xAB', '\x00\xAC', '\x00\xAD', '\x00\xAE',
        '\x00\xAF', '\x00\xB0', '\x00\xB1', '\x00\xB2', '\x00\xB3', '\x00\xB4',
        '\x00\xB5', '\x00\xB6', '\x00\xB7', '\x00\xB8', '\x00\xB9', '\x00\xBA',
        '\x00\xBB', '\x00\xBC', '\x00\xBD', '\x00\xBE', '\x00\xBF', '\x00\xC0',
        '\x00\xC1', '\x00\xC2', '\x00\xC3', '\x00\xC4', '\x00\xC5', '\x00\x00',
        '\xc0\x01', '\xc0\x02', '\xc0\x03', '\xc0\x04', '\xc0\x05', '\xc0\x06',
        '\xc0\x07', '\xc0\x08', '\xc0\x09', '\xc0\x0a', '\xc0\x0b', '\xc0\x0c',
        '\xc0\x0d', '\xc0\x0e', '\xc0\x0f', '\xc0\x10', '\xc0\x11', '\xc0\x12',
        '\xc0\x13', '\xc0\x14', '\xc0\x15', '\xc0\x16', '\xc0\x17', '\xc0\x18',
        '\xc0\x19', '\xC0\x1A', '\xC0\x1B', '\xC0\x1C', '\xC0\x1D', '\xC0\x1E',
        '\xC0\x1F', '\xC0\x20', '\xC0\x21', '\xC0\x22', '\xC0\x23', '\xC0\x24',
        '\xC0\x25', '\xC0\x26', '\xC0\x27', '\xC0\x28', '\xC0\x29', '\xC0\x2A',
        '\xC0\x2B', '\xC0\x2C', '\xC0\x2D', '\xC0\x2E', '\xC0\x2F', '\xC0\x30',
        '\xC0\x31', '\xC0\x32', '\xC0\x33', '\xC0\x34', '\xC0\x35', '\xC0\x36',
        '\xC0\x37', '\xC0\x38', '\xC0\x39', '\xC0\x3A', '\xC0\x3B', '\xfe\xfe',
        '\xfe\xff', '\xff\xe0', '\xff\xe1'
    ]

    # Create a TLS record out of a protocol packet
    def make_record(self, t, body):
        l = struct.pack("!H", len(body))
        return chr(t) + self.ssl_tokens[self._ssl_version] + l + body

    def make_hello(self):
        suites = "".join(self.ssl3_cipher)
        rand = "".join([chr(int(256 * random.random())) for x in range(32)])
        l = struct.pack("!L", 39 + len(suites))[1:]  # 3 bytes
        sl = struct.pack("!H", len(suites))

        # Client hello, lenght and version
        # Random data + session ID + cipher suites + compression suites
        data = "\x01" + l + self.ssl_tokens[self._ssl_version] + rand + "\x00"
        data += sl + suites + "\x01\x00"

        return self.make_record(22, data)

    def make_ccs(self):
        ccsbody = "\x01"  # Empty CCS
        return self.make_record(20, ccsbody)

    def parse_handshake_pkt(self, buf):
        r = []
        while len(buf) >= 4:
            mt = ord(buf[0])
            mlen = struct.unpack("!L", buf[0:4])[0] & 0xFFFFFF

            if mlen + 4 > len(buf):
                break

            r.append({"type": mt, "data": buf[4:4 + mlen]})
            buf = buf[4 + mlen:]
        return r

    def parse_alert_pkt(self, buf):
        return [{"level": ord(buf[0]), "desc": ord(buf[1])}]

    def parse_records(self):
        r = []
        # 5 byte header
        while len(self._inbuffer) >= 5:
            mtype = ord(self._inbuffer[0])
            mtlsv = self._inbuffer[1:3]
            mlen = struct.unpack("!H", self._inbuffer[3:5])[0]

            if len(self._inbuffer) < 5 + mlen:
                break

            if mtype == 22:  # Handshake
                protp = self.parse_handshake_pkt(self._inbuffer[5:5 + mlen])
            elif mtype == 21:  # Alert
                protp = self.parse_alert_pkt(self._inbuffer[5:5 + mlen])
            else:
                protp = []

            r.append({"type": mtype, "sslv": mtlsv, "proto": protp})

            self._inbuffer = self._inbuffer[5 + mlen:]

        return r
class OpenSslCipherSuitesPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(
        "OpenSslCipherSuitesPlugin",
        "Scans the server(s) for supported OpenSSL cipher suites.")
    interface.add_command(
        command="sslv2",
        help=
        "Lists the SSL 2.0 OpenSSL cipher suites supported by the server(s).",
        aggressive=False)
    interface.add_command(
        command="sslv3",
        help=
        "Lists the SSL 3.0 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_command(
        command="tlsv1",
        help=
        "Lists the TLS 1.0 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_command(
        command="tlsv1_1",
        help=
        "Lists the TLS 1.1 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_command(
        command="tlsv1_2",
        help=
        "Lists the TLS 1.2 OpenSSL cipher suites supported by the server(s).",
        aggressive=True)
    interface.add_option(
        option='http_get',
        help="Option - For each cipher suite, sends an HTTP GET request after "
        "completing the SSL handshake and returns the HTTP status code.")
    interface.add_option(
        option='hide_rejected_ciphers',
        help=
        "Option - Hides the (usually long) list of cipher suites that were rejected by the server(s)."
    )

    MAX_THREADS = 15
    SSL_VERSIONS_MAPPING = {
        'sslv2': SSLV2,
        'sslv3': SSLV3,
        'tlsv1': TLSV1,
        'tlsv1_1': TLSV1_1,
        'tlsv1_2': TLSV1_2
    }

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     options_dict=None):
        ssl_version = self.SSL_VERSIONS_MAPPING[plugin_command]

        # Get the list of available cipher suites for the given ssl version
        ssl_client = SslClient(ssl_version=ssl_version)
        ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL')
        cipher_list = ssl_client.get_cipher_list()

        # Scan for every available cipher suite
        thread_pool = ThreadPool()
        for cipher in cipher_list:
            thread_pool.add_job(
                (self._test_ciphersuite, (server_connectivity_info,
                                          ssl_version, cipher)))

        # Scan for the preferred cipher suite
        preferred_cipher = self._get_preferred_ciphersuite(
            server_connectivity_info, ssl_version)

        # Start processing the jobs; One thread per cipher
        thread_pool.start(nb_threads=min(len(cipher_list), self.MAX_THREADS))

        accepted_cipher_list = []
        rejected_cipher_list = []
        errored_cipher_list = []

        # Store the results as they come
        for completed_job in thread_pool.get_result():
            (job, cipher_result) = completed_job
            if isinstance(cipher_result, AcceptedCipherSuite):
                accepted_cipher_list.append(cipher_result)
            elif isinstance(cipher_result, RejectedCipherSuite):
                rejected_cipher_list.append(cipher_result)
            elif isinstance(cipher_result, ErroredCipherSuite):
                errored_cipher_list.append(cipher_result)
            else:
                raise ValueError('Unexpected result')

        # Store thread pool errors; only something completely unexpected would trigger an error
        for failed_job in thread_pool.get_error():
            (_, exception) = failed_job
            raise exception

        thread_pool.join()

        plugin_result = OpenSSLCipherSuitesResult(server_connectivity_info,
                                                  plugin_command, options_dict,
                                                  preferred_cipher,
                                                  accepted_cipher_list,
                                                  rejected_cipher_list,
                                                  errored_cipher_list)
        return plugin_result

    def _test_ciphersuite(self, server_connectivity_info, ssl_version,
                          ssl_cipher):
        """Initiates a SSL handshake with the server, using the SSL version and cipher suite specified.
        """
        rfc_cipher_name = OPENSSL_TO_RFC_NAMES_MAPPING[ssl_version].get(
            ssl_cipher, ssl_cipher)
        ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version)
        ssl_connection.set_cipher_list(ssl_cipher)

        try:
            # Perform the SSL handshake
            ssl_connection.connect()

        except SSLHandshakeRejected as e:
            cipher_result = RejectedCipherSuite(rfc_cipher_name, str(e))

        except Exception as e:
            cipher_result = ErroredCipherSuite(rfc_cipher_name, e)

        else:
            keysize = ssl_connection.get_current_cipher_bits()

            if 'ECDH' in ssl_cipher:
                dh_infos = ssl_connection.get_ecdh_param()
            elif 'DH' in ssl_cipher:
                dh_infos = ssl_connection.get_dh_param()
            else:
                dh_infos = None

            status_msg = ssl_connection.post_handshake_check()
            cipher_result = AcceptedCipherSuite(rfc_cipher_name, keysize,
                                                dh_infos, status_msg)

        finally:
            ssl_connection.close()

        return cipher_result

    def _get_preferred_ciphersuite(self, server_connectivity_info,
                                   ssl_version):
        """Initiates a SSL handshake with the server, using the SSL version and cipher suite specified.
        """
        preferred_cipher = None
        # First try the default cipher list, and then all ciphers
        for cipher_list in [
                SSLConnection.DEFAULT_SSL_CIPHER_LIST, 'ALL:COMPLEMENTOFALL'
        ]:
            ssl_connection = server_connectivity_info.get_preconfigured_ssl_connection(
                override_ssl_version=ssl_version)
            ssl_connection.set_cipher_list(cipher_list)
            try:
                # Perform the SSL handshake
                ssl_connection.connect()
                cipher_name = ssl_connection.get_current_cipher_name()
                rfc_cipher_name = OPENSSL_TO_RFC_NAMES_MAPPING[
                    ssl_version].get(cipher_name, cipher_name)
                keysize = ssl_connection.get_current_cipher_bits()

                if 'ECDH' in cipher_name:
                    dh_infos = ssl_connection.get_ecdh_param()
                elif 'DH' in cipher_name:
                    dh_infos = ssl_connection.get_dh_param()
                else:
                    dh_infos = None

                status_msg = ssl_connection.post_handshake_check()
                preferred_cipher = AcceptedCipherSuite(rfc_cipher_name,
                                                       keysize, dh_infos,
                                                       status_msg)
                break

            except:
                pass

            finally:
                ssl_connection.close()

        return preferred_cipher
Beispiel #14
0
class LuckyThirteenVulnerabilityTesterPlugin(plugin_base.PluginBase):
    """@class LuckyThirteenVulnerabilityTesterPlugin
    
    This class inherited from abstract class PluginBase. Instance of this class tests server for vulnerability CVE-2013-0169 and makes decision if the server is vulnerable.
    """
    interface = plugin_base.PluginInterface(
        "LuckyThirteenVulnerabilityTesterPlugin",
        "Scans the server(s) and checks if requirements for Lucky13 attack are satisfied."
    )
    interface.add_command(
        command="lucky13_tls",
        help=
        "Tests server(s) for CVE-2013-0169 vulnerability. It uses only protocol TLSv1.1 and TLSv1.2."
    )
    interface.add_command(
        command="lucky13_dtls",
        help=
        "Tests server(s) for CVE-2013-0169 vulnerability. It uses only protocol DTLSv1 and DTLSv1.2."
    )
    interface.add_option(option="port",
                         help="Specify which port you want connect to.",
                         dest="port")

    MAX_THREADS = 20
    DTLSv1_METHOD = 7
    DTLSv1_2_METHOD = 8

    DTLS_MODULE = {
        DTLSv1_METHOD: "DTLSv1_client_method",
        DTLSv1_2_METHOD: "DTLSv1_2_client_method"
    }

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     option_dict=None):
        dtls_title = None
        if option_dict and 'verbose' in option_dict.keys():
            verbose_mode = option_dict['verbose']
        else:
            verbose_mode = False

        if plugin_command == 'lucky13_tls':
            (thread_pool, threads) = self.create_thread_pool_for_protocol_tls(
                server_connectivity_info)
            adv_info = {'protocol': 'TLS'}
        elif plugin_command == 'lucky13_dtls':
            if option_dict and 'port' in option_dict.keys():
                (thread_pool,
                 threads) = self.create_thread_pool_for_protocol_dtls(
                     server_connectivity_info, int(option_dict['port']))
                adv_info = {'protocol': 'DTLS', 'port': option_dict['port']}
            else:
                raise ValueError(
                    "LuckyThirteenVulnerabilityTesterPlugin: Missing option --port for command --lucky13-dtls"
                )
        else:
            raise ValueError(
                "LuckyThirteenVulnerabilityTesterPlugin: Unknown command")
        thread_pool.start(nb_threads=threads)

        accept_ciphers = []
        reject_ciphers = []

        if verbose_mode:
            print '  VERBOSE MODE PRINT'
            print '  ------------------'
        for completed_job in thread_pool.get_result():
            (job, cipher_result) = completed_job
            if isinstance(cipher_result, AcceptCipher):
                accept_ciphers.append(cipher_result)
            elif isinstance(cipher_result, RejectCipher):
                reject_ciphers.append(cipher_result)
            else:
                raise ValueError("Unexpected result")
            if verbose_mode:
                cipher_result.print_cipher()

        if verbose_mode:
            print '  ----------------------'
            print '  END VERBOSE MODE PRINT'
            print '  ----------------------'
        for error_job in thread_pool.get_error():
            (_, exception) = error_job
            raise exception

        thread_pool.join()

        support_vulnerable_ciphers = self.get_vulnerable_cipher_set(
            accept_ciphers)
        is_vulnerable = True \
            if len(support_vulnerable_ciphers) > 0 \
            else False

        return LuckyThirteenVulnerabilityTesterResult(
            server_connectivity_info, plugin_command, option_dict,
            is_vulnerable, support_vulnerable_ciphers, adv_info)

    def create_thread_pool_for_protocol_tls(self, server_connectivity_info):
        """ Creates and returns instance of ThreadPool class. Adds into ThreadPool new jobs for each cipher suite, which is available for protocol TLS 1.1 and TLS 1.2
    
            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server.
        """
        thread_pool = ThreadPool()
        protocols = ['TLSv1.1', 'TLSv1.2']
        cipher_list = []
        for protocol in protocols:
            if self.test_protocol_support(server_connectivity_info, protocol):
                cipher_list = self.get_cipher_list(protocol)
                for cipher in cipher_list:
                    thread_pool.add_job(
                        (self._test_ciphersuite, (server_connectivity_info,
                                                  protocol, cipher)))

        return (thread_pool, min(self.MAX_THREADS, len(cipher)))

    def create_thread_pool_for_protocol_dtls(self, server_connectivity_info,
                                             port):
        """ Creates and returns instance of ThreadPool class. Adds into ThreadPool new jobs for each cipher suite, which is available for protocol DTLS 1.0 and/or DTLS 1.2

            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            port (int): contains port number for connecting comunication.
        """
        dtls_protocols = self.get_support_dtls_protocols_by_client()
        thread_pool = ThreadPool()
        cipher_list = []
        for protocol in dtls_protocols:
            if self.test_dtls_protocol_support(server_connectivity_info,
                                               protocol, port):
                cipher_list = self.get_dtls_cipher_list(protocol)
                for cipher in cipher_list:
                    thread_pool.add_job(
                        (self._test_dtls_ciphersuite,
                         (server_connectivity_info, protocol, cipher, port)))

        return (thread_pool, 1)

    def _test_ciphersuite(self, server_connectivity_info, ssl_version, cipher):
        """This function is used by threads to it investigates with support the cipher suite on server, when TLS protocol(s) is/are tested. Returns instance of class AcceptCipher or RejectCipher.

            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            ssl_version (str): contains SSL/TLS protocol version, which is used to connect
            cipher (str): contains OpenSSL shortcut for identification cipher suite.
        """
        ssl_conn = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=PROTOCOL_VERSION[ssl_version])
        ssl_conn.set_cipher_list(cipher)
        try:
            ssl_conn.connect()
        except SSLHandshakeRejected as e:
            cipher_result = RejectCipher(
                OPENSSL_TO_RFC_NAMES_MAPPING[
                    PROTOCOL_VERSION[ssl_version]].get(cipher, cipher), str(e))
        else:
            cipher_result = AcceptCipher(OPENSSL_TO_RFC_NAMES_MAPPING[
                PROTOCOL_VERSION[ssl_version]].get(cipher, cipher))
        finally:
            ssl_conn.close()
        return cipher_result

    def _test_dtls_ciphersuite(self, server_connectivity_info, dtls_version,
                               cipher, port):
        """This function is used by threads to it investigates with support the cipher suite on server, when DTLS protocol(s) is/are tested. Returns instance of class AcceptCipher or RejectCipher.

            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            dtls_version (str): contains SSL/TLS protocol version, which is used to connect
            cipher (str): contains OpenSSL shortcut for identification cipher suite
            port (int): contains port number for connecting comunication.
        """
        cnx = SSL.Context(dtls_version)
        cnx.set_cipher_list(cipher)
        conn = SSL.Connection(cnx,
                              socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
        try:
            conn.connect((server_connectivity_info.ip_address, port))
            conn.do_handshake()
        except SSL.Error as e:
            error_msg = ((e[0])[0])[2]
            cipher_result = RejectCipher(
                TLS_OPENSSL_TO_RFC_NAMES_MAPPING[cipher], error_msg)
        else:
            cipher_result = AcceptCipher(
                TLS_OPENSSL_TO_RFC_NAMES_MAPPING[cipher])
        finally:
            conn.shutdown()
            conn.close()
        return cipher_result

    def test_dtls_protocol_support(self, server_connectivity_info,
                                   dtls_version, port):
        """Tests if DTLS protocols are supported by server. Returns true if server supports protocol otherwise returns false.
    
            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            dtls_protocol (str): contains version of DTLS protocol, which is supposed to be tested
            port (int): contains port number for connecting comunication.
        """
        cnx = SSL.Context(dtls_version)
        cnx.set_cipher_list('ALL:COMPLEMENTOFALL')
        conn = SSL.Connection(cnx,
                              socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
        try:
            conn.connect((server_connectivity_info.ip_address, port))
            conn.do_handshake()
        except SSL.SysCallError as ex:
            if ex[0] == 111:
                raise ValueError(
                    'LuckyThirteenVulnerabilityTesterPlugin: It is entered wrong port for DTLS connection.'
                )
            else:
                support = False
        else:
            support = True
        finally:
            conn.shutdown()
            conn.close()
        return support

    def get_support_dtls_protocols_by_client(self):
        """Returns array which contains all DTLS protocols which are supported by client.
        """
        dtls_ary = []
        for dtls_version in self.DTLS_MODULE:
            try:
                SSL.Context._methods[dtls_version] = getattr(
                    _lib, self.DTLS_MODULE[dtls_version])
            except Exception as e:
                pass
            else:
                dtls_ary.append(dtls_version)
        return dtls_ary

    def test_protocol_support(self, server_connectivity_info, ssl_protocol):
        """Tests if SSL/TLS protocols are supported by server. Returns true if server supports protocol otherwise returns false.
    
            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            ssl_protocol (str): contains version of SSL/TLS protocol, which is supposed to be tested.            
        """
        if server_connectivity_info.highest_ssl_version_supported < PROTOCOL_VERSION[
                ssl_protocol]:
            return False
        ssl_conn = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=PROTOCOL_VERSION[ssl_protocol])
        protocol_supported = True
        try:
            ssl_conn.connect()
        except SSLHandshakeRejected:
            protocol_supported = False
        finally:
            ssl_conn.close()
        return protocol_supported

    def get_dtls_cipher_list(self, dtls_version):
        """Returns list of cipher suites available for protocol version, saves in parameter dtls_protocol.
    
            Args:
            dtls_protocol (str):.
        """
        cnx = SSL.Context(dtls_version)
        cnx.set_cipher_list('ALL:COMPLEMENTOFALL')
        conn = SSL.Connection(cnx,
                              socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
        return conn.get_cipher_list()

    def get_cipher_list(self, ssl_protocol):
        """Returns list of cipher suites available for protocol version, saves in parameter ssl_protocol.
    
            Args:
            ssl_protocol (str):.
        """
        ssl_client = SslClient(ssl_version=PROTOCOL_VERSION[ssl_protocol])
        ssl_client.set_cipher_list('ALL:COMPLEMENTOFALL')
        return ssl_client.get_cipher_list()

    def get_vulnerable_cipher_set(self, accept_ciphers):
        """Returns set of cipher suites, which using CBC mode and are supported by server.
    
            Args:
            accept_ciphers(array): contains cipher suites, which are supported by server.
        """
        result_set = Set()
        for cipher in accept_ciphers:
            if cipher.use_CBC_mode():
                result_set = result_set.union([cipher._cipher_rfc_name])
        return result_set
class BEASTVulnerabilityTesterPlugin(plugin_base.PluginBase):
    """class BEASTVulnerabilityTesterPlugin
    
    This class inherited from abstract class PluginBase. Instance of this class tests server for vulnerability CVE-2011-3389 and makes decision if the server is vulnerable.
    """

    interface = plugin_base.PluginInterface(
        "BEASTVulnerabilityTesterPlugin",
        "Scans the server(s) and checks if requirements for BEAST attack are satisfied."
    )
    interface.add_command(command="beast",
                          help="Tests server for BEAST vulnerability.")

    MAX_THREADS = 15

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     options_dict=None):
        if options_dict and 'verbose' in options_dict.keys():
            verbose_mode = options_dict['verbose']
        else:
            verbose_mode = False
        test_protocols = {'SSLv3', 'TLSv1'}
        thread_pool = ThreadPool()
        ciphers_list = []
        support_protocol_list = []
        for proto in test_protocols:
            if self.test_protocol_support(PROTOCOL_VERSION[proto],
                                          server_connectivity_info):
                ssl_client = SslClient(ssl_version=PROTOCOL_VERSION[proto])
                ssl_client.set_cipher_list(proto)
                ciphers_list = ssl_client.get_cipher_list()
                for cipher in ciphers_list:
                    thread_pool.add_job((self._test_ciphersuite,
                                         (server_connectivity_info,
                                          PROTOCOL_VERSION[proto], cipher)))
                support_protocol_list.append(proto)
        thread_pool.start(nb_threads=min(len(ciphers_list), self.MAX_THREADS))

        accept_ciphers = []
        reject_ciphers = []
        if verbose_mode:
            print '  VERBOSE MODE PRINT'
            print '  ------------------'

        for completed_job in thread_pool.get_result():
            (job, cipher_result) = completed_job
            if isinstance(cipher_result, AcceptCipher):
                accept_ciphers.append(cipher_result)
            elif isinstance(cipher_result, RejectCipher):
                reject_ciphers.append(cipher_result)
            else:
                raise ValueError("Unexpected result")
            if verbose_mode:
                cipher_result.print_cipher()

        if verbose_mode:
            print '  ----------------------'
            print '  END VERBOSE MODE PRINT'
            print '  ----------------------'

        for error_job in thread_pool.get_error():
            (_, exception) = error_job
            raise exception

        thread_pool.join()

        support_vulnerable_ciphers_set = self.get_vulnerable_ciphers(
            accept_ciphers)
        is_vulnerable = True \
            if len(support_vulnerable_ciphers_set) > 0 \
            else False

        return BEASTVulnerabilityTesterResult(server_connectivity_info,
                                              plugin_command, options_dict,
                                              support_vulnerable_ciphers_set,
                                              is_vulnerable,
                                              support_protocol_list)

    def _test_ciphersuite(self, server_connectivity_info, ssl_version, cipher):
        """This function is used by threads to it investigates with support the cipher suite on server. Returns instance of class AcceptCipher or RejectCipher.

            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            ssl_version (str): contains version of SSL/TLS protocol, uses to connect
            cipher (str): contains OpenSSL shortcut for identification cipher suite
        """
        ssl_conn = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version)
        ssl_conn.set_cipher_list(cipher)
        try:
            ssl_conn.connect()
        except SSLHandshakeRejected as e:
            cipher_result = RejectCipher(
                OPENSSL_TO_RFC_NAMES_MAPPING[ssl_version].get(cipher, cipher),
                str(e))
        else:
            cipher_result = AcceptCipher(
                OPENSSL_TO_RFC_NAMES_MAPPING[ssl_version].get(cipher, cipher))
        finally:
            ssl_conn.close()
        return cipher_result

    def test_protocol_support(self, ssl_version, server_info):
        """Tests if SSL/TLS protocol version in parameter ssl_version is supported by server. Returns true if server supports protocol version otherwise returns false.

            Args:
            ssl_version (str):
            server_info (ServerConnectivityInfo): contains information for connection on server            
        """
        ssl_conn = server_info.get_preconfigured_ssl_connection(
            override_ssl_version=ssl_version)
        try:
            ssl_conn.connect()
        except SSLHandshakeRejected:
            support = False
        else:
            support = True
        finally:
            ssl_conn.close()
        return support

    def get_vulnerable_ciphers(self, accept_ciphers):
        """Returns set of cipher suites, which using CBC mode and are supported by server.

            Args:
            accept_ciphers(array): contains cipher suites, which are supported by server
        """
        result_set = Set()
        for cipher in accept_ciphers:
            if cipher.use_CBC_mode():
                result_set = result_set.union([cipher._cipher_rfc_name])
        return result_set
class POODLEVulnerabilityTesterPlugin(plugin_base.PluginBase):
    """class POODLEVulnerabilityTesterPlugin
    
    This class inherited from abstract class PluginBase. Instance of this class tests server for vulnerability CVE-2014-3566 and makes decision if the server is vulnerable.
    """
    interface = plugin_base.PluginInterface(
        "POODLEVulnerabilityTesterPlugin",
        "Scans the server(s) and checks if requirements for POODLE attack are satisfied."
    )
    interface.add_command(
        command="poodle",
        help="Tests server(s) for CVE-2014-3566 vulnerability.")

    MAX_THREADS = 15

    def process_task(self,
                     server_connectivity_info,
                     plugin_command,
                     option_dict=None):
        if option_dict and 'verbose' in option_dict.keys():
            verbose_mode = option_dict['verbose']
        else:
            verbose_mode = False
        ssl3_support = self.test_SSLv3_support(server_connectivity_info)
        support_vulnerable_ciphers = None
        if ssl3_support:
            cipher_list = self.get_ssl3_cipher_list()
            thread_pool = ThreadPool()
            for cipher in cipher_list:
                thread_pool.add_job((self._test_ciphersuite,
                                     (server_connectivity_info, cipher)))
            thread_pool.start(
                nb_threads=min(len(cipher_list), self.MAX_THREADS))

            accept_ciphers = []
            reject_ciphers = []
            if verbose_mode:
                print '  VERBOSE MODE PRINT'
                print '  ------------------'
            for completed_job in thread_pool.get_result():
                (job, cipher_result) = completed_job
                if isinstance(cipher_result, AcceptCipher):
                    accept_ciphers.append(cipher_result)
                elif isinstance(cipher_result, RejectCipher):
                    reject_ciphers.append(cipher_result)
                else:
                    raise ValueError("Unexpected result")
                if verbose_mode:
                    cipher_result.print_cipher()

            if verbose_mode:
                print '  ----------------------'
                print '  END VERBOSE MODE PRINT'
                print '  ----------------------'

            for error_job in thread_pool.get_error():
                (_, exception) = error_job
                raise exception

            thread_pool.join()
            support_vulnerable_ciphers = self.get_vulnerable_ciphers(
                accept_ciphers)

        is_vulnerable = ssl3_support and (
            support_vulnerable_ciphers is not None
            or len(support_vulnerable_ciphers) > 0)

        return POODLEVulnerabilityTesterResult(server_connectivity_info,
                                               plugin_command, option_dict,
                                               ssl3_support, is_vulnerable,
                                               support_vulnerable_ciphers)

    def _test_ciphersuite(self, server_connectivity_info, cipher):
        """This function is used by threads to it investigates with support the cipher suite on server. Returns instance of class AcceptCipher or RejectCipher.

            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server
            cipher (str): contains OpenSSL shortcut for identification cipher suite
        """
        ssl_conn = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=SSLV3)
        ssl_conn.set_cipher_list(cipher)
        try:
            ssl_conn.connect()
        except SSLHandshakeRejected as e:
            cipher_result = RejectCipher(
                OPENSSL_TO_RFC_NAMES_MAPPING[SSLV3].get(cipher, cipher),
                str(e))
        except Exception as e:
            cipher_result = RejectCipher(
                OPENSSL_TO_RFC_NAMES_MAPPING[SSLV3].get(cipher, cipher),
                str(e))
        else:
            cipher_result = AcceptCipher(
                OPENSSL_TO_RFC_NAMES_MAPPING[SSLV3].get(cipher, cipher))
        finally:
            ssl_conn.close()
        return cipher_result

    def get_ssl3_cipher_list(self):
        """Returns list of cipher suites available for protocol SSL 3.0
        """
        ssl_client = SslClient(ssl_version=SSLV3)
        ssl_client.set_cipher_list('SSLv3')
        return ssl_client.get_cipher_list()

    def test_SSLv3_support(self, server_connectivity_info):
        """Tests if protocol SSL 3.0 is supported by server. Returns true if server supports protocol SSL 3.0 otherwise returns false.

            Args:
            server_connectivity_info (ServerConnectivityInfo): contains information for connection on server            
        """
        if server_connectivity_info.highest_ssl_version_supported <= SSLV3:
            return True
        ssl_conn = server_connectivity_info.get_preconfigured_ssl_connection(
            override_ssl_version=SSLV3)
        try:
            ssl_conn.connect()
        except SSLHandshakeRejected as e:
            ssl3 = False
        else:
            ssl3 = True
        finally:
            ssl_conn.close()
        return ssl3

    def get_vulnerable_ciphers(self, accept_ciphers):
        """Returns set of cipher suites, which using CBC mode and are supported by server.

            Args:
            accept_ciphers(array): contains cipher suites, which are supported by server
        """
        result_set = []
        for cipher in accept_ciphers:
            if cipher.use_CBC_mode:
                result_set.append([cipher._cipher_rfc_name])
        return result_set
Beispiel #17
0
class SessionRenegotiationPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface("SessionRenegotiationPlugin", "")
    interface.add_command(
        command="reneg",
        help=
        'Tests the server(s) for client-initiated renegotiation and secure renegotiation support.'
    )

    def process_task(self, server_info, command, options_dict=None):
        # Check for client-initiated renegotiation
        accepts_client_renegotiation = self._test_client_renegotiation(
            server_info)

        # Check for secure renegotiation
        supports_secure_renegotiation = self._test_secure_renegotiation(
            server_info)

        return SessionRenegotiationResult(server_info, command, options_dict,
                                          accepts_client_renegotiation,
                                          supports_secure_renegotiation)

    @staticmethod
    def _test_secure_renegotiation(server_info):
        """Checks whether the server supports secure renegotiation.
        """
        ssl_connection = server_info.get_preconfigured_ssl_connection()

        try:
            # Perform the SSL handshake
            ssl_connection.connect()
            supports_secure_renegotiation = ssl_connection.get_secure_renegotiation_support(
            )

        finally:
            ssl_connection.close()

        return supports_secure_renegotiation

    @staticmethod
    def _test_client_renegotiation(server_info):
        """Checks whether the server honors session renegotiation requests.
        """
        ssl_connection = server_info.get_preconfigured_ssl_connection()

        try:
            # Perform the SSL handshake
            ssl_connection.connect()

            try:
                # Let's try to renegotiate
                ssl_connection.do_renegotiate()
                accepts_client_renegotiation = True

            # Errors caused by a server rejecting the renegotiation
            except socket.timeout:
                # This is how Netty rejects a renegotiation - https://github.com/nabla-c0d3/sslyze/issues/114
                accepts_client_renegotiation = False
            except socket.error as e:
                if 'connection was forcibly closed' in str(e.args):
                    accepts_client_renegotiation = False
                elif 'reset by peer' in str(e.args):
                    accepts_client_renegotiation = False
                else:
                    raise
            except OpenSSLError as e:
                if 'handshake failure' in str(e.args):
                    accepts_client_renegotiation = False
                elif 'no renegotiation' in str(e.args):
                    accepts_client_renegotiation = False
                elif 'tlsv1 unrecognized name' in str(e.args):
                    # Yahoo's very own way of rejecting a renegotiation
                    accepts_client_renegotiation = False
                else:
                    raise

            # Should be last as socket errors are also IOError
            except IOError as e:
                if 'Nassl SSL handshake failed' in str(e.args):
                    accepts_client_renegotiation = False
                else:
                    raise

        finally:
            ssl_connection.close()

        return accepts_client_renegotiation
Beispiel #18
0
class HttpHeadersPlugin(plugin_base.PluginBase):

    interface = plugin_base.PluginInterface(title="HttpHeadersPlugin",
                                            description='')
    interface.add_command(
        command="http_headers",
        help=
        "Checks for the HTTP Strict Transport Security (HSTS) and HTTP Public Key Pinning (HPKP) HTTP headers "
        "within the response sent back by the server(s). Also computes the HPKP pins for the server(s)' current "
        "certificate chain.")

    def process_task(self, server_info, command, options_dict=None):

        if server_info.tls_wrapped_protocol not in [
                TlsWrappedProtocolEnum.PLAIN_TLS, TlsWrappedProtocolEnum.HTTPS
        ]:
            raise ValueError(
                'Cannot test for HTTP headers on a StartTLS connection.')

        hsts_header, hpkp_header, hpkp_report_only, certificate_chain = self._get_security_headers(
            server_info)
        return HttpHeadersResult(server_info, command, options_dict,
                                 hsts_header, hpkp_header, hpkp_report_only,
                                 certificate_chain)

    # Sample GET request with the Chrome for Windows 7 User Agent
    HTTP_GET_FORMAT = 'GET / HTTP/1.1\r\n' \
                      'Host: {host}\r\n' \
                      'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36\r\n' \
                      'Accept: */*' \
                      'Connection: close\r\n\r\n'

    @classmethod
    def _get_security_headers(cls, server_info):
        hpkp_report_only = False

        # Perform the SSL handshake
        ssl_connection = server_info.get_preconfigured_ssl_connection()
        ssl_connection.connect()
        certificate_chain = ssl_connection.get_peer_cert_chain()

        # Send an HTTP GET request to the server
        ssl_connection.write(
            cls.HTTP_GET_FORMAT.format(host=server_info.hostname))
        http_resp = parse_http_response(ssl_connection)
        ssl_connection.close()

        if http_resp.version == 9:
            # HTTP 0.9 => Probably not an HTTP response
            raise ValueError('Server did not return an HTTP response')
        else:
            hsts_header = http_resp.getheader('strict-transport-security',
                                              None)
            hpkp_header = http_resp.getheader('public-key-pins', None)
            if hpkp_header is None:
                hpkp_report_only = True
                hpkp_header = http_resp.getheader(
                    'public-key-pins-report-only', None)

        # We do not follow redirections because the security headers must be set on the first page according to
        # https://hstspreload.appspot.com/:
        # "If you are serving an additional redirect from your HTTPS site, that redirect must still have the HSTS
        # header (rather than the page it redirects to)."

        return hsts_header, hpkp_header, hpkp_report_only, certificate_chain