def retrieve_ssl_vulnerabilities_for_tcp_service( self, org_uuid=None, service_uuid=None, scan_uuid=None, ): """ This performs various SSL vulnerability scans on the tcp service :param org_uuid: The UUID of the organization to collect information on behalf of. :param service_uuid: The UUID of the network service to retrieve the SSL certificate for. :param scan_uuid: The UUID of the network service scan that this SSL certificate retrieval is associated with. :return: None """ ip_address, port, protocol = self.get_endpoint_information(service_uuid) ssl_vulnerabilities_record = SslVulnerabilitiesModel.from_database_model_uuid( uuid=scan_uuid, db_session=self.db_session ) server_info = ServerConnectivityInfo(hostname=ip_address, ip_address=ip_address, port=port) try: server_info.test_connectivity_to_server() except ServerConnectivityError as e: # Could not establish an SSL connection to the server logger.error( "Error making an SSL connection to the server while looking for vulnerabilities, something went really wrong." ) synchronous_scanner = SynchronousScanner() fallback_scsv_command = FallbackScsvScanCommand() fallback_scsv_result = synchronous_scanner.run_scan_command(server_info, fallback_scsv_command) ssl_vulnerabilities_record.supports_fallback_scsv = fallback_scsv_result.supports_fallback_scsv heartbleed_command = HeartbleedScanCommand() heartbleed_result = synchronous_scanner.run_scan_command(server_info, heartbleed_command) ssl_vulnerabilities_record.is_vulnerable_to_heartbleed = heartbleed_result.is_vulnerable_to_heartbleed openssl_css_injection_command = OpenSslCcsInjectionScanCommand() openssl_css_injection_result = synchronous_scanner.run_scan_command(server_info, openssl_css_injection_command) ssl_vulnerabilities_record.is_vulnerable_to_ccs_injection = openssl_css_injection_result.is_vulnerable_to_ccs_injection session_renegotion_command = SessionRenegotiationScanCommand() session_renegotion_result = synchronous_scanner.run_scan_command(server_info, session_renegotion_command) ssl_vulnerabilities_record.accepts_client_renegotiation = session_renegotion_result.accepts_client_renegotiation ssl_vulnerabilities_record.supports_secure_renegotiation = session_renegotion_result.supports_secure_renegotiation session_resumption_support_command = SessionResumptionSupportScanCommand() session_resumption_support_result = synchronous_scanner.run_scan_command(server_info, session_resumption_support_command) ssl_vulnerabilities_record.is_ticket_resumption_supported = session_resumption_support_result.is_ticket_resumption_supported ssl_vulnerabilities_record.save(org_uuid)
def ssl_scan(self): print("Running SSL Scan") try: server_info = ServerConnectivityInfo( hostname=self.options["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_ssl_service_for_ssl_vulnerability( self, org_uuid=None, network_service_uuid=None, network_service_scan_uuid=None, vulnerability_name=None, ): """ Test the given network service for the specified SSL vulnerability. :param org_uuid: The UUID of the organization to enumerate SSL vulnerabilities on behalf of. :param network_service_uuid: The UUID of the network service that is being scanned. :param network_service_scan_uuid: The UUID of the network service scan that this enumeration is a part of. :param vulnerability_name: A string representing the vulnerability to test for. :return: None """ logger.info( "Now testing network service %s for SSL vulnerability %s." % (network_service_uuid, vulnerability_name) ) command_map = get_ssl_vulnerabilities_command_map() ValidationHelper.validate_in(to_check=vulnerability_name, contained_by=command_map.keys()) command = command_map[vulnerability_name]["command"] ip_address, port, protocol = self.get_endpoint_information(network_service_uuid) scanner = SynchronousScanner() server_info = ServerConnectivityInfo(hostname=ip_address, ip_address=ip_address, port=port) try: server_info.test_connectivity_to_server() except ServerConnectivityError as e: logger.warning( "ServerConnectivityError thrown when attempting to test SSL at %s:%s for %s vulnerability: %s" % (ip_address, port, vulnerability_name, e.message) ) return try: result = scanner.run_scan_command(server_info, command()) vuln_model = SslVulnerabilityModel.from_database_model_uuid( uuid=network_service_scan_uuid, db_session=self.db_session, test_errored=False, vuln_test_name=vulnerability_name, ) vuln_model.test_results = [] for field in command_map[vulnerability_name]["fields"]: vuln_model.test_results.append({ "key": field, "value": getattr(result, field), }) vuln_model.save(org_uuid) except (socket.error, OpenSSLError): vuln_model = SslVulnerabilityModel.from_database_model_uuid( uuid=network_service_scan_uuid, db_session=self.db_session, test_errored=True, ) vuln_model.save(org_uuid) logger.info( "Network service %s successfully tested for SSL vulnerability %s." % (network_service_uuid, vulnerability_name) )
def server_scanner(p): try: logger = logging.getLogger(__name__) logger.info("Opening file") server_tester = ServerConnectivityTester(hostname='localhost', port=p) server_info = server_tester.perform() scanner = SynchronousScanner() results = dict() suite_names = [ 'SSLV2 Cipher Suites', 'SSLV3 Cipher Suites', 'TLSV1_0 Cipher Suites', 'TLSV1_1 Cipher Suites', 'TLSV1_2 Cipher Suites', 'TLSV1_3 Cipher Suites' ] suites = [ Sslv20ScanCommand, Sslv30ScanCommand, Tlsv10ScanCommand, Tlsv11ScanCommand, Tlsv12ScanCommand, Tlsv13ScanCommand ] logger.info("Scanning SSL/TLS suites, writing to ciphers.json") for suite_name, suite in zip(suite_names, suites): scan = scanner.run_scan_command(server_info, suite()) results[suite_name] = [ cipher.name for cipher in scan.accepted_cipher_list ] with open(filename, 'w') as outfile: json.dump(results, outfile) except ServerConnectivityError as e: raise RuntimeError( f'Could not connect to {e.server_info.hostname}: {e.error_message}' )
def enumerate_cipher_suites_for_ssl_service( self, org_uuid=None, network_service_uuid=None, network_service_scan_uuid=None, order_uuid=None, ): """ Enumerate all of the cipher suites that the given SSL/TLS service supports. :param org_uuid: The UUID of the organization to enumerate cipher suites on behalf of. :param network_service_uuid: The UUID of the network service that is being scanned. :param network_service_scan_uuid: The UUID of the network service scan that this enumeration is a part of. :return: None """ logger.info( "Now enumerating supported cipher suites for network service %s." % (network_service_uuid, )) ip_address = self.network_service.ip_address.address port = self.network_service.port server_info = ServerConnectivityInfo(hostname=ip_address, ip_address=ip_address, port=port) try: server_info.test_connectivity_to_server() except ServerConnectivityError as e: logger.warning( "ServerConnectivityError thrown when attempting to inspect SSL at %s:%s: %s" % (ip_address, port, e.message)) return scanner = SynchronousScanner() bulk_query = BulkElasticsearchQuery() network_service_scan = self.network_service_scan for ssl_protocol, command in get_ssl_cipher_suite_commands(): result = scanner.run_scan_command(server_info, command()) ssl_support_record = SslSupportModel.from_database_model( network_service_scan, ssl_version=ssl_protocol, supported=len(result.accepted_cipher_list) > 0, ) ssl_support_record.accepted_ciphers = [ cipher.name for cipher in result.accepted_cipher_list ] ssl_support_record.rejected_ciphers = [ cipher.name for cipher in result.rejected_cipher_list ] ssl_support_record.errored_ciphers = [ cipher.name for cipher in result.errored_cipher_list ] ssl_support_record.preferred_cipher = result.preferred_cipher.name if result.preferred_cipher else None bulk_query.add_model_for_indexing(model=ssl_support_record, index=org_uuid) logger.info( "All cipher suite information converted to Elasticsearch data. Now updating via bulk query." ) bulk_query.save() logger.info( "Bulk query completed. SSL cipher suites enumerated for network service %s." % (network_service_uuid, ))
def test_synchronous_scanner(self): server_test = ServerConnectivityTester(hostname='www.google.com') server_info = server_test.perform() 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_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_synchronous_scanner(self): server_test = ServerConnectivityTester(hostname='www.google.com') server_info = server_test.perform() sync_scanner = SynchronousScanner() plugin_result = sync_scanner.run_scan_command(server_info, CompressionScanCommand()) assert plugin_result.as_text() assert plugin_result.as_xml()
def ciphers_scanner(server_info: ServerConnectivityInfo) -> dict: scanner = SynchronousScanner() commands = COMMANDS ciphers_info: dict = {} for command in commands: scan_result = scanner.run_scan_command(server_info, command) ciphers: list = [] for cipher in scan_result.accepted_cipher_list: ciphers.append(cipher.name) ciphers_info[command.get_cli_argument()] = ciphers return ciphers_info
def scan_ssl(self, ssl_domain, scan_id, cipher_list): # Comparing ciphers -- #used_ciphers = {cipher for cipher in sslyze.get("Accepted Ciphers").split(', ')} #bad_ciphers = list(used_ciphers - accepted_ciphers) #signature_algorithm = sslyze.get("Signature Algorithm", "sha1") #acceptable_ciphers = not bad_ciphers ssl_dict = {} ssl_dict["domain"] = ssl_domain["domain"] domain = ssl_domain["domain"] self.update_state(state="SCANNING") print(f'Scanning {domain} for ssl info...') #_install_req = subprocess.check_output("") try: server_tester = ServerConnectivityTester( hostname=ssl_domain["domain"], port=443, tls_wrapped_protocol=TlsWrappedProtocolEnum.PLAIN_TLS) print( f'\nTesting connectivity with {server_tester.hostname}:{server_tester.port}...' ) server_info = server_tester.perform() except ServerConnectivityError as e: # Could not establish an SSL connection to the server raise RuntimeError( f'Could not connect to {e.server_info.hostname}: {e.error_message}' ) _cmd = Tlsv10ScanCommand() synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command(server_info, _cmd) i = 0 used_ciphers = [] for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}') used_ciphers.append(cipher.name) i = i + 1 print(f'Finished scanning {domain} for ssl info') bad_ciphers = [] for cipher in used_ciphers: if cipher not in cipher_list: bad_ciphers.append(cipher) ssl_dict['accepted_ciphers'] = used_ciphers ssl_dict['bad_ciphers'] = bad_ciphers return ssl_dict, "ssl", scan_id
class WorkerProcess(Process): """The main process class responsible for instantiating and running the plugins. """ def __init__( self, priority_queue_in: JoinableQueue, queue_in: JoinableQueue, queue_out: JoinableQueue, network_retries: int, network_timeout: int, ) -> None: Process.__init__(self) self.priority_queue_in = priority_queue_in self.queue_in = queue_in self.queue_out = queue_out # The object that will actually run the scan commands self._synchronous_scanner = SynchronousScanner(network_retries, network_timeout) def run(self) -> None: """The process will first complete tasks it gets from self.queue_in. Once it gets notified that all the tasks have been completed, it terminates. """ from sslyze.concurrent_scanner import PluginRaisedExceptionScanResult # Start processing task in the priority queue first current_queue_in = self.priority_queue_in while True: task = current_queue_in.get() # Grab a task from queue_in if task is None: # All tasks have been completed current_queue_in.task_done() if current_queue_in == self.priority_queue_in: # All high priority tasks have been completed; switch to low priority tasks current_queue_in = self.queue_in continue else: # All the tasks have been completed; pass on the sentinel to result_queue and exit self.queue_out.put(None) break server_info, scan_command = task try: result = self._synchronous_scanner.run_scan_command(server_info, scan_command) except Exception as e: # raise result = PluginRaisedExceptionScanResult(server_info, scan_command, e) # Send the result to queue_out self.queue_out.put(result) current_queue_in.task_done()
def executePerDomainAction(self, domain): """ This plugin's per-domain action is to run a barrage of sslyze certificate tests Params: domain (str) """ from sslyze.plugins.certificate_info_plugin import CertificateInfoScanCommand from sslyze.plugins.compression_plugin import CompressionScanCommand from sslyze.plugins.session_renegotiation_plugin import SessionRenegotiationScanCommand from sslyze.server_connectivity_tester import ServerConnectivityTester from sslyze.server_connectivity_tester import ServerConnectivityError from sslyze.synchronous_scanner import SynchronousScanner try: sslyze_conn_test = ServerConnectivityTester(hostname=domain) sslyze_server_info = sslyze_conn_test.perform() sslyze_scanner = SynchronousScanner() sslyze_results = sslyze_scanner.run_scan_command( sslyze_server_info, CertificateInfoScanCommand()) sslyze_result_lines = sslyze_results.as_text() for line in sslyze_result_lines: self.logger.info(line) sslyze_results = sslyze_scanner.run_scan_command( sslyze_server_info, SessionRenegotiationScanCommand()) sslyze_result_lines = sslyze_results.as_text() for line in sslyze_result_lines: self.logger.info(line) sslyze_results = sslyze_scanner.run_scan_command( sslyze_server_info, CompressionScanCommand()) sslyze_result_lines = sslyze_results.as_text() for line in sslyze_result_lines: self.logger.info(line) except ServerConnectivityError as e: # Could not establish a TLS/SSL connection to the server self.logger.error( f'sslyze ended early, could not connect to {e.server_info.hostname}: {e.error_message}' ) print( f'sslyze ended early, could not connect to {e.server_info.hostname}: {e.error_message}' )
class WorkerProcess(Process): """The main process class responsible for instantiating and running the plugins. """ def __init__( self, priority_queue_in: JoinableQueue, queue_in: JoinableQueue, queue_out: JoinableQueue, network_retries: int, network_timeout: int ) -> None: Process.__init__(self) self.priority_queue_in = priority_queue_in self.queue_in = queue_in self.queue_out = queue_out # The object that will actually run the scan commands self._synchronous_scanner = SynchronousScanner(network_retries, network_timeout) def run(self) -> None: """The process will first complete tasks it gets from self.queue_in. Once it gets notified that all the tasks have been completed, it terminates. """ from sslyze.concurrent_scanner import PluginRaisedExceptionScanResult # Start processing task in the priority queue first current_queue_in = self.priority_queue_in while True: task = current_queue_in.get() # Grab a task from queue_in if task is None: # All tasks have been completed current_queue_in.task_done() if current_queue_in == self.priority_queue_in: # All high priority tasks have been completed; switch to low priority tasks current_queue_in = self.queue_in continue else: # All the tasks have been completed; pass on the sentinel to result_queue and exit self.queue_out.put(None) break server_info, scan_command = task try: result = self._synchronous_scanner.run_scan_command(server_info, scan_command) except Exception as e: # raise result = PluginRaisedExceptionScanResult(server_info, scan_command, e) # Send the result to queue_out self.queue_out.put(result) current_queue_in.task_done()
def check_tcp_service_for_ssl_protocol_cipher_support( self, org_uuid=None, service_uuid=None, ssl_protocol=None, scan_uuid=None ): """ Check to see what ciphers this TCP service supports for the given SSL protocol. :param org_uuid: The UUID of the organization to collect information on behalf of. :param service_uuid: The UUID of the network service to check for SSL support. :param ssl_protocol: The SSL protocol to check :param scan_uuid: The UUID of the network service scan that this SSL version check is associated with. :return: None """ ip_address, port, protocol = self.get_endpoint_information(service_uuid) server_info = ServerConnectivityInfo(hostname=ip_address, ip_address=ip_address, port=port) synchronous_scanner = SynchronousScanner() command = None if ssl_protocol == 'PROTOCOL_TLSv1' or ssl_protocol == 'PROTOCOL_TLS': command = Tlsv10ScanCommand() elif ssl_protocol == 'PROTOCOL_TLSv1_1': command = Tlsv11ScanCommand() elif ssl_protocol == 'PROTOCOL_TLSv1_2': command = Tlsv12ScanCommand() elif ssl_protocol == 'PROTOCOL_SSLv23': command = Sslv20ScanCommand() elif ssl_protocol == 'PROTOCOL_SSLv3': command = Sslv30ScanCommand() else: raise ValueError( "Not sure how to run scan command for SSL protocol of %s." % (ssl_protocol,) ) cipher_scan_result = synchronous_scanner.run_scan_command(server_info, command) ssl_support_record = SslSupportModel.from_database_model_uuid( uuid=scan_uuid, db_session=self.db_session, ssl_version=ssl_protocol, supported=True, ) ssl_support_record.accepted_ciphers = [ cipher.name for cipher in cipher_scan_result.accepted_cipher_list ] ssl_support_record.rejected_ciphers = [ cipher.name for cipher in cipher_scan_result.rejected_cipher_list ] ssl_support_record.errored_ciphers = [ cipher.name for cipher in cipher_scan_result.errored_cipher_list ] if cipher_scan_result.preferred_cipher is not None: ssl_support_record.preferred_cipher = cipher_scan_result.preferred_cipher.name else: ssl_support_record.preferred_cipher = None ssl_support_record.save(org_uuid)
def get_cert(site_json): hostname = get_hostname(site_json['hostname']) try: server_info = ServerConnectivityInfo(hostname=hostname) server_info.test_connectivity_to_server() command = CertificateInfoScanCommand() synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command(server_info, command) except ServerConnectivityError: return None return scan_result
def get_cert_alt_names(host, port=443): try: server_tester = ServerConnectivityTester(hostname=host, port=port, tls_wrapped_protocol=TlsWrappedProtocolEnum.HTTPS) server_info = server_tester.perform() except ServerConnectivityError: print("Impossible to connect") sys.exit(1) command = CertificateInfoScanCommand() synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command(server_info, command) cert = scan_result.verified_certificate_chain[0] subj_alt_names = [] san_ext = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME) subj_alt_names = san_ext.value.get_values_for_type(DNSName) return subj_alt_names
def scan(name, ip, view, suite): """ Two inputs: web site name and ip TODO: extend this to include port """ try: server_tester = ServerConnectivityTester( hostname=name, ip_address=ip, port=443, tls_wrapped_protocol=TlsWrappedProtocolEnum.HTTPS) # This line checks to see if the host is online server_info = server_tester.perform() ip = server_info.ip_address # Could not establish an SSL connection to the server except ConnectionToServerTimedOut: error = results.set_error(f'{name}', "Connection to TCP 443 timed-out") return error except ServerConnectivityError: error = results.set_error(f'{name}', "Unknown Error") return error # Create a new results dictionary scan_output = results.new() # I hash the combination of hostname and ip for tracking key = md5((f'{name}' + ip).encode("utf-8")).hexdigest() results.set_result(scan_output, "MD5", key) results.set_result(scan_output, "Hostname", f'{name}') results.set_result(scan_output, "IP", ip) results.set_result(scan_output, "View", view) for suite in ciphersuites.get(suite): synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command(server_info, suite) for cipher in scan_result.accepted_cipher_list: results.set_ciphers(scan_output, { "Version": cipher.ssl_version.name, "Cipher": cipher.name }) if len(scan_output["Results"]) == 0: results.set_result(scan_output, "Results", "No Policy Violations") return scan_output
def scan(name, ip, port, view, suite): """ Five inputs: web site name, ip, port split-dns view, and cipher suite """ try: server_tester = ServerConnectivityTester( hostname=name, ip_address=ip, port=port, tls_wrapped_protocol=TlsWrappedProtocolEnum.HTTPS) # This line checks to see if the host is online server_info = server_tester.perform() ip = server_info.ip_address # Could not establish an SSL connection to the server except ConnectionToServerTimedOut: raise ConnectionError('Connection Timeout', ERROR_MSG_CONNECTION_TIMEOUT(name, port)) except ServerConnectivityError: raise ConnectionError('Unknow Connection Error', ERROR_MSG_UNKNOWN_CONNECTION(name, port)) # Create a new results dictionary scan_output = results.new() # I hash the combination of hostname and ip for tracking key = md5((f'{name}' + ip).encode("utf-8")).hexdigest() results.set_result(scan_output, "MD5", key) results.set_result(scan_output, "Hostname", f'{name}') results.set_result(scan_output, "IP", ip) results.set_result(scan_output, "View", view) for suite in ciphersuites.get(suite): synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command(server_info, suite) for cipher in scan_result.accepted_cipher_list: results.set_ciphers(scan_output, { "Version": cipher.ssl_version.name, "Cipher": cipher.name }) if len(scan_output["Results"]) == 0: results.set_result(scan_output, "Results", "No Policy Violations") return scan_output
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 concurrent_scan(site_info): # Setup the server to scan and ensure it is online/reachable server_info = server_connectivity_tester(site_info.name) if server_info: synchronous_scanner = SynchronousScanner() cert_info_plugin = CertificateInfoPlugin() plugin_result = cert_info_plugin.process_task( server_info, CertificateInfoScanCommand()) #not plugin_result.verified_certificate_chain or #not plugin_result.leaf_certificate_subject_matches_hostname some sites' certs CN is with "www." so the result here is false if plugin_result.verified_certificate_chain and site_info.name not in str( plugin_result.verified_certificate_chain[0].subject): site_info.cert_trusted = "False" print("not trusted: " + site_info.name) print(plugin_result.__dict__) # elif not plugin_result.verified_certificate_chain: # site_info.cert_trusted = "False" # print("not trusted: " + site_info.name) # print(plugin_result.__dict__) else: site_info.cert_trusted = "True" scan_result1 = synchronous_scanner.run_scan_command( server_info, Sslv20ScanCommand()) if len(scan_result1.accepted_cipher_list) > 0: site_info.sslv2 = "True" scan_result2 = synchronous_scanner.run_scan_command( server_info, Sslv30ScanCommand()) if len(scan_result2.accepted_cipher_list) > 0: site_info.sslv3 = "True" scan_result3 = synchronous_scanner.run_scan_command( server_info, Tlsv10ScanCommand()) if len(scan_result3.accepted_cipher_list) > 0: site_info.tlsv1 = "True" scan_result4 = synchronous_scanner.run_scan_command( server_info, Tlsv11ScanCommand()) if len(scan_result4.accepted_cipher_list) > 0: site_info.tlsv11 = "True" scan_result5 = synchronous_scanner.run_scan_command( server_info, Tlsv12ScanCommand()) if len(scan_result5.accepted_cipher_list) > 0: site_info.tlsv12 = "True" scan_result6 = synchronous_scanner.run_scan_command( server_info, Tlsv13ScanCommand()) if len(scan_result6.accepted_cipher_list) > 0: site_info.tlsv13 = "True" recheck_cert(site_info)
def demo_synchronous_scanner(): # Run one scan command to list the server's TLS 1.0 cipher suites try: server_tester = ServerConnectivityTester( hostname='smtp.gmail.com', port=587, tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_SMTP ) print(f'\nTesting connectivity with {server_tester.hostname}:{server_tester.port}...') server_info = server_tester.perform() except ServerConnectivityError as e: # Could not establish an SSL connection to the server raise RuntimeError(f'Could not connect to {e.server_info.hostname}: {e.error_message}') command = Tlsv10ScanCommand() synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command(server_info, command) for cipher in scan_result.accepted_cipher_list: print(f' {cipher.name}')
def test_ssl_server_headers(self, hostname, port=443): ''' Uses the ServerConnectivityTester to identify host headers specific to TLS/SSL implementations to identify apparent security flaws with SSL/TLS implementations at the web server level. Currently, we can enumerate HSTS and Expect-CT Headers. HPKP is available, but is not being included because its being deprecated by Chrome. | test ssl server headers | hostname | port (optional | ''' try: tester = ServerConnectivityTester(hostname=hostname, port=port) server_info = tester.perform() scanner = SynchronousScanner() result = scanner.run_scan_command(server_info, HttpHeadersScanCommand()) logger.info("Test for HSTS Header") if result.hsts_header: preload = result.hsts_header.preload include_subdomains = result.hsts_header.include_subdomains max_age = result.hsts_header.max_age logger.info( "\tHSTS Header with Preload: {}, Include Subdomains: {} and max_age: {}" .format(preload, include_subdomains, max_age)) else: logger.warn("\tNo HSTS Header found") logger.info("Test for Expect-CT Header") if result.expect_ct_header: logger.info("\tExpect-CT Header found: {}".format( result.expect_ct_header)) else: logger.warn("\tNo Expect-CT Header found.") except ServerConnectivityError as e: logger.error('Error when trying to connect to {}: {}'.format( e.server_info.hostname, e.error_message))
def get_cert(site_json): hostname = get_hostname(site_json['hostname']) try: server_tester = ServerConnectivityTester(hostname=hostname) server_info = server_tester.perform() command = CertificateInfoScanCommand() synchronous_scanner = SynchronousScanner() scan_result = synchronous_scanner.run_scan_command( server_info, command) except sslyze.server_connectivity_tester.ServerNotReachableError: return None except sslyze.server_connectivity_tester.ServerConnectivityError: # plugin has very little documentation, keeping this here for now return None except OSError: return None except RuntimeError: return None except ValueError: return None return scan_result
def handler(event, context): """Handler for all Lambda events The event parameter is a dictionary containing the following keys and value types: * hostname - A string containing the hostname to be scanned. For example, "dhs.gov". * port - An integer specifying the port to be scanned. If omitted then the default value of 443 is used. * timeout - An integer denoting the number of seconds to wait when creating a connection. If omitted then the default value of 5 is used. * starttls_smtp - A boolean value denoting whether to try to use STARTTLS after connecting to an SMTP server. This option should be True is connecting to an SMTP host and otherwise False. If omitted then the default value of False is used. * scan_tlsv10 - A boolean value denoting whether to scan for TLS version 1.0 ciphers. If omitted then the default value of False is used. * scan_tlsv11 - A boolean value denoting whether to scan for TLS version 1.1 ciphers. If omitted then the default value of False is used. * scan_tlsv12 - A boolean value denoting whether to scan for TLS version 1.2 ciphers. If omitted then the default value of False is used. * scan_tlsv13 - A boolean value denoting whether to scan for TLS version 1.3 ciphers. If omitted then the default value of False is used. * scan_sslv20 - A boolean value denoting whether to scan for SSL version 2.0 ciphers. If omitted then the default value of False is used. * scan_sslv30 - A boolean value denoting whether to scan for SSL version 3.0 ciphers. If omitted then the default value of False is used. * scan_cert_info - A boolean value denoting whether to return certificate information. If omitted then the default value of False is used. Parameters ---------- event : dict A dictionary containing the scan parameters, as described above. context : LambdaContext The context for the Lambda function. See the corresponding AWS documentation for more details: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------- OrderedDict An OrderedDict specifying the fields of the trustymail.domain.Domain object resulting from the scan activity. """ logging.info('AWS Event was: {}'.format(event)) # Extract some variables from the event dictionary hostname = event['hostname'] port = event.get('port', 443) timeout = event.get('timeout', 5) starttls_smtp = event.get('starttls_smtp', False) scan_tlsv10 = event.get('scan_tlsv10', False) scan_tlsv11 = event.get('scan_tlsv11', False) scan_tlsv12 = event.get('scan_tlsv12', False) scan_tlsv13 = event.get('scan_tlsv13', False) scan_sslv20 = event.get('scan_sslv20', False) scan_sslv30 = event.get('scan_sslv30', False) scan_cert_info = event.get('scan_cert_info', False) # Initialize sslyze protocol = TlsWrappedProtocolEnum.PLAIN_TLS if starttls_smtp: protocol = TlsWrappedProtocolEnum.STARTTLS_SMTP try: server_tester = ServerConnectivityTester(hostname=hostname, port=port, tls_wrapped_protocol=protocol) server_info = server_tester.perform(network_timeout=timeout) except ServerConnectivityError: logging.error('Unable to connect to {}:{}'.format(hostname, port), exc_info=True, stack_info=True) return None except Exception: logging.error('Unable to connect to {}:{}'.format(hostname, port), exc_info=True, stack_info=True) return None scanner = SynchronousScanner(network_timeout=timeout) # Perform the scans if scan_tlsv10: logging.debug('Performing TLSv1.0 scan') tlsv10 = scanner.run_scan_command(server_info, Tlsv10ScanCommand()) logging.debug('tlsv10 = {}'.format(tlsv10)) if scan_tlsv11: logging.debug('Performing TLSv1.1 scan') tlsv11 = scanner.run_scan_command(server_info, Tlsv11ScanCommand()) logging.debug('tlsv11 = {}'.format(tlsv11)) if scan_tlsv12: logging.debug('Performing TLSv1.2 scan') tlsv12 = scanner.run_scan_command(server_info, Tlsv12ScanCommand()) logging.debug('tlsv12 = {}'.format(tlsv12)) if scan_tlsv13: logging.debug('Performing TLSv1.3 scan') tlsv13 = scanner.run_scan_command(server_info, Tlsv13ScanCommand()) logging.debug('tlsv13 = {}'.format(tlsv13)) if scan_sslv20: logging.debug('Performing SSLv2 scan') sslv20 = scanner.run_scan_command(server_info, Sslv20ScanCommand()) logging.debug('sslv20 = {}'.format(sslv20)) if scan_sslv30: logging.debug('Performing SSLv3 scan') sslv30 = scanner.run_scan_command(server_info, Sslv30ScanCommand()) logging.debug('sslv30 = {}'.format(sslv30)) if scan_cert_info: logging.debug('Performing certificate scan') cert_info = scanner.run_scan_command(server_info, CertificateInfoScanCommand()) logging.debug('cert_info = {}'.format(cert_info)) return None
def run_tests(self, tests): """ Run all enabled tests against the target url with the http or https port specified in the connect function. The Test objects within the tests array describes each test. The array should be changed according to the outcome, and if both ports have been specified and are supported the results must be merged before returned. :param tests: Array of Test objects :type tests: Array[Test...] :param targetURL: The target including address and port :type targetURL: str :return: Array of test objects :rtype: Array[Test...] """ # Perform tests synchronous_scanner = SynchronousScanner() # Run all plugins and do individual checks to see if the tests passed for i, plugin in enumerate(PluginsRepository._PLUGIN_CLASSES): if tests[i].enabled is True: plugin_passed = True # Individual check for cipher suites if plugin is OpenSslCipherSuitesPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_open_ssl_cihper_suites_secure( command, scan_result): # If one command fails the plugin fails as a whole plugin_passed = False break # Individual check for certificates elif plugin is CertificateInfoPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_certificate_info_secure(scan_result): plugin_passed = False break # Individual check for compression elif plugin is CompressionPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_compression_secure(scan_result): plugin_passed = False break # Individual check for fallback scsv elif plugin is FallbackScsvPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_fallback_scsv_secure(scan_result): plugin_passed = False break # Individual check for heartbleed elif plugin is HeartbleedPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_heartbleed_secure(scan_result): plugin_passed = False break # Individual check for secure http headers elif plugin is HttpHeadersPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_http_headers_secure(scan_result): plugin_passed = False break # Individual check for openssl css injection elif plugin is OpenSslCcsInjectionPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_openssl_css_injection_secure( scan_result): plugin_passed = False break # Individual check for session renegotiation elif plugin is SessionRenegotiationPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_session_renegotiation_secure( scan_result): plugin_passed = False break # Individual check for session resumption elif plugin is SessionResumptionPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_session_resumption_secure(scan_result): plugin_passed = False break # Individual check for the robot attack elif plugin is RobotPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_robot_secure(scan_result): plugin_passed = False break # Individual check for early data support elif plugin is EarlyDataPlugin: for command in plugin.get_available_commands(): scan_result = synchronous_scanner.run_scan_command( self.server_info, command()) if not self.is_early_data_secure(scan_result): plugin_passed = False break else: plugin_passed = None tests[i].passed = plugin_passed return tests
def attack_host(results, host_name): print("Running forward_secrecy_attack.py host name: {}".format(host_name)) expected_tls1_2_suites = ['TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384', 'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'] try: server_tester = ServerConnectivityTester( hostname=host_name, port=443, tls_wrapped_protocol=TlsWrappedProtocolEnum.HTTPS) server_info = server_tester.perform() except ServerConnectivityError as e: results.append(AttackResult('10.13', host_name, False, e)) return synchronous_scanner = SynchronousScanner() tls1_3_scan_result = synchronous_scanner.run_scan_command(server_info, Tlsv13ScanCommand()) tls1_3_accepted_ciphers = tls1_3_scan_result.accepted_cipher_list tls1_2_scan_result = synchronous_scanner.run_scan_command(server_info, Tlsv12ScanCommand()) tls1_2_accepted_ciphers = tls1_2_scan_result.accepted_cipher_list tls1_2_accepted_cipher_names = cipher_list_to_string_list(tls1_2_accepted_ciphers) tls1_1_scan_result = synchronous_scanner.run_scan_command(server_info, Tlsv11ScanCommand()) tls1_1_accepted_ciphers = tls1_1_scan_result.accepted_cipher_list tls1_0_scan_result = synchronous_scanner.run_scan_command(server_info, Tlsv10ScanCommand()) tls1_0_accepted_ciphers = tls1_0_scan_result.accepted_cipher_list ssl3_0_scan_result = synchronous_scanner.run_scan_command(server_info, Sslv30ScanCommand()) ssl3_0_accepted_ciphers = ssl3_0_scan_result.accepted_cipher_list ssl2_0_scan_result = synchronous_scanner.run_scan_command(server_info, Sslv20ScanCommand()) ssl2_0_accepted_ciphers = ssl2_0_scan_result.accepted_cipher_list # Determine result result = list_equal(tls1_2_accepted_cipher_names, expected_tls1_2_suites) and \ len(tls1_3_accepted_ciphers) == 0 and \ len(tls1_1_accepted_ciphers) == 0 and \ len(tls1_0_accepted_ciphers) == 0 and \ len(ssl3_0_accepted_ciphers) == 0 and \ len(ssl3_0_accepted_ciphers) == 0 # Build details details = '' if len(tls1_3_accepted_ciphers) != 0: details += 'TLS 1.3 should not be supported ' if not list_equal(tls1_2_accepted_cipher_names, expected_tls1_2_suites): details += ' TLS 1.2 supporting ' + str(tls1_2_accepted_cipher_names) \ + ', should only be ' + str(expected_tls1_2_suites) + ' ' if len(tls1_1_accepted_ciphers) != 0: details += 'TLS 1.1 should not be supported ' if len(tls1_0_accepted_ciphers) != 0: details += 'TLS 1.0 should not be supported ' if len(ssl3_0_accepted_ciphers) != 0: details += 'SSL 3.0 should not be supported ' if len(ssl2_0_accepted_ciphers) != 0: details += 'SSL 2.0 should not be supported ' if details == '': details = 'Cipher suites are correct' results.append(AttackResult('forward_secrecy', host_name, result, details)) # attack_host([], 'google.co.uk')
def ssltlsscan(web): target = web.split('//')[1] print(R + '\n ===============================') print(R + ' S S L E N U M E R A T I O N') print(R + ' ===============================\n') print(GR + ' [*] Testing server SSL status...') try: req = requests.get('https://' + target) print(G + ' [+] SSL Working Properly...') time.sleep(0.6) print(O + " [!] Running SSL Enumeration...\n") try: server_tester = ServerConnectivityTester(hostname=target) server_info = server_tester.perform() scanner = SynchronousScanner() command = Tlsv10ScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + " [+] Available TLS v1.0 Ciphers:") for cipher in scan_result.accepted_cipher_list: print(C + ' {}'.format(cipher.name)) print('') command = Tlsv11ScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + " [+] Available TLS v1.1 Ciphers:") for cipher in scan_result.accepted_cipher_list: print(C + ' {}'.format(cipher.name)) print('') command = Tlsv12ScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + " [+] Available TLS v1.2 Ciphers:") for cipher in scan_result.accepted_cipher_list: print(C + ' {}'.format(cipher.name)) print('') command = CertificateInfoScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + ' [+] Certificate Information:') for entry in scan_result.as_text(): if entry != '': if 'certificate information' in entry.lower(): pass elif ':' in entry: print(GR + ' [+] ' + entry.strip().split(':', 1)[0].strip() + ' : ' + C + entry.strip().split(':', 1)[1].strip()) else: print(O + '\n [+] ' + entry.strip()) print('') command = HttpHeadersScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + ' [+] HTTP Results:') for entry in scan_result.as_text(): if 'http security' not in entry.strip().lower( ) and entry != '': if '-' in entry: print(GR + ' [+] ' + entry.split('-', 1)[0].strip() + ' - ' + C + entry.split('-', 1)[1].strip()) elif ':' in entry: print(GR + ' [+] ' + entry.strip().split(':', 1)[0].strip() + ' : ' + C + entry.strip().split(':', 1)[1].strip()) else: print(O + '\n [+] ' + entry.strip()) print('') except Exception as e: print(R + ' [-] Unhandled SSL Runtime Exception : ' + str(e)) pass except requests.exceptions.SSLError as e: print(R + ' [-] Distant Server SSL not working : ' + str(e)) print(G + ' [+] SSlScan Module Completed!')
def main(): if len(sys.argv) < 2: print("Error: please provide a domain") exit(-1) hostname = sys.argv[1] """ Testing connectivity to the server """ try: server_info = ServerConnectivityInfo(hostname) server_info.test_connectivity_to_server() print( "[*] Connection established. \n[.] Starting tests on {} \n".format( hostname)) except ServerConnectivityError as e: raise RuntimeError("Error when connecting to {}: {}".format( hostname, e.error_msg)) scanner = SynchronousScanner() """ Creating an output file """ output = open("/root/PycharmProjects/SSL-TLS-Tool/output/" + hostname, "w") output.write("##############################################\n") output.write("Output result for host: {}\n".format(hostname)) output.write("Start {}\n".format(datetime.datetime.now())) output.write("##############################################\n\n") """ Certificate: """ scan_result = scanner.run_scan_command(server_info, CertificateInfoScanCommand()) for e in scan_result.as_text(): output.write(e + "\n") """ Protocols and Ciphers Suits: """ run_command(scanner, server_info, Tlsv10ScanCommand(), output) run_command(scanner, server_info, Tlsv11ScanCommand(), output) run_command(scanner, server_info, Tlsv12ScanCommand(), output) run_command(scanner, server_info, Sslv20ScanCommand(), output) run_command(scanner, server_info, Sslv30ScanCommand(), output) """ Testing vulnerabilities: """ run_command(scanner, server_info, DrownScanCommand(), output) run_command(scanner, server_info, PoodleSslScanCommand(), output) run_command(scanner, server_info, HeartbleedScanCommand(), output) run_command(scanner, server_info, OpenSslCcsInjectionScanCommand(), output) run_command(scanner, server_info, CompressionScanCommand(), output) run_command(scanner, server_info, FallbackScsvScanCommand(), output) run_command(scanner, server_info, SessionRenegotiationScanCommand(), output) run_command(scanner, server_info, SessionResumptionSupportScanCommand(), output) run_command(scanner, server_info, SessionResumptionRateScanCommand(), output) """ Closing """ output.close() print("\n[*] Check output file for more details") print("[*] Test completed!")
class TLSProfiler: PROFILES_URL = "https://ssl-config.mozilla.org/guidelines/5.4.json" PROFILES = None SCT_REQUIRED_DATE = datetime( year=2018, month=4, day=1 ) # SCTs are required after this date, see https://groups.google.com/a/chromium.org/forum/#!msg/ct-policy/sz_3W_xKBNY/6jq2ghJXBAAJ SCAN_COMMANDS = { "SSLv2": Sslv20ScanCommand, "SSLv3": Sslv30ScanCommand, "TLSv1": Tlsv10ScanCommand, "TLSv1.1": Tlsv11ScanCommand, "TLSv1.2": Tlsv12ScanCommand, "TLSv1.3": Tlsv13ScanCommand, } def __init__( self, domain: str, target_profile: str, ca_file: Optional[str] = None, cert_expire_warning: int = 15, ) -> None: """ :param domain: :param target_profile: One of [old|intermediate|modern] :param ca_file: Path to a trusted custom root certificates in PEM format. :param cert_expire_warning: A warning is issued if the certificate expires in less days than specified. """ self.ca_file = ca_file self.cert_expire_warning = cert_expire_warning if TLSProfiler.PROFILES is None: TLSProfiler.PROFILES = requests.get(self.PROFILES_URL).json() log.info( f"Loaded version {TLSProfiler.PROFILES['version']} of the Mozilla TLS configuration recommendations." ) self.target_profile = TLSProfiler.PROFILES["configurations"][ target_profile] self.target_profile["tls_curves"] = self._get_equivalent_curves( self.target_profile["tls_curves"]) self.target_profile[ "certificate_curves"] = self._get_equivalent_curves( self.target_profile["certificate_curves"]) self.scanner = SynchronousScanner() try: server_tester = ServerConnectivityTester(hostname=domain, ) log.info( f"Testing connectivity with {server_tester.hostname}:{server_tester.port}..." ) self.server_info = server_tester.perform() self.server_error = None except ServerConnectivityError as e: # Could not establish an SSL connection to the server log.warning( f"Could not connect to {e.server_info.hostname}: {e.error_message}" ) self.server_error = e.error_message self.server_info = None def _get_equivalent_curves(self, curves: List[str]) -> Optional[List[str]]: if not curves: return None curves_tmp = curves.copy() for curve in curves: for curve_tuple in _EQUIVALENT_CURVES: if curve == curve_tuple[0]: curves_tmp.append(curve_tuple[1]) elif curve == curve_tuple[1]: curves_tmp.append(curve_tuple[0]) return curves_tmp def run(self) -> TLSProfilerResult: if self.server_info is None: return ( validation_errors, cert_profile_error, cert_warnings, pub_key_type, ) = self._check_certificate() hsts_errors = self._check_hsts_age() self._scan_supported_ciphers_and_protocols() profile_errors = self._check_server_matches_profile(pub_key_type) vulnerability_errors = self._check_vulnerabilities() return TLSProfilerResult( validation_errors, cert_warnings, profile_errors + hsts_errors + cert_profile_error, vulnerability_errors, ) def _scan(self, command: PluginScanCommand): return self.scanner.run_scan_command(self.server_info, command) def _scan_supported_ciphers_and_protocols(self): supported_ciphers = dict() supported_protocols = [] supported_key_exchange = [] supported_curves = [] server_preferred_order = dict() for name, command in self.SCAN_COMMANDS.items(): log.debug(f"Testing protocol {name}") result = self._scan(command()) # type: CipherSuiteScanResult ciphers = [ cipher.openssl_name for cipher in result.accepted_cipher_list ] supported_ciphers[name] = ciphers key_exchange = [(cipher.dh_info, cipher.openssl_name) for cipher in result.accepted_cipher_list if cipher.dh_info] supported_key_exchange.extend(key_exchange) supported_curves.extend(result.supported_curves) server_preferred_order[name] = result.server_cipher_preference if len(ciphers): supported_protocols.append(name) self.supported_ciphers = supported_ciphers self.supported_protocols = set(supported_protocols) self.supported_key_exchange = (supported_key_exchange ) # type: List[(KeyExchangeInfo, str)] self.supported_curves = set(supported_curves) self.server_preferred_order = server_preferred_order def _check_pub_key_supports_cipher(self, cipher: str, pub_key_type: str) -> bool: """ Checks if cipher suite works with the servers certificate (for TLS 1.2 and older). Source: https://wiki.mozilla.org/Security/Server_Side_TLS, https://tools.ietf.org/html/rfc5246#appendix-A.5 :param cipher: OpenSSL cipher name :param pub_key_type: :return: """ if "anon" in cipher: return True elif pub_key_type in cipher: return True elif pub_key_type == "RSA" and "ECDSA" not in cipher and "DSS" not in cipher: return True return False def _check_protocols(self) -> List[str]: errors = [] # match supported TLS versions allowed_protocols = set(self.target_profile["tls_versions"]) illegal_protocols = self.supported_protocols - allowed_protocols missing_protocols = allowed_protocols - self.supported_protocols for protocol in illegal_protocols: errors.append(f"must not support {protocol}") for protocol in missing_protocols: errors.append(f"must support {protocol}") return errors def _check_cipher_suites_and_order(self, pub_key_type: str) -> List[str]: errors = [] # match supported cipher suite order for each supported protocol all_supported_ciphers = [] for protocol, supported_ciphers in self.supported_ciphers.items(): all_supported_ciphers.extend(supported_ciphers) if protocol in self.supported_protocols: allowed_ciphers = self.target_profile["ciphers"]["openssl"] # check if the server chooses the cipher suite if (self.target_profile["server_preferred_order"] and not self.server_preferred_order[protocol]): errors.append( f"server must choose the cipher suite, not the client (Protocol {protocol})" ) # check if the client chooses the cipher suite if (not self.target_profile["server_preferred_order"] and self.server_preferred_order[protocol]): errors.append( f"client must choose the cipher suite, not the server (Protocol {protocol})" ) # check whether the servers preferred cipher suite preference is correct if (self.target_profile["server_preferred_order"] and self.server_preferred_order[protocol] and not utils.check_cipher_order( allowed_ciphers, supported_ciphers)): errors.append( f"server has the wrong cipher suites order (Protocol {protocol})" ) # find cipher suites that should not be supported allowed_ciphers = (self.target_profile["ciphersuites"] + self.target_profile["ciphers"]["openssl"]) illegal_ciphers = set(all_supported_ciphers) - set(allowed_ciphers) for cipher in illegal_ciphers: errors.append(f"must not support {cipher}") # find missing cipher suites missing_ciphers = set(allowed_ciphers) - set(all_supported_ciphers) for cipher in missing_ciphers: if self._check_pub_key_supports_cipher(cipher, pub_key_type): errors.append(f"must support {cipher}") return errors def _check_ecdh_and_dh(self) -> List[str]: errors = [] # match DHE and ECDHE parameters for (key_info, cipher) in self.supported_key_exchange: if (isinstance(key_info, DhKeyExchangeInfo) and not self.target_profile["dh_param_size"]): errors.append(f"must not support finite field DH key exchange") break elif (isinstance(key_info, DhKeyExchangeInfo) and key_info.key_size != self.target_profile["dh_param_size"]): errors.append( f"wrong DHE parameter size {key_info.key_size} for cipher {cipher}" f", should be {self.target_profile['dh_param_size']}") # match ECDH curves used for key exchange allowed_curves = self.target_profile["tls_curves"] for curve in self.supported_curves: if curve not in allowed_curves: errors.append( f"must not support ECDH curve {curve} for key exchange") return errors def _check_server_matches_profile(self, pub_key_type: str): errors = [] errors.extend(self._check_protocols()) errors.extend(self._check_cipher_suites_and_order(pub_key_type)) errors.extend(self._check_ecdh_and_dh()) return errors def _cert_type_string(self, pub_key) -> str: if isinstance(pub_key, rsa.RSAPublicKey): return "RSA" elif isinstance(pub_key, ec.EllipticCurvePublicKey): return "ECDSA" elif isinstance(pub_key, ed25519.Ed25519PublicKey): return "ED25519" elif isinstance(pub_key, ed448.Ed448PublicKey): return "ED448" elif isinstance(pub_key, dsa.DSAPublicKey): return "DSA" return "" def _check_certificate_properties( self, certificate: Certificate, ocsp_stapling: bool) -> Tuple[List[str], List[str], str]: errors = [] warnings = [] # check certificate lifespan lifespan = certificate.not_valid_after - certificate.not_valid_before if self.target_profile["maximum_certificate_lifespan"] < lifespan.days: errors.append( f"certificate lifespan too long (is {lifespan.days}, " f"should be less than {self.target_profile['maximum_certificate_lifespan']})" ) current_time = datetime.now() days_before_expire = certificate.not_valid_after - current_time if days_before_expire.days < self.cert_expire_warning: warnings.append( f"Certificate expires in {days_before_expire.days} days") # check certificate public key type pub_key_type = self._cert_type_string(certificate.public_key()) if pub_key_type.lower( ) not in self.target_profile["certificate_types"]: errors.append(f"wrong certificate type ({pub_key_type})") # check key property pub_key = certificate.public_key() if (isinstance(pub_key, rsa.RSAPublicKey) and self.target_profile["rsa_key_size"] and pub_key.key_size != self.target_profile["rsa_key_size"]): errors.append( f"RSA certificate has wrong key size (is {pub_key.key_size}, " f"should be {self.target_profile['rsa_key_size']})") elif (isinstance(pub_key, ec.EllipticCurvePublicKey) and self.target_profile["certificate_curves"] and pub_key.curve.name not in self.target_profile["certificate_curves"]): errors.append( f"ECDSA certificate uses wrong curve " f"(is {pub_key.curve.name}, should be one of {self.target_profile['certificate_curves']})" ) # check certificate signature if (certificate.signature_algorithm_oid._name not in self.target_profile["certificate_signatures"]): errors.append(f"certificate has a wrong signature") # check if ocsp stabling is supported if ocsp_stapling != self.target_profile["ocsp_staple"]: errors.append(f"OCSP stapling must be supported") return errors, warnings, pub_key_type def _check_certificate( self) -> Tuple[List[str], List[str], List[str], str]: result = self._scan(CertificateInfoScanCommand( ca_file=self.ca_file)) # type: CertificateInfoScanResult validation_errors = [] certificate = result.received_certificate_chain[0] ( profile_errors, cert_warnings, pub_key_type, ) = self._check_certificate_properties(certificate, result.ocsp_response_is_trusted) for r in result.path_validation_result_list: if not r.was_validation_successful: validation_errors.append( f"validation not successful: {r.verify_string} (trust store {r.trust_store.name})" ) if result.path_validation_error_list: validation_errors = (fail.error_message for fail in result.path_validation_error_list) validation_errors.append( f'Validation failed: {", ".join(validation_errors)}') if not result.leaf_certificate_subject_matches_hostname: validation_errors.append( f"Leaf certificate subject does not match hostname!") if not result.received_chain_has_valid_order: validation_errors.append(f"Certificate chain has wrong order.") if result.verified_chain_has_sha1_signature: validation_errors.append(f"SHA1 signature found in chain.") if result.verified_chain_has_legacy_symantec_anchor: validation_errors.append( f"Symantec legacy certificate found in chain.") sct_count = result.leaf_certificate_signed_certificate_timestamps_count if sct_count < 2 and certificate.not_valid_before >= self.SCT_REQUIRED_DATE: validation_errors.append( f"Certificates issued on or after 2018-04-01 need certificate transparency, " f"i.e., two signed SCTs in certificate. Leaf certificate only has {sct_count}." ) if len(validation_errors) == 0: log.debug(f"Certificate is ok") else: log.debug(f"Error validating certificate") for error in validation_errors: log.debug(f" → {error}") return validation_errors, profile_errors, cert_warnings, pub_key_type def _check_vulnerabilities(self): errors = [] result = self._scan( HeartbleedScanCommand()) # type: HeartbleedScanResult if result.is_vulnerable_to_heartbleed: errors.append(f"Server is vulnerable to Heartbleed attack") result = self._scan(OpenSslCcsInjectionScanCommand() ) # type: OpenSslCcsInjectionScanResult if result.is_vulnerable_to_ccs_injection: errors.append( f"Server is vulnerable to OpenSSL CCS Injection (CVE-2014-0224)" ) result = self._scan(RobotScanCommand()) # type: RobotScanResult if result.robot_result_enum in [ RobotScanResultEnum.VULNERABLE_WEAK_ORACLE, RobotScanResultEnum.VULNERABLE_STRONG_ORACLE, ]: errors.append(f"Server is vulnerable to ROBOT attack.") return errors def _check_hsts_age(self) -> List[str]: result = self._scan( HttpHeadersScanCommand()) # type: HttpHeadersScanResult errors = [] if result.strict_transport_security_header: if (result.strict_transport_security_header.max_age < self.target_profile["hsts_min_age"]): errors.append( f"wrong HSTS age (is {result.strict_transport_security_header.max_age}, " f"should be at least {self.target_profile['hsts_min_age']})" ) else: errors.append(f"HSTS header not set") return errors
def scan(self, protocol='all'): """ Scans target for all policy checks :param protocol: (str) Default: all; It can also be one of the keys of self.policies :returns: (array) of (dict) [] empty array when there is no service running in host:port. [ { "protocol":"ssl2.0", "is_supported":True, "is_allowed":False, "has_passed":False, "ciphers_supported":[TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA] "problematic_ciphers":[TLS_RSA_WITH_AES_128_CBC_SHA] }, ... ] """ # Define if one of all policies will be checked policies = {} if protocol == 'all': policies = self.policies elif protocol in list(self.policies): policies[protocol] = self.policies[protocol] else: raise ValueError('Invalid protocol selected for scanning.') # Try handshake server_info = self.connect() if server_info is None: message = "SSL Handshake failed. Checking service port for "\ "unencrypted channels." print(message) if self.check_service(): return [{ "protocol": 'unencrypted', "is_supported": True, "is_allowed": False, "has_passed": False, "ciphers_supported": [], "problematic_ciphers": [], }] else: return [] # Retrieve definitions from class to iterate print("Initializing synchronous SSL scan.") synchronous_scanner = SynchronousScanner() results = [] for protocol, policy in policies.items(): print("Checking {} protocol support".format(protocol)) is_allowed = policy['allowed'] command = policy['command'] scan_result = synchronous_scanner.run_scan_command( server_info, command) # Detect cipher support ciphers = scan_result.accepted_cipher_list ciphers_supported = [] problematic_ciphers = [] for cipher in ciphers: name = cipher.name ciphers_supported.append(name) if re.match(r'.*(SHA)$', name) or \ (name.find('RSA_WITH_AES') == -1): problematic_ciphers.append(name) # Determine support based on ciphers if len(ciphers_supported) > 0: is_supported = True else: is_supported = False # Determine if the check has passed for this protocol if not is_allowed and not is_supported: has_passed = True # don't care about cipher suite list elif not is_allowed and is_supported: has_passed = False elif is_allowed and not is_supported: has_passed = True elif is_allowed and is_supported: if len(problematic_ciphers) > 0: has_passed = False else: has_passed = True else: print('One should never get here!') raise OSError('Program logic error.') result = { "protocol": protocol, "is_supported": is_supported, "is_allowed": is_allowed, "has_passed": has_passed, "ciphers_supported": ciphers_supported, "problematic_ciphers": problematic_ciphers, } results.insert(0, result) print("SSL scan completed.") return results
def ssltlsscan(web): global name name = targetname(web) global lvl2 lvl2 = inspect.stack()[0][3] global module module = "ScanANDEnum" global lvl1 lvl1 = "Scanning & Enumeration" global lvl3 lvl3 = "" target = web.split('//')[1] #print(R+'\n ===============================') #print(R+' S S L E N U M E R A T I O N') #print(R+' ===============================\n') from core.methods.print import pscan pscan("ssl enumeration") print(GR + ' [*] Testing server SSL status...') try: req = requests.get('https://' + target) print(G + ' [+] SSL Working Properly...' + color.TR2 + C) time.sleep(0.6) print(C + " [!] Running SSL Enumeration...\n") try: server_tester = ServerConnectivityTester(hostname=target) server_info = server_tester.perform() scanner = SynchronousScanner() command = Tlsv10ScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + " [+] Available TLS v1.0 Ciphers:" + color.TR2 + C) for cipher in scan_result.accepted_cipher_list: print(C + ' {}'.format(cipher.name)) print('') data = "TLS 1.0 :> " + str(scan_result.accepted_cipher_list) save_data(database, module, lvl1, lvl2, lvl3, name, data) command = Tlsv11ScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + " [+] Available TLS v1.1 Ciphers:" + color.TR2 + C) for cipher in scan_result.accepted_cipher_list: print(C + ' {}'.format(cipher.name)) print('') data = "TLS 1.1 :> " + str(scan_result.accepted_cipher_list) save_data(database, module, lvl1, lvl2, lvl3, name, data) command = Tlsv12ScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + " [+] Available TLS v1.2 Ciphers:" + color.TR2 + C) for cipher in scan_result.accepted_cipher_list: print(C + ' {}'.format(cipher.name)) print('') data = "TLS 1.2 :> " + str(scan_result.accepted_cipher_list) save_data(database, module, lvl1, lvl2, lvl3, name, data) command = CertificateInfoScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + ' [+] Certificate Information:' + color.TR2 + C) for entry in scan_result.as_text(): if entry != '': if 'certificate information' in entry.lower(): pass elif ':' in entry: print(GR + ' [+] ' + entry.strip().split(':', 1)[0].strip() + ' : ' + C + entry.strip().split(':', 1)[1].strip()) else: print(C + '\n [+] ' + entry.strip()) print('') data = "Cert Info :> " + str(scan_result.as_text()) save_data(database, module, lvl1, lvl2, lvl3, name, data) command = HttpHeadersScanCommand() scan_result = scanner.run_scan_command(server_info, command) print(G + ' [+] HTTP Results:' + C + color.TR2 + C) for entry in scan_result.as_text(): if 'http security' not in entry.strip().lower( ) and entry != '': if '-' in entry: print(GR + ' [+] ' + entry.split('-', 1)[0].strip() + ' - ' + C + entry.split('-', 1)[1].strip()) elif ':' in entry: print(GR + ' [+] ' + entry.strip().split(':', 1)[0].strip() + ' : ' + C + entry.strip().split(':', 1)[1].strip()) else: print(C + '\n [+] ' + entry.strip()) print('') data = "HTTP :> " + str(scan_result.as_text()) save_data(database, module, lvl1, lvl2, lvl3, name, data) except Exception as e: print(R + ' [-] Unhandled SSL Runtime Exception : ' + str(e)) pass except requests.exceptions.SSLError as e: print(R + ' [-] Distant Server SSL not working : ' + str(e)) print(G + ' [+] SSlScan Module Completed!' + C + color.TR2 + C)
try: server_info = ServerConnectivityInfo( hostname=hostname, port=587, tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_SMTP) 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)) # Example 1: Run one scan command synchronously to list the server's TLS 1.0 cipher suites print('\nRunning one scan command synchronously...') synchronous_scanner = SynchronousScanner() command = Tlsv10ScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) # Example 2: Run multiple scan commands concurrently. It is of course much faster than the SynchronousScanner concurrent_scanner = ConcurrentScanner() # Queue some scan commands print('\nQueuing some commands...') concurrent_scanner.queue_scan_command(server_info, Tlsv12ScanCommand()) concurrent_scanner.queue_scan_command(server_info, SessionRenegotiationScanCommand()) concurrent_scanner.queue_scan_command(server_info, CertificateInfoScanCommand()) # Process the results
# Setup the server to scan and ensure it is online/reachable hostname = 'smtp.gmail.com' try: server_info = ServerConnectivityInfo(hostname=hostname, port=587, tls_wrapped_protocol=TlsWrappedProtocolEnum.STARTTLS_SMTP) 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)) # Example 1: Run one scan command synchronously to list the server's TLS 1.0 cipher suites print('\nRunning one scan command synchronously...') synchronous_scanner = SynchronousScanner() command = Tlsv10ScanCommand() scan_result = synchronous_scanner.run_scan_command(server_info, command) for cipher in scan_result.accepted_cipher_list: print(' {}'.format(cipher.name)) # Example 2: Run multiple scan commands concurrently. It is of course much faster than the SynchronousScanner concurrent_scanner = ConcurrentScanner() # Queue some scan commands print('\nQueuing some commands...') concurrent_scanner.queue_scan_command(server_info, Tlsv12ScanCommand()) concurrent_scanner.queue_scan_command(server_info, SessionRenegotiationScanCommand()) concurrent_scanner.queue_scan_command(server_info, CertificateInfoScanCommand()) # Process the results reneg_result = None