def test_valid_chain(self): server_info = ServerConnectivityInfo(hostname='www.hotmail.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.ocsp_response) self.assertTrue(plugin_result.is_ocsp_response_trusted) self.assertTrue(plugin_result.is_leaf_certificate_ev) self.assertEquals(len(plugin_result.certificate_chain), 2) self.assertEquals(len(plugin_result.path_validation_result_list), 5) for path_validation_result in plugin_result.path_validation_result_list: self.assertTrue(path_validation_result.is_certificate_trusted) self.assertEquals(len(plugin_result.path_validation_error_list), 0) self.assertEquals(plugin_result.hostname_validation_result, X509_NAME_MATCHES_SAN) self.assertTrue(plugin_result.is_certificate_chain_order_valid) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Test the --ca_path option plugin_result = plugin.process_task(server_info, 'certinfo_basic', {'ca_file': os.path.join(os.path.dirname(__file__), 'utils', 'wildcard-self-signed.pem')}) self.assertEquals(len(plugin_result.path_validation_result_list), 6) for path_validation_result in plugin_result.path_validation_result_list: if path_validation_result.trust_store.name == 'Custom --ca_file': self.assertFalse(path_validation_result.is_certificate_trusted) else: self.assertTrue(path_validation_result.is_certificate_trusted)
def test_https_tunneling(self): # Start a local proxy proxy_port = 8000 p = multiprocessing.Process(target=proxy_worker, args=(proxy_port, )) p.start() try: # Run a scan through the proxy tunnel_settings = HttpConnectTunnelingSettings('localhost', proxy_port) server_info = ServerConnectivityInfo(hostname=u'www.google.com', http_tunneling_settings=tunnel_settings) # Try to connect to the proxy - retry if the proxy subprocess wasn't ready proxy_connection_attempts = 0 while True: try: server_info.test_connectivity_to_server() break except ServerConnectivityError: if proxy_connection_attempts > 3: raise proxy_connection_attempts += 1 plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) finally: # Kill the local proxy - unclean p.terminate()
def test_valid_chain_with_ev_cert(self): server_info = ServerConnectivityInfo(hostname='www.comodo.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertTrue(plugin_result.is_leaf_certificate_ev) self.assertEqual(len(plugin_result.certificate_chain), 3) self.assertEqual(len(plugin_result.verified_certificate_chain), 3) self.assertFalse(plugin_result.has_anchor_in_certificate_chain) self.assertEqual(len(plugin_result.path_validation_result_list), 5) for path_validation_result in plugin_result.path_validation_result_list: self.assertTrue(path_validation_result.is_certificate_trusted) self.assertEqual(len(plugin_result.path_validation_error_list), 0) self.assertEqual(plugin_result.certificate_matches_hostname, True) self.assertTrue(plugin_result.is_certificate_chain_order_valid) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_ca_file_bad_file(self): server_info = ServerConnectivityInfo(hostname='www.hotmail.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() with self.assertRaises(ValueError): plugin.process_task(server_info, CertificateInfoScanCommand(ca_file='doesntexist'))
def test_invalid_chain(self): server_info = ServerConnectivityInfo(hostname='self-signed.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertIsNone(plugin_result.ocsp_response) self.assertEqual(len(plugin_result.certificate_chain), 1) self.assertEqual(len(plugin_result.path_validation_result_list), 5) for path_validation_result in plugin_result.path_validation_result_list: self.assertFalse(path_validation_result.is_certificate_trusted) self.assertEqual(plugin_result.certificate_included_scts_count, 0) self.assertEqual(len(plugin_result.path_validation_error_list), 0) self.assertEqual(plugin_result.certificate_matches_hostname, True) self.assertTrue(plugin_result.is_certificate_chain_order_valid) self.assertIsNone(plugin_result.has_anchor_in_certificate_chain) self.assertIsNone(plugin_result.has_sha1_in_certificate_chain) self.assertFalse(plugin_result.verified_certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_1000_sans_chain(self): # Ensure SSLyze can process a leaf cert with 1000 SANs server_info = ServerConnectivityInfo(hostname='1000-sans.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin.process_task(server_info, CertificateInfoScanCommand())
def _get_plugin_result(self, hostname, command=Tlsv12ScanCommand()): server_info = ServerConnectivityInfo(hostname=hostname) server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, command) return plugin_result
def test_synchronous_scanner(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() sync_scanner = SynchronousScanner() plugin_result = sync_scanner.run_scan_command(server_info, CompressionScanCommand()) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_smtp_post_handshake_response(self): server_info = ServerConnectivityInfo(hostname='smtp.gmail.com', port=587, tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_SMTP) server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, Tlsv12ScanCommand()) self._test_plugin_outputs(plugin_result)
def test_1000_sans_chain(self): # Ensure SSLyze can process a leaf cert with 1000 SANs server_info = ServerConnectivityInfo(hostname='1000-sans.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') san_list = plugin_result.certificate_chain[0].as_dict['extensions']['X509v3 Subject Alternative Name']['DNS'] self.assertEquals(len(san_list), 1000)
def test_sha256_chain(self): server_info = ServerConnectivityInfo(hostname='sha256.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertIn('OK - No SHA1-signed certificate in the chain', '\n'.join(plugin_result.as_text())) self.assertTrue(plugin_result.as_xml())
def test_dh_info(self): server_info = ServerConnectivityInfo(hostname=u'dh480.badssl.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, 'tlsv1') self.assertTrue(plugin_result.preferred_cipher) self.assertEquals(plugin_result.preferred_cipher.dh_info['GroupSize'], '480') self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_sha1_chain(self): server_info = ServerConnectivityInfo(hostname=u'sha1-2017.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.has_sha1_in_certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_chain_with_anchor(self): server_info = ServerConnectivityInfo(hostname=u'www.verizon.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.has_anchor_in_certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_hsts_enabled(self): server_info = ServerConnectivityInfo(hostname='hsts.badssl.com') server_info.test_connectivity_to_server() plugin = HstsPlugin() plugin_result = plugin.process_task(server_info, 'hsts') self.assertTrue(plugin_result.hsts_header) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_ccs_injection_good(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = OpenSslCcsInjectionPlugin() plugin_result = plugin.process_task(server_info, OpenSslCcsInjectionScanCommand()) self.assertFalse(plugin_result.is_vulnerable_to_ccs_injection) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_fallback_good(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = FallbackScsvPlugin() plugin_result = plugin.process_task(server_info, 'fallback') self.assertTrue(plugin_result.supports_fallback_scsv) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_sha1_chain(self): server_info = ServerConnectivityInfo(hostname='sha1-intermediate.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertTrue(plugin_result.has_sha1_in_certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_robot_attack_good(self): server_info = ServerConnectivityInfo(hostname='www.facebook.com') server_info.test_connectivity_to_server() plugin = RobotPlugin() plugin_result = plugin.process_task(server_info, RobotScanCommand()) self.assertEqual(plugin_result.robot_result_enum, RobotScanResultEnum.NOT_VULNERABLE_NO_ORACLE) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_optional_client_authentication(self): for hostname in ['auth.startssl.com', 'xnet-eu.intellij.net']: server_info = ServerConnectivityInfo(hostname=hostname) server_info.test_connectivity_to_server() self.assertEquals(server_info.client_auth_requirement, ClientAuthenticationServerConfigurationEnum.OPTIONAL) plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_sha1_chain(self): server_info = ServerConnectivityInfo(hostname='sha1-2017.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') # TODO: Expose has_sha1 as an attribute self.assertIn('INSECURE - SHA1-signed certificate in the chain', '\n'.join(plugin_result.as_text())) self.assertTrue(plugin_result.as_xml())
def test_heartbleed_good(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = HeartbleedPlugin() plugin_result = plugin.process_task(server_info, 'heartbleed') self.assertFalse(plugin_result.is_vulnerable_to_heartbleed) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_unicode_certificate(self): server_info = ServerConnectivityInfo(hostname=u'www.főgáz.hu') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_compression_disabled(self): server_info = ServerConnectivityInfo(hostname=u"www.google.com") server_info.test_connectivity_to_server() plugin = CompressionPlugin() plugin_result = plugin.process_task(server_info, "compression") self.assertFalse(plugin_result.compression_name) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_international_names(self): server_info = ServerConnectivityInfo(hostname=u'www.sociétégénérale.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, u'certinfo_basic') self.assertEquals(len(plugin_result.certificate_chain), 3) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_ccs_injection_bad(self): with VulnerableOpenSslServer() as server: server_info = ServerConnectivityInfo(hostname=server.hostname, ip_address=server.ip_address, port=server.port) server_info.test_connectivity_to_server() plugin = OpenSslCcsInjectionPlugin() plugin_result = plugin.process_task(server_info, OpenSslCcsInjectionScanCommand()) self.assertTrue(plugin_result.is_vulnerable_to_ccs_injection) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_renegotiation_good(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = SessionRenegotiationPlugin() plugin_result = plugin.process_task(server_info, 'reneg') self.assertFalse(plugin_result.accepts_client_renegotiation) self.assertTrue(plugin_result.supports_secure_renegotiation) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_smtp_custom_port(self): server_info = ServerConnectivityInfo(hostname='smtp.gmail.com', port=587, tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_SMTP) server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertEquals(len(plugin_result.certificate_chain), 3) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_robot_attack_good_boringssl(self): # Validate the bug fix for https://github.com/nabla-c0d3/sslyze/issues/282 server_info = ServerConnectivityInfo(hostname='guide.duo.com') server_info.test_connectivity_to_server() plugin = RobotPlugin() plugin_result = plugin.process_task(server_info, RobotScanCommand()) self.assertEqual(plugin_result.robot_result_enum, RobotScanResultEnum.NOT_VULNERABLE_NO_ORACLE) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_xmpp_to(self): server_info = ServerConnectivityInfo(hostname='talk.google.com', tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_XMPP, xmpp_to_hostname='gmail.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertEquals(len(plugin_result.certificate_chain), 3) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_resumption_rate(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = SessionResumptionPlugin() plugin_result = plugin.process_task(server_info, SessionResumptionRateScanCommand()) self.assertTrue(plugin_result.attempted_resumptions_nb) self.assertTrue(plugin_result.successful_resumptions_nb) self.assertFalse(plugin_result.errored_resumptions_list) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_sslv2_disabled(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, Sslv20ScanCommand()) self.assertIsNone(plugin_result.preferred_cipher) self.assertFalse(plugin_result.accepted_cipher_list) self.assertTrue(plugin_result.rejected_cipher_list) self.assertFalse(plugin_result.errored_cipher_list) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_not_trusted_by_mozilla_but_trusted_by_microsoft(self): server_info = ServerConnectivityInfo( hostname='webmail.russia.nasa.gov') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertEqual(plugin_result.successful_trust_store.name, 'Microsoft') self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_ipv6(self): if not self._is_ipv6_available(): logging.warning(u'WARNING: IPv6 not available - skipping test') return server_info = ServerConnectivityInfo( hostname=u'www.google.com', ip_address=u'2607:f8b0:4005:804::2004') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertEquals(len(plugin_result.certificate_chain), 3) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def _test_client_renegotiation( server_info: ServerConnectivityInfo, tls_version_to_use: TlsVersionEnum) -> Tuple[_ScanJobResultEnum, bool]: """Check whether the server honors session renegotiation requests. """ ssl_connection = server_info.get_preconfigured_tls_connection( override_tls_version=tls_version_to_use, should_use_legacy_openssl=True) if not isinstance(ssl_connection.ssl_client, LegacySslClient): raise RuntimeError("Should never happen") try: # Perform the SSL handshake ssl_connection.connect() try: # Let's try to renegotiate ssl_connection.ssl_client.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 ConnectionError: accepts_client_renegotiation = False except OSError as e: # OSError is the parent of all (non-TLS) socket/connection errors so it should be last if "Nassl SSL handshake failed" in e.args[0]: # Special error returned by nassl accepts_client_renegotiation = False else: raise except OpenSSLError as e: if "handshake failure" in e.args[0]: accepts_client_renegotiation = False elif "no renegotiation" in e.args[0]: accepts_client_renegotiation = False elif "tlsv1 unrecognized name" in e.args[0]: # Yahoo's very own way of rejecting a renegotiation accepts_client_renegotiation = False elif "tlsv1 alert internal error" in e.args[0]: # Jetty server: https://github.com/nabla-c0d3/sslyze/issues/290 accepts_client_renegotiation = False elif "decryption failed or bad record mac" in e.args[0]: # Some servers such as reddit.com accepts_client_renegotiation = False elif "sslv3 alert unexpected message" in e.args[0]: # traefik https://github.com/nabla-c0d3/sslyze/issues/422 accepts_client_renegotiation = False else: raise finally: ssl_connection.close() return _ScanJobResultEnum.ACCEPTS_CLIENT_RENEG, accepts_client_renegotiation
def search_subject_alt_name(self, target): print("Searching for Subject Alt Names") try: server_info = ServerConnectivityInfo(hostname=target) server_info.test_connectivity_to_server() synchronous_scanner = SynchronousScanner() # Certificate information command = CertificateInfoScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) # Direct object reference is pretty bad, but then again so is the crypto.x509 object implementation, so... extensions = scan_result.certificate_chain[0].extensions[6] for entry in extensions.value: if entry.value.strip() not in self.domains: self.domains.append(entry.value.strip()) except Exception as e: self.handle_exception(e)
def test_hsts_and_hpkp_disabled(self): server_info = ServerConnectivityInfo(hostname='expired.badssl.com') server_info.test_connectivity_to_server() plugin = HttpHeadersPlugin() plugin_result = plugin.process_task(server_info, HttpHeadersScanCommand()) self.assertFalse(plugin_result.hsts_header) self.assertFalse(plugin_result.hpkp_header) self.assertIsNone(plugin_result.is_valid_pin_configured) self.assertIsNone(plugin_result.is_backup_pin_configured) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_https_tunneling_bad_arguments(self): # Ensure that an IP address cannot be specified when using an HTTP proxy for scans tunnel_settings = HttpConnectTunnelingSettings('fakedomain', 443) with self.assertRaisesRegexp( ValueError, 'Cannot specify both ip_address and http_tunneling_settings'): ServerConnectivityInfo(hostname='www.google.com', ip_address='1.2.3.4', http_tunneling_settings=tunnel_settings)
def test_hpkp_enabled(self): server_info = ServerConnectivityInfo(hostname='github.com') server_info.test_connectivity_to_server() plugin = HttpHeadersPlugin() plugin_result = plugin.process_task(server_info, HttpHeadersScanCommand()) self.assertTrue(plugin_result.hpkp_header) self.assertTrue(plugin_result.is_valid_pin_configured) self.assertTrue(plugin_result.is_backup_pin_configured) self.assertTrue(plugin_result.verified_certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_tls_1_3_cipher_suites(self): server_info = ServerConnectivityInfo(hostname='www.cloudflare.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, Tlsv13ScanCommand()) accepted_cipher_name_list = [ cipher.name for cipher in plugin_result.accepted_cipher_list ] # TODO(AD): Update to TLS 1.3 draft 23 and re-enable this test return self.assertEqual( { 'TLS_CHACHA20_POLY1305_SHA256', 'TLS_AES_256_GCM_SHA384', 'TLS_AES_128_GCM_SHA256' }, set(accepted_cipher_name_list))
def test_concurrent_scanner(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() # Queue some scan commands that are quick concurrent_scanner = ConcurrentScanner() concurrent_scanner.queue_scan_command(server_info, CertificateInfoScanCommand()) concurrent_scanner.queue_scan_command(server_info, SessionRenegotiationScanCommand()) concurrent_scanner.queue_scan_command(server_info, CompressionScanCommand()) # Process the results nb_results = 0 for plugin_result in concurrent_scanner.get_results(): self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) nb_results +=1 self.assertEquals(nb_results, 3)
def test_rc4_md5_cipher_suites(self): server_info = ServerConnectivityInfo(hostname='rc4-md5.badssl.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, Tlsv12ScanCommand()) accepted_cipher_name_list = [ cipher.name for cipher in plugin_result.accepted_cipher_list ] self.assertEqual({'TLS_RSA_WITH_RC4_128_MD5'}, set(accepted_cipher_name_list)) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_https_tunneling(self): # Start a local proxy proxy_port = 8000 p = multiprocessing.Process(target=proxy_worker, args=(proxy_port, )) p.start() # On Travis CI, the server sometimes is still not ready to accept connections when we get here # Wait a bit more to make the test suite less flaky time.sleep(0.5) try: # Run a scan through the proxy tunnel_settings = HttpConnectTunnelingSettings( 'localhost', proxy_port, basic_auth_user='******', basic_auth_password='******') server_info = ServerConnectivityInfo( hostname='www.google.com', http_tunneling_settings=tunnel_settings) # Try to connect to the proxy - retry if the proxy subprocess wasn't ready proxy_connection_attempts = 0 while True: try: server_info.test_connectivity_to_server() break except ServerConnectivityError: if proxy_connection_attempts > 3: raise proxy_connection_attempts += 1 plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertGreaterEqual(len(plugin_result.certificate_chain), 1) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) finally: # Kill the local proxy - unclean p.terminate()
def test_only_trusted_by_custom_ca_file(self): server_info = ServerConnectivityInfo(hostname='self-signed.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() ca_file_path = os.path.join(os.path.dirname(__file__), '..', 'utils', 'self-signed.badssl.com.pem') plugin_result = plugin.process_task( server_info, CertificateInfoScanCommand(ca_file=ca_file_path)) self.assertEqual(plugin_result.successful_trust_store.name, 'Custom --ca_file') self.assertTrue(plugin_result.verified_certificate_chain) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_sslv2_enabled(self): try: with VulnerableOpenSslServer() as server: server_info = ServerConnectivityInfo( hostname=server.hostname, ip_address=server.ip_address, port=server.port) server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, Sslv20ScanCommand()) except NotOnLinux64Error: # The test suite only has the vulnerable OpenSSL version compiled for Linux 64 bits logging.warning( 'WARNING: Not on Linux - skipping test_sslv2_enabled() test') return # The embedded server does not have a preference self.assertFalse(plugin_result.preferred_cipher) accepted_cipher_name_list = [ cipher.name for cipher in plugin_result.accepted_cipher_list ] self.assertEqual( { 'SSL_CK_RC4_128_EXPORT40_WITH_MD5', 'SSL_CK_IDEA_128_CBC_WITH_MD5', 'SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5', 'SSL_CK_DES_192_EDE3_CBC_WITH_MD5', 'SSL_CK_DES_192_EDE3_CBC_WITH_MD5', 'SSL_CK_RC4_128_WITH_MD5', 'SSL_CK_RC2_128_CBC_WITH_MD5', 'SSL_CK_DES_64_CBC_WITH_MD5' }, set(accepted_cipher_name_list)) self.assertTrue(plugin_result.accepted_cipher_list) self.assertFalse(plugin_result.rejected_cipher_list) self.assertFalse(plugin_result.errored_cipher_list) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_valid_chain_with_ocsp_stapling_and_must_staple(self): server_info = ServerConnectivityInfo(hostname='www.scotthelme.co.uk') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertTrue(plugin_result.ocsp_response) self.assertEqual(plugin_result.ocsp_response_status, OcspResponseStatusEnum.SUCCESSFUL) self.assertTrue(plugin_result.is_ocsp_response_trusted) self.assertTrue(plugin_result.certificate_has_must_staple_extension) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_null_cipher_suites(self): server_info = ServerConnectivityInfo(hostname=u'null.badssl.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, Tlsv12ScanCommand()) accepted_cipher_name_list = [cipher.name for cipher in plugin_result.accepted_cipher_list] self.assertEquals({'TLS_ECDH_anon_WITH_AES_256_CBC_SHA', 'TLS_DH_anon_WITH_AES_256_CBC_SHA256', 'TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA', 'TLS_DH_anon_WITH_AES_256_GCM_SHA384', 'TLS_DH_anon_WITH_AES_256_CBC_SHA', 'TLS_ECDH_anon_WITH_AES_128_CBC_SHA', 'TLS_DH_anon_WITH_AES_128_CBC_SHA256', 'TLS_DH_anon_WITH_AES_128_CBC_SHA', 'TLS_DH_anon_WITH_AES_128_GCM_SHA256', 'TLS_DH_anon_WITH_SEED_CBC_SHA', 'TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA', 'TLS_ECDHE_RSA_WITH_NULL_SHA', 'TLS_ECDH_anon_WITH_NULL_SHA', 'TLS_RSA_WITH_NULL_SHA256', 'TLS_RSA_WITH_NULL_SHA'}, set(accepted_cipher_name_list)) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def scan(hostname, port): # Setup the servers to scan and ensure they are reachable try: server_info = ServerConnectivityInfo(hostname=hostname, port=port) server_info.test_connectivity_to_server() except ServerConnectivityError as e: # Could not establish an SSL connection to the server raise RuntimeError('Error when connecting to {}: {}'.format(hostname, e.error_msg)) # Get the list of available plugins sslyze_plugins = PluginsFinder() # Create a process pool to run scanning commands concurrently plugins_process_pool = PluginsProcessPool(sslyze_plugins) # Queue some scan commands; the commands are same as what is described in the SSLyze CLI --help text. # print '\nQueuing some commands...' plugins_process_pool.queue_plugin_task(server_info, 'sslv3') plugins_process_pool.queue_plugin_task(server_info, 'certinfo_basic') plugins_process_pool.queue_plugin_task(server_info, 'tlsv1') plugins_process_pool.queue_plugin_task(server_info, 'tlsv1_1') plugins_process_pool.queue_plugin_task(server_info, 'tlsv1_2') plugins_process_pool.queue_plugin_task(server_info, 'sslv2') result = {} result['server_info'] = server_info.__dict__ for res in plugins_process_pool.get_results(): if res.plugin_command in ['sslv2','sslv3','tlsv1','tlsv1_1','tlsv1_2']: supported = False if len(res.accepted_cipher_list) > 0: supported = True acc_ciphers = [] rej_ciphers = [] for cipher in res.accepted_cipher_list: acc_ciphers.append(cipher.name) result[res.plugin_command] = {'supported': supported, 'accepted_ciphers': acc_ciphers} elif res.plugin_command == 'certinfo_basic': result['certinfo'] = parse_certinfo(res.__dict__) return result
def ssl_scan(self, target): print("Running SSL Scan") try: server_info = ServerConnectivityInfo(hostname=target) server_info.test_connectivity_to_server() synchronous_scanner = SynchronousScanner() # TLS 1.0 command = Tlsv10ScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) print("Available TLSv1.0 Ciphers:") for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) # TLSv1.2 command = Tlsv12ScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) print("Available TLSv1.2 Ciphers:") for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) # Certificate information command = CertificateInfoScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) for entry in scan_result.as_text(): print(entry) # Heartbleed vulnerability info command = HeartbleedScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) for entry in scan_result.as_text(): print(entry) # HTTP Headers info command = HttpHeadersScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) for entry in scan_result.as_text(): print(entry) except Exception as e: self.handle_exception(e, "Error running SSL scan") pass
def test_robot_attack_good(self): # Validate the bug fix for https://github.com/nabla-c0d3/sslyze/issues/282 server_info = ServerConnectivityInfo(hostname='guide.duo.com') server_info.test_connectivity_to_server() plugin = RobotPlugin() plugin_result = plugin.process_task(server_info, RobotScanCommand()) # On Travis CI we sometimes get inconsistent results if IS_RUNNING_ON_TRAVIS: self.assertIn(plugin_result.robot_result_enum, [ RobotScanResultEnum.NOT_VULNERABLE_NO_ORACLE, RobotScanResultEnum.UNKNOWN_INCONSISTENT_RESULTS ]) else: self.assertEqual(plugin_result.robot_result_enum, RobotScanResultEnum.NOT_VULNERABLE_NO_ORACLE) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_heartbleed_bad(self): try: with VulnerableOpenSslServer() as server: server_info = ServerConnectivityInfo(hostname=server.hostname, ip_address=server.ip_address, port=server.port) server_info.test_connectivity_to_server() plugin = HeartbleedPlugin() plugin_result = plugin.process_task(server_info, HeartbleedScanCommand()) except NotOnLinux64Error: # The test suite only has the vulnerable OpenSSL version compiled for Linux 64 bits logging.warning('WARNING: Not on Linux - skipping test_heartbleed_bad() test') return self.assertTrue(plugin_result.is_vulnerable_to_heartbleed) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))
def test_fallback_bad(self): try: server = VulnerableOpenSslServer(port=8010) except NotOnLinux64Error: # The test suite only has the vulnerable OpenSSL version compiled for Linux 64 bits logging.warning('WARNING: Not on Linux - skipping test_fallback_bad() test') return server.start() server_info = ServerConnectivityInfo(hostname=server.hostname, ip_address=server.ip_address, port=server.port) server_info.test_connectivity_to_server() plugin = FallbackScsvPlugin() plugin_result = plugin.process_task(server_info, FallbackScsvScanCommand()) server.terminate() self.assertFalse(plugin_result.supports_fallback_scsv) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_tlsv1_0_enabled(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() plugin_result = plugin.process_task(server_info, 'tlsv1') self.assertTrue(plugin_result.preferred_cipher) accepted_cipher_name_list = [cipher.name for cipher in plugin_result.accepted_cipher_list] self.assertEquals({'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', 'TLS_RSA_WITH_AES_256_CBC_SHA', 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', 'TLS_RSA_WITH_AES_128_CBC_SHA', 'TLS_RSA_WITH_3DES_EDE_CBC_SHA'}, set(accepted_cipher_name_list)) self.assertTrue(plugin_result.accepted_cipher_list) self.assertTrue(plugin_result.rejected_cipher_list) self.assertFalse(plugin_result.errored_cipher_list) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def _send_robot_payload( server_info: ServerConnectivityInfo, tls_version_to_use: TlsVersionEnum, rsa_cipher_string: str, robot_payload_enum: RobotPmsPaddingPayloadEnum, robot_should_finish_handshake: bool, rsa_modulus: int, rsa_exponent: int, ) -> str: # Do a handshake which each record and keep track of what the server returned ssl_connection = server_info.get_preconfigured_tls_connection(override_tls_version=tls_version_to_use) # Replace nassl.sslClient.do_handshake() with a ROBOT checking SSL handshake so that all the SSLyze # options (startTLS, proxy, etc.) still work ssl_connection.ssl_client.do_handshake = types.MethodType( # type: ignore do_handshake_with_robot, ssl_connection.ssl_client ) ssl_connection.ssl_client.set_cipher_list(rsa_cipher_string) # Compute the payload tls_parser_tls_version: tls_parser.tls_version.TlsVersionEnum if tls_version_to_use == TlsVersionEnum.SSL_3_0: tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.SSLV3 elif tls_version_to_use == TlsVersionEnum.TLS_1_0: tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.TLSV1 elif tls_version_to_use == TlsVersionEnum.TLS_1_1: tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.TLSV1_1 elif tls_version_to_use == TlsVersionEnum.TLS_1_2: tls_parser_tls_version = tls_parser.tls_version.TlsVersionEnum.TLSV1_2 else: raise ValueError("Should never happen") cke_payload = _RobotTlsRecordPayloads.get_client_key_exchange_record( robot_payload_enum, tls_parser_tls_version, rsa_modulus, rsa_exponent ) # H4ck: we need to pass some arguments to the handshake but there is no simple way to do it; we use an attribute ssl_connection.ssl_client._robot_cke_record = cke_payload # type: ignore ssl_connection.ssl_client._robot_should_finish_handshake = robot_should_finish_handshake # type: ignore server_response = "" try: # Start the SSL handshake ssl_connection.connect() except ServerResponseToRobot as e: # Should always be thrown server_response = e.server_response except socket.timeout: # https://github.com/nabla-c0d3/sslyze/issues/361 server_response = "Connection timed out" finally: ssl_connection.close() return server_response
def test_starttls(self): for hostname, protocol in [ ('imap.comcast.net', TlsWrappedProtocolEnum.STARTTLS_IMAP), ('pop.comcast.net', TlsWrappedProtocolEnum.STARTTLS_POP3), ('ldap.uchicago.edu', TlsWrappedProtocolEnum.STARTTLS_LDAP), ('jabber.org', TlsWrappedProtocolEnum.STARTTLS_XMPP_SERVER), # Some Heroku Postgres instance I created ('ec2-54-75-226-17.eu-west-1.compute.amazonaws.com', TlsWrappedProtocolEnum.STARTTLS_POSTGRES) ]: server_info = ServerConnectivityInfo(hostname=hostname, tls_wrapped_protocol=protocol) server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, CertificateInfoScanCommand()) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def test_valid_chain(self): server_info = ServerConnectivityInfo(hostname=u'www.hotmail.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertTrue(plugin_result.ocsp_response) self.assertTrue(plugin_result.is_ocsp_response_trusted) self.assertTrue(plugin_result.is_leaf_certificate_ev) self.assertEquals(len(plugin_result.certificate_chain), 2) self.assertEquals(len(plugin_result.verified_certificate_chain), 3) self.assertFalse(plugin_result.has_anchor_in_certificate_chain) self.assertEquals(len(plugin_result.path_validation_result_list), 5) for path_validation_result in plugin_result.path_validation_result_list: self.assertTrue(path_validation_result.is_certificate_trusted) self.assertEquals(len(plugin_result.path_validation_error_list), 0) self.assertEquals(plugin_result.hostname_validation_result, X509_NAME_MATCHES_SAN) self.assertTrue(plugin_result.is_certificate_chain_order_valid) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Test the --ca_path option plugin_result = plugin.process_task( server_info, 'certinfo_basic', { 'ca_file': os.path.join(os.path.dirname(__file__), 'utils', 'wildcard-self-signed.pem') }) self.assertEquals(len(plugin_result.path_validation_result_list), 5) for path_validation_result in plugin_result.path_validation_result_list: if path_validation_result.trust_store.name == 'Custom --ca_file': self.assertFalse(path_validation_result.is_certificate_trusted) else: self.assertTrue(path_validation_result.is_certificate_trusted)
def test_invalid_chain(self): server_info = ServerConnectivityInfo(hostname='self-signed.badssl.com') server_info.test_connectivity_to_server() plugin = CertificateInfoPlugin() plugin_result = plugin.process_task(server_info, 'certinfo_basic') self.assertIsNone(plugin_result.ocsp_response) self.assertEquals(len(plugin_result.certificate_chain), 1) self.assertEquals(len(plugin_result.path_validation_result_list), 5) for path_validation_result in plugin_result.path_validation_result_list: self.assertFalse(path_validation_result.is_certificate_trusted) self.assertEquals(len(plugin_result.path_validation_error_list), 0) self.assertEquals(plugin_result.hostname_validation_result, X509_NAME_MATCHES_SAN) self.assertTrue(plugin_result.is_certificate_chain_order_valid) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml())
def _test_client_renegotiation( server_info: ServerConnectivityInfo, tls_version_to_use: OpenSslVersionEnum ) -> Tuple[_ScanJobResultEnum, bool]: """Check whether the server honors session renegotiation requests. """ ssl_connection = server_info.get_preconfigured_tls_connection( override_tls_version=tls_version_to_use, should_use_legacy_openssl=True) if not isinstance(ssl_connection.ssl_client, LegacySslClient): raise RuntimeError("Should never happen") try: # Perform the SSL handshake ssl_connection.connect() try: # Let's try to renegotiate ssl_connection.ssl_client.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 elif "Nassl SSL handshake failed" 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 elif "tlsv1 alert internal error" in str(e.args): # Jetty server: https://github.com/nabla-c0d3/sslyze/issues/290 accepts_client_renegotiation = False else: raise finally: ssl_connection.close() return _ScanJobResultEnum.ACCEPTS_CLIENT_RENEG, accepts_client_renegotiation
def connect_with_cipher_suite( server_connectivity_info: ServerConnectivityInfo, tls_version: TlsVersionEnum, cipher_suite: CipherSuite ) -> Union[CipherSuiteAcceptedByServer, CipherSuiteRejectedByServer]: """Initiates a SSL handshake with the server using the SSL version and the cipher suite specified. """ requires_legacy_openssl = True if tls_version == TlsVersionEnum.TLS_1_2: # For TLS 1.2, we need to pick the right version of OpenSSL depending on which cipher suite requires_legacy_openssl = WorkaroundForTls12ForCipherSuites.requires_legacy_openssl( cipher_suite.openssl_name) elif tls_version == TlsVersionEnum.TLS_1_3: requires_legacy_openssl = False ssl_connection = server_connectivity_info.get_preconfigured_tls_connection( override_tls_version=tls_version, should_use_legacy_openssl=requires_legacy_openssl) _set_cipher_suite_string(tls_version, cipher_suite.openssl_name, ssl_connection.ssl_client) ephemeral_key = None try: # Perform the SSL handshake ssl_connection.connect() ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key() except ServerTlsConfigurationNotSupported: # SSLyze rejected the handshake because the server's DH config was too insecure; this means the # cipher suite is actually supported pass except ClientCertificateRequested: # When the handshake failed due to ClientCertificateRequested ephemeral_key = ssl_connection.ssl_client.get_ephemeral_key() pass except ServerRejectedTlsHandshake as e: return CipherSuiteRejectedByServer(cipher_suite=cipher_suite, error_message=e.error_message) except TlsHandshakeTimedOut as e: # Sometimes triggered by servers that don't support (at all) a specific version of TLS # Amazon Cloudfront does that with TLS 1.3 # There's no easy way to differentiate this error from a network glitch/timeout return CipherSuiteRejectedByServer(cipher_suite=cipher_suite, error_message=e.error_message) finally: ssl_connection.close() return CipherSuiteAcceptedByServer(cipher_suite=cipher_suite, ephemeral_key=ephemeral_key)
def test_tlsv1_2_enabled(self): server_info = ServerConnectivityInfo(hostname='www.google.com') server_info.test_connectivity_to_server() plugin = OpenSslCipherSuitesPlugin() # Also do full HTTP connections plugin_result = plugin.process_task(server_info, Tlsv12ScanCommand(http_get=True)) self.assertTrue(plugin_result.preferred_cipher) self.assertTrue(plugin_result.accepted_cipher_list) accepted_cipher_name_list = [ cipher.name for cipher in plugin_result.accepted_cipher_list ] self.assertEqual( { 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', 'TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA', 'TLS_RSA_WITH_AES_256_GCM_SHA384', 'TLS_RSA_WITH_AES_256_CBC_SHA', 'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA', 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256', 'TLS_RSA_WITH_AES_128_GCM_SHA256', 'TLS_RSA_WITH_AES_128_CBC_SHA', 'TLS_RSA_WITH_3DES_EDE_CBC_SHA', 'TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256', 'TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256' }, set(accepted_cipher_name_list)) self.assertTrue(plugin_result.rejected_cipher_list) self.assertFalse(plugin_result.errored_cipher_list) self.assertTrue(plugin_result.as_text()) self.assertTrue(plugin_result.as_xml()) # Ensure the results are pickable so the ConcurrentScanner can receive them via a Queue self.assertTrue(pickle.dumps(plugin_result))