Esempio n. 1
0
def basic_example() -> None:
    # Define the server that you want to scan
    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup("www.google.com", 443)

    # Do connectivity testing to ensure SSLyze is able to connect
    try:
        server_info = ServerConnectivityTester().perform(server_location)
    except ConnectionToServerFailed as e:
        # Could not connect to the server; abort
        print(f"Error connecting to {server_location}: {e.error_message}")
        return

    # Then queue some scan commands for the server
    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_info=server_info, scan_commands={ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES},
    )
    scanner.start_scans([server_scan_req])

    # Then retrieve the results
    for server_scan_result in scanner.get_results():
        print(f"\nResults for {server_scan_result.server_info.server_location.hostname}:")

        # SSL 2.0 results
        ssl2_result = server_scan_result.scan_commands_results[ScanCommand.SSL_2_0_CIPHER_SUITES]
        print("\nAccepted cipher suites for SSL 2.0:")
        for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
            print(f"* {accepted_cipher_suite.cipher_suite.name}")

        # Certificate info results
        certinfo_result = server_scan_result.scan_commands_results[ScanCommand.CERTIFICATE_INFO]
        print("\nCertificate info:")
        for cert_deployment in certinfo_result.certificate_deployments:
            print(f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")
Esempio n. 2
0
def main() -> None:
    # First validate that we can connect to the servers we want to scan
    servers_to_scan = []
    for hostname in ["cloudflare.com", "google.com"]:
        server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
            hostname, 443)
        try:
            server_info = ServerConnectivityTester().perform(server_location)
            servers_to_scan.append(server_info)
        except ConnectionToServerFailed as e:
            print(
                f"Error connecting to {server_location.hostname}:{server_location.port}: {e.error_message}"
            )
            return

    scanner = Scanner()

    # Then queue some scan commands for each server
    for server_info in servers_to_scan:
        server_scan_req = ServerScanRequest(
            server_info=server_info,
            scan_commands={
                ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES
            },
        )
        scanner.queue_scan(server_scan_req)

    # Then retrieve the result of the scan commands for each server
    for server_scan_result in scanner.get_results():
        print(
            f"\nResults for {server_scan_result.server_info.server_location.hostname}:"
        )

        # Scan commands that were run with no errors
        try:
            ssl2_result = server_scan_result.scan_commands_results[
                ScanCommand.SSL_2_0_CIPHER_SUITES]
            print(f"\nAccepted cipher suites for SSL 2.0:")
            for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
                print(f"* {accepted_cipher_suite.cipher_suite.name}")
        except KeyError:
            pass

        try:
            certinfo_result = server_scan_result.scan_commands_results[
                ScanCommand.CERTIFICATE_INFO]
            print("\nCertificate info:")
            for cert_deployment in certinfo_result.certificate_deployments:
                print(
                    f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}"
                )
        except KeyError:
            pass

        # Scan commands that were run with errors
        for scan_command, error in server_scan_result.scan_commands_errors.items(
        ):
            print(
                f"\nError when running {scan_command}:\n{error.exception_trace}"
            )
Esempio n. 3
0
    def create(
        server_location: Optional[ServerNetworkLocation] = None,
        scan_commands: Optional[Set[ScanCommand]] = None,
        scan_commands_extra_arguments: Optional[
            ScanCommandsExtraArguments] = None,
    ) -> ServerScanRequest:
        final_server_location: Optional[ServerNetworkLocation]
        if server_location is None:
            final_server_location = ServerNetworkLocationViaDirectConnectionFactory.create(
            )
        else:
            final_server_location = server_location

        if scan_commands is None:
            final_scan_commands = {
                ScanCommand.CERTIFICATE_INFO, ScanCommand.ROBOT
            }
        else:
            final_scan_commands = scan_commands

        if scan_commands_extra_arguments is None:
            final_extra_args = ScanCommandsExtraArguments()
        else:
            final_extra_args = scan_commands_extra_arguments

        return ServerScanRequest(
            server_location=final_server_location,
            scan_commands=final_scan_commands,
            scan_commands_extra_arguments=final_extra_args,
        )
Esempio n. 4
0
    def test_error_client_certificate_needed(self):
        # Given a server that requires client authentication
        with LegacyOpenSslServer(client_auth_config=ClientAuthConfigEnum.REQUIRED) as server:

            # And a scan request for it that does NOT provide a client certificate
            scan_request = ServerScanRequest(
                server_location=ServerNetworkLocation(
                    hostname=server.hostname, ip_address=server.ip_address, port=server.port
                ),
                scan_commands={
                    # And the request has a scan command that cannot be completed without a client certificate
                    ScanCommand.HTTP_HEADERS,
                },
            )

            # When running the scan
            scanner = Scanner()
            scanner.queue_scans([scan_request])

            # It succeeds
            all_results = []
            for result in scanner.get_results():
                all_results.append(result)

        # And the right result was returned
        assert len(all_results) == 1

        # And the fact that a client certificate is needed was properly returned
        http_headers_result = all_results[0].scan_result.http_headers
        assert http_headers_result.status == ScanCommandAttemptStatusEnum.ERROR
        assert http_headers_result.error_reason == ScanCommandErrorReasonEnum.CLIENT_CERTIFICATE_NEEDED
        assert http_headers_result.error_trace
        assert http_headers_result.result is None
Esempio n. 5
0
    def initiateScan(self, commands):
        self.scanner = Scanner()
        serverScanReq = ServerScanRequest(
            server_info=self.serverInfo,
            scan_commands=commands,
        )
        self.scanner.queue_scan(serverScanReq)

        return self.scanner.get_results()
Esempio n. 6
0
    def test_default_values(self):
        # Given just a server location
        server_location = ServerNetworkLocation(hostname="www.google.com",
                                                port=443,
                                                ip_address="1.1.1.1")

        # When creating a scan request with the minimum set of arguments, it succeeds
        scan_request = ServerScanRequest(server_location=server_location)

        # And default values were generated
        assert scan_request.uuid
        assert scan_request.network_configuration
        assert len(scan_request.scan_commands) > 5
Esempio n. 7
0
def addScanRequests(scanner, servers_to_scan, commands):
    """
    Queue scan requests for each open server connections
    :param scanner: Scanner object which holds connections open for each IP
    :param servers_to_scan: list of open connections
    :param commands: set of string scan commands (like 'certificate_info' and 'tls_1_0_cipher_suites'
    :return: None
    """
    print('Queueing TLS scans (this might take a little while...)')
    for server_info in servers_to_scan:
        server_scan_req = ServerScanRequest(server_info=server_info,
                                            scan_commands=commands)
        scanner.queue_scan(server_scan_req)
Esempio n. 8
0
    def test_extra_arguments_but_no_corresponding_scan_command(self):
        # Given a server location
        server_location = ServerNetworkLocation(hostname="www.google.com",
                                                port=443,
                                                ip_address="1.1.1.1")

        # When trying to queue a scan for a server
        with pytest.raises(ValueError):
            ServerScanRequest(
                server_location=server_location,
                # With an extra argument for one command
                scan_commands_extra_arguments=ScanCommandsExtraArguments(
                    certificate_info=CertificateInfoExtraArgument(
                        custom_ca_file=Path(__file__))),
                # But that specific scan command was not queued
                scan_commands={ScanCommand.ROBOT},
            )
    def test_badssl_compliant_with_modern(self):
        # Given the scan results for a server that is compliant with the "modern" Mozilla config
        scanner = Scanner()
        scanner.queue_scans(
            [ServerScanRequest(server_location=ServerNetworkLocation(hostname="mozilla-modern.badssl.com"))]
        )
        server_scan_result = next(scanner.get_results())

        # When checking if the server is compliant with the Mozilla "modern" TLS config
        # It succeeds and the server is returned as compliant
        checker = MozillaTlsConfigurationChecker.get_default()
        checker.check_server(
            against_config=MozillaTlsConfigurationEnum.MODERN,
            server_scan_result=server_scan_result,
        )

        # And the server is returned as NOT compliant for the other Mozilla configs
        for mozilla_config in [MozillaTlsConfigurationEnum.OLD, MozillaTlsConfigurationEnum.INTERMEDIATE]:
            with pytest.raises(ServerNotCompliantWithMozillaTlsConfiguration):
                checker.check_server(against_config=mozilla_config, server_scan_result=server_scan_result)
Esempio n. 10
0
def scan_runner(seq,host):
    hostname=host.decode("utf-8")
    servers_to_scan = []
    server_location = None
    try:
        if r.hget(seq,"ipaddr"):
            server_location = ServerNetworkLocationViaDirectConnection(hostname, 443, r.hget(seq,"ipaddr").decode("utf-8"))
        else:
            server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(hostname, 443)
            r.hset(seq,"ipaddr",server_location.ip_address)
        #Initialize with hostname, port int and ip address str 
        #print(server_location)
    except Exception as e:
        return
    try:
        server_info = ServerConnectivityTester().perform(server_location)
        servers_to_scan.append(server_info)
    except ConnectionToServerFailed as e:
        return

    scanner = Scanner()

    # Then queue some scan commands for each server
    for server_info in servers_to_scan:
        server_scan_req = ServerScanRequest(
            server_info=server_info, scan_commands={ScanCommand.TLS_1_3_EARLY_DATA},
        )
        scanner.queue_scan(server_scan_req)

    # Then retrieve the result of the scan commands for each server
    for server_scan_result in scanner.get_results():
        try:
            if server_scan_result.scan_commands_results[ScanCommand.TLS_1_3_EARLY_DATA].supports_early_data:
                r.hset(seq,"early","TRUE")

        except KeyError:
            return
Esempio n. 11
0
def search_subject_alt_name(self, target):
  print("Searching for Subject Alt Names")
  try:
    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
      target, 443)

    # Do connectivity testing to ensure SSLyze is able to connect
    try:
      server_info = ServerConnectivityTester().perform(server_location)
    except ConnectionToServerFailed as e:
      # Could not connect to the server; abort
      print(f"Error connecting to {server_location}: {e.error_message}")
      return

    # Then queue some scan commands for the server
    scanner = Scanner()
    server_scan_req = ServerScanRequest(server_info=server_info, scan_commands={
      ScanCommand.CERTIFICATE_INFO}, )
    scanner.queue_scan(server_scan_req)
    # Then retrieve the results
    for server_scan_result in scanner.get_results():
      # Certificate info results
      certinfo_result = server_scan_result.scan_commands_results[
        ScanCommand.CERTIFICATE_INFO]

      # Direct object reference is pretty bad, but then again so is the crypto.x509 object implementation, so...
      cert_deployment = certinfo_result.certificate_deployments[0]
      chain = cert_deployment.received_certificate_chain[0]
      ext = chain.extensions.get_extension_for_oid(
        ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
      for entry in ext.value.get_values_for_type(x509.DNSName):
        if entry.strip() not in self.domains:
          self.domains.append(entry.strip())

  except Exception as e:
    self.handle_exception(e)
Esempio n. 12
0
def main() -> None:
    # Ensure the server is accessible on localhost
    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup("localhost", 443)
    server_info = ServerConnectivityTester().perform(server_location)

    if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.REQUIRED:
        raise RuntimeError(
            f"SSLyze did not detect that client authentication was required by the server:"
            f" {server_info.tls_probing_result.client_auth_requirement}."
        )

    # Queue all scan commands
    print("Starting scan.")
    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_info=server_info, scan_commands=ScanCommandsRepository.get_all_scan_commands(),
    )
    scanner.queue_scan(server_scan_req)

    # Retrieve the result
    for server_scan_result in scanner.get_results():
        successful_cmds_count = len(server_scan_result.scan_commands_results)
        errored_cmds_count = len(server_scan_result.scan_commands_errors)
        print(f"Finished scan with {successful_cmds_count} results and {errored_cmds_count} errors.")

        # Crash if any scan commands triggered an error that's not due to client authentication being required
        triggered_unexpected_error = False
        for scan_command, error in server_scan_result.scan_commands_errors.items():
            if error.reason != ScanCommandErrorReasonEnum.CLIENT_CERTIFICATE_NEEDED:
                triggered_unexpected_error = True
                print(f"\nError when running {scan_command}: {error.reason.name}.")
                if error.exception_trace:
                    exc_trace = ""
                    for line in error.exception_trace.format(chain=False):
                        exc_trace += f"       {line}"
                    print(exc_trace)

                print("\n")

        if triggered_unexpected_error:
            raise RuntimeError("The scan triggered unexpected errors")
        else:
            # The CLIENT_CERTIFICATE_NEEDED errors are expected, because of how Apache2 is configured
            print("OK: Triggered CLIENT_CERTIFICATE_NEEDED errors only.")

        # Crash if SSLyze didn't complete the scan commands that are supposed to work even when we don't provide a
        # client certificate
        expected_scan_command_results = {
            ScanCommand.TLS_1_3_CIPHER_SUITES,
            ScanCommand.TLS_1_2_CIPHER_SUITES,
            ScanCommand.TLS_1_1_CIPHER_SUITES,
            ScanCommand.TLS_1_0_CIPHER_SUITES,
            ScanCommand.SSL_3_0_CIPHER_SUITES,
            ScanCommand.SSL_2_0_CIPHER_SUITES,
            ScanCommand.OPENSSL_CCS_INJECTION,
            ScanCommand.HEARTBLEED,
            ScanCommand.ELLIPTIC_CURVES,
            ScanCommand.TLS_FALLBACK_SCSV,
            ScanCommand.CERTIFICATE_INFO,
            ScanCommand.TLS_COMPRESSION,
        }
        if server_scan_result.scan_commands_results.keys() != expected_scan_command_results:
            raise RuntimeError("SSLyze did not complete all the expected scan commands.")
        else:
            print("OK: Completed all the expected scan commands.")

        # Ensure TLS 1.2 and 1.3 were detected by SSLyze to be enabled
        # https://github.com/nabla-c0d3/sslyze/issues/472
        for ciphers_scan_cmd in [ScanCommand.TLS_1_3_CIPHER_SUITES, ScanCommand.TLS_1_2_CIPHER_SUITES]:
            scan_cmd_result = server_scan_result.scan_commands_results[ciphers_scan_cmd]  # type: ignore
            if not scan_cmd_result.accepted_cipher_suites:
                raise RuntimeError(
                    f"SSLyze did not detect {scan_cmd_result.tls_version_used.name} to be enabled on the server."
                )
            else:
                print(f"OK: Scan command {ciphers_scan_cmd} detected cipher suites.")
def server_scan_result_for_google():
    scanner = Scanner()
    scanner.queue_scans([ServerScanRequest(server_location=ServerNetworkLocation(hostname="google.com"))])
    for server_scan_result in scanner.get_results():
        yield server_scan_result
Esempio n. 14
0
def main() -> None:
    # First create the scan requests for each server that we want to scan
    try:
        all_scan_requests = [
            ServerScanRequest(server_location=ServerNetworkLocation(hostname="cloudflare.com")),
            ServerScanRequest(server_location=ServerNetworkLocation(hostname="google.com")),
        ]
    except ServerHostnameCouldNotBeResolved:
        # Handle bad input ie. invalid hostnames
        print("Error resolving the supplied hostnames")
        return

    # Then queue all the scans
    scanner = Scanner()
    scanner.queue_scans(all_scan_requests)

    # And retrieve and process the results for each server
    for server_scan_result in scanner.get_results():
        print(f"\n\n****Results for {server_scan_result.server_location.hostname}****")

        # Were we able to connect to the server and run the scan?
        if server_scan_result.scan_status == ServerScanStatusEnum.ERROR_NO_CONNECTIVITY:
            # No we weren't
            print(
                f"\nError: Could not connect to {server_scan_result.server_location.hostname}:"
                f" {server_scan_result.connectivity_error_trace}"
            )
            continue

        # Since we were able to run the scan, scan_result is populated
        assert server_scan_result.scan_result

        # Process the result of the SSL 2.0 scan command
        ssl2_attempt = server_scan_result.scan_result.ssl_2_0_cipher_suites
        if ssl2_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
            # An error happened when this scan command was run
            _print_failed_scan_command_attempt(ssl2_attempt)
        elif ssl2_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
            # This scan command was run successfully
            ssl2_result = ssl2_attempt.result
            assert ssl2_result
            print("\nAccepted cipher suites for SSL 2.0:")
            for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
                print(f"* {accepted_cipher_suite.cipher_suite.name}")

        # Process the result of the TLS 1.3 scan command
        tls1_3_attempt = server_scan_result.scan_result.tls_1_3_cipher_suites
        if tls1_3_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
            _print_failed_scan_command_attempt(ssl2_attempt)
        elif tls1_3_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
            tls1_3_result = tls1_3_attempt.result
            assert tls1_3_result
            print("\nAccepted cipher suites for TLS 1.3:")
            for accepted_cipher_suite in tls1_3_result.accepted_cipher_suites:
                print(f"* {accepted_cipher_suite.cipher_suite.name}")

        # Process the result of the certificate info scan command
        certinfo_attempt = server_scan_result.scan_result.certificate_info
        if certinfo_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
            _print_failed_scan_command_attempt(certinfo_attempt)
        elif certinfo_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
            certinfo_result = certinfo_attempt.result
            assert certinfo_result
            print("\nLeaf certificates deployed:")
            for cert_deployment in certinfo_result.certificate_deployments:
                leaf_cert = cert_deployment.received_certificate_chain[0]
                print(
                    f"{leaf_cert.public_key().__class__.__name__}: {leaf_cert.subject.rfc4514_string()}"
                    f" (Serial: {leaf_cert.serial_number})"
                )
    def test(self):
        # Given a bunch of servers to scan
        # Including two reachable servers
        reachable_request1 = ServerScanRequest(
            server_location=ServerNetworkLocation(hostname="www.google.com",
                                                  port=443))
        reachable_request2 = ServerScanRequest(
            server_location=ServerNetworkLocation(
                hostname="www.cloudflare.com", port=443))
        # And two servers that are not reachable
        non_reachable_request1 = ServerScanRequest(
            server_location=ServerNetworkLocation(hostname="localhost",
                                                  port=12345))
        non_reachable_request2 = ServerScanRequest(
            server_location=ServerNetworkLocation(hostname="localhost",
                                                  port=54321))

        # When using the MassConnectivityTester to test connectivity
        completed_request_uuids = set()

        def server_connectivity_test_completed_callback(
                server_scan_request: ServerScanRequest,
                tls_probing_result: ServerTlsProbingResult) -> None:
            completed_request_uuids.add(server_scan_request.uuid)

        error_request_uuids = set()

        def server_connectivity_test_error_callback(
                server_scan_request: ServerScanRequest,
                connectivity_error: ConnectionToServerFailed) -> None:
            error_request_uuids.add(server_scan_request.uuid)

        tester = MassConnectivityTester(concurrent_server_scans_count=3)
        tester.start_work([
            reachable_request1, reachable_request2, non_reachable_request1,
            non_reachable_request2
        ])
        assert tester.has_started_work

        # It succeeds
        tester.wait_until_all_work_was_processed(
            server_connectivity_test_completed_callback=
            server_connectivity_test_completed_callback,
            server_connectivity_test_error_callback=
            server_connectivity_test_error_callback,
        )

        # And the reachable servers were returned
        assert len(completed_request_uuids) == 2
        assert {reachable_request1.uuid,
                reachable_request2.uuid} == completed_request_uuids

        # And the non-reachable server was returned
        assert len(error_request_uuids) == 2
        assert {non_reachable_request1.uuid,
                non_reachable_request2.uuid} == error_request_uuids

        # And the tester was shutdown cleanly
        tester._scan_requests_queue.join()
        tester._results_queue.join()
        for thread in tester._all_worker_threads:
            assert not thread.is_alive()
Esempio n. 16
0
def scan(target, ip, port, view, suite):
    """ Five inputs: web site name, ip, port
    split-dns view, and cipher suite """

    server_location = ServerNetworkLocationViaDirectConnection(
        target, port, ip)

    # This line checks to see if the host is online
    try:
        server_info = ServerConnectivityTester().perform(server_location)
    except errors.ConnectionToServerTimedOut:
        raise ConnectionError("Connection Timeout",
                              ERROR_MSG_CONNECTION_TIMEOUT(target, port))
    except errors.ConnectionToServerFailed:
        raise ConnectionError("Unknown Connection Error",
                              ERROR_MSG_UNKNOWN_CONNECTION(target, port))

    # Create a new results dictionary
    scan_output = results.new_result_set()

    # I hash the combination of hostname and ip for tracking
    key = md5((target + ip).encode("utf-8")).hexdigest()
    results.set_result(scan_output, "MD5", key)
    results.set_result(scan_output, "Target", f"{target}:{port}")
    results.set_result(scan_output, "IP", f"{ip}:{port}")
    results.set_result(scan_output, "Scan", suite)
    results.set_result(scan_output, "View", view)

    scanner = Scanner()
    server_scan_req = ServerScanRequest(server_info=server_info,
                                        scan_commands=CIPHER_SUITES.get(suite))
    scanner.queue_scan(server_scan_req)

    for result in scanner.get_results():
        for cipher_suite in CIPHER_SUITES.get(suite):
            scan_result = result.scan_commands_results[cipher_suite]

            for accepted_cipher_suite in scan_result.accepted_cipher_suites:
                if suite == "policy" and scan_result.tls_version_used.name == "TLS_1_2":
                    if (accepted_cipher_suite.cipher_suite.name
                            not in ALLOWED_TLS12_CIPHERS):
                        results.set_ciphers(
                            scan_output,
                            {
                                "Version":
                                f"{scan_result.tls_version_used.name}",
                                "Cipher":
                                f"{accepted_cipher_suite.cipher_suite.name}",
                            },
                        )
                else:
                    results.set_ciphers(
                        scan_output,
                        {
                            "Version": f"{scan_result.tls_version_used.name}",
                            "Cipher":
                            f"{accepted_cipher_suite.cipher_suite.name}",
                        },
                    )

    if len(scan_output["Results"]) == 0:
        results.set_result(scan_output, "Results", "No Policy Violations")

    return scan_output
Esempio n. 17
0
def main(server_software_running_on_localhost: WebServerSoftwareEnum) -> None:
    # Queue all scan commands against a server running on localhost
    print("Starting scan.")
    date_scans_started = datetime.utcnow()
    scanner = Scanner()
    scanner.queue_scans([
        ServerScanRequest(
            server_location=ServerNetworkLocation("localhost", 443))
    ])

    # Retrieve the result
    for server_scan_result in scanner.get_results():

        # First validate the connectivity testing
        assert server_scan_result.connectivity_status == ServerConnectivityStatusEnum.COMPLETED
        assert server_scan_result.connectivity_result
        if server_software_running_on_localhost == WebServerSoftwareEnum.APACHE2:
            # Apache2 is configured to require a client cert, and returns an error at the TLS layer if it is missing
            if server_scan_result.connectivity_result.client_auth_requirement != ClientAuthRequirementEnum.REQUIRED:
                raise RuntimeError(
                    f"SSLyze did not detect that client authentication was required by Apache2:"
                    f" {server_scan_result.connectivity_result.client_auth_requirement}."
                )
        elif server_software_running_on_localhost == WebServerSoftwareEnum.NGINX:
            # Nginx is configured to require a client cert but implements this by returning an error at the HTTP layer,
            # if the client cert is missing. This gets translated in SSLyze as "optionally" requiring a client cert
            if server_scan_result.connectivity_result.client_auth_requirement != ClientAuthRequirementEnum.OPTIONAL:
                raise RuntimeError(
                    f"SSLyze did not detect that client authentication was required by Nginx:"
                    f" {server_scan_result.connectivity_result.client_auth_requirement}."
                )
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # IIS is not configured to require a client cert for now because I don't know how to enable this
            if server_scan_result.connectivity_result.client_auth_requirement != ClientAuthRequirementEnum.DISABLED:
                raise RuntimeError(
                    f"SSLyze detected that client authentication was enabled by IIS:"
                    f" {server_scan_result.connectivity_result.client_auth_requirement}."
                )
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        successful_cmds = set()
        triggered_unexpected_error = False
        for scan_command in ScanCommandsRepository.get_all_scan_commands():
            scan_cmd_attempt = getattr(server_scan_result.scan_result,
                                       scan_command.value)
            if scan_cmd_attempt.status == ScanCommandAttemptStatusEnum.COMPLETED:
                successful_cmds.add(scan_command)
            elif scan_cmd_attempt.status == ScanCommandAttemptStatusEnum.ERROR:
                # Crash if any scan commands triggered an error that's not due to client authentication being required
                if scan_cmd_attempt.error_reason != ScanCommandErrorReasonEnum.CLIENT_CERTIFICATE_NEEDED:
                    triggered_unexpected_error = True
                    print(
                        f"\nError when running {scan_command}: {scan_cmd_attempt.error_reason}."
                    )
                    if scan_cmd_attempt.error_trace:
                        exc_trace = ""
                        for line in scan_cmd_attempt.error_trace.format(
                                chain=False):
                            exc_trace += f"       {line}"
                        print(exc_trace)

        print(f"Finished scan with {len(successful_cmds)} results.")
        if triggered_unexpected_error:
            raise RuntimeError("The scan triggered unexpected errors")
        else:
            # The CLIENT_CERTIFICATE_NEEDED errors are expected, because of how Apache2 is configured
            print("OK: Triggered CLIENT_CERTIFICATE_NEEDED errors only.")

        # Crash if SSLyze didn't complete the scan commands that are supposed to work even when we don't provide a
        # client certificate
        if server_software_running_on_localhost == WebServerSoftwareEnum.APACHE2:
            expected_scan_cmds_to_succeed = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
                ScanCommand.SSL_3_0_CIPHER_SUITES,
                ScanCommand.SSL_2_0_CIPHER_SUITES,
                ScanCommand.OPENSSL_CCS_INJECTION,
                ScanCommand.HEARTBLEED,
                ScanCommand.ELLIPTIC_CURVES,
                ScanCommand.TLS_FALLBACK_SCSV,
                ScanCommand.CERTIFICATE_INFO,
                ScanCommand.TLS_COMPRESSION,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.NGINX:
            # With nginx, when configured to require client authentication, more scan commands work because unlike
            # Apache2, it does complete a full TLS handshake even when a client cert was not provided. It then returns
            # an error page at the HTTP layer.
            expected_scan_cmds_to_succeed = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
                ScanCommand.SSL_3_0_CIPHER_SUITES,
                ScanCommand.SSL_2_0_CIPHER_SUITES,
                ScanCommand.OPENSSL_CCS_INJECTION,
                ScanCommand.HEARTBLEED,
                ScanCommand.ELLIPTIC_CURVES,
                ScanCommand.TLS_FALLBACK_SCSV,
                ScanCommand.CERTIFICATE_INFO,
                ScanCommand.TLS_COMPRESSION,
                ScanCommand.SESSION_RESUMPTION,
                ScanCommand.TLS_1_3_EARLY_DATA,
                ScanCommand.HTTP_HEADERS,
                ScanCommand.SESSION_RENEGOTIATION,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # With IIS, client authentication is not enabled so all scan commands should succeed
            expected_scan_cmds_to_succeed = ScanCommandsRepository.get_all_scan_commands(
            )  # type: ignore
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        missing_scan_cmds = expected_scan_cmds_to_succeed.difference(
            successful_cmds)
        if missing_scan_cmds:
            raise RuntimeError(
                f"SSLyze did not complete all the expected scan commands: {missing_scan_cmds}"
            )
        print("OK: Completed all the expected scan commands.")

        # Ensure the right TLS versions were detected by SSLyze as enabled
        # https://github.com/nabla-c0d3/sslyze/issues/472
        if server_software_running_on_localhost in [
                WebServerSoftwareEnum.APACHE2, WebServerSoftwareEnum.NGINX
        ]:
            # Apache and nginx are configured to only enable TLS 1.2 and TLS 1.3
            expected_enabled_tls_scan_commands = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # TLS 1.3 is not supported by IIS
            expected_enabled_tls_scan_commands = {
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
            }
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        for ciphers_scan_cmd in expected_enabled_tls_scan_commands:
            scan_cmd_attempt = getattr(server_scan_result.scan_result,
                                       ciphers_scan_cmd, None)
            scan_cmd_result = scan_cmd_attempt.result
            if not scan_cmd_result.accepted_cipher_suites:
                raise RuntimeError(
                    f"SSLyze did not detect {scan_cmd_result.tls_version_used.name} to be enabled on the server."
                )
            else:
                print(
                    f"OK: Scan command {ciphers_scan_cmd} detected cipher suites."
                )

        # Ensure a JSON output can be generated from the results
        final_json_output = SslyzeOutputAsJson(
            server_scan_results=[
                ServerScanResultAsJson.from_orm(server_scan_result)
            ],
            date_scans_started=date_scans_started,
            date_scans_completed=datetime.utcnow(),
        )
        final_json_output.json(sort_keys=True, indent=4, ensure_ascii=True)
        print("OK: Was able to generate JSON output.")
Esempio n. 18
0
def main(server_software_running_on_localhost: WebServerSoftwareEnum) -> None:
    # Ensure the server is accessible on localhost
    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
        "localhost", 443)
    server_info = ServerConnectivityTester().perform(server_location)

    if server_software_running_on_localhost == WebServerSoftwareEnum.APACHE2:
        # Apache2 is configured to require a client cert, and returns an error at the TLS layer if it is missing
        if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.REQUIRED:
            raise RuntimeError(
                f"SSLyze did not detect that client authentication was required by Apache2:"
                f" {server_info.tls_probing_result.client_auth_requirement}.")
    elif server_software_running_on_localhost == WebServerSoftwareEnum.NGINX:
        # Nginx is configured to require a client cert but implements this by returning an error at the HTTP layer,
        # if the client cert is missing. This gets translated in SSLyze as "optionally" requiring a client cert
        if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.OPTIONAL:
            raise RuntimeError(
                f"SSLyze did not detect that client authentication was required by Nginx:"
                f" {server_info.tls_probing_result.client_auth_requirement}.")
    elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
        # IIS is not configured to require a client cert for now because I don't know how to enable this
        if server_info.tls_probing_result.client_auth_requirement != ClientAuthRequirementEnum.DISABLED:
            raise RuntimeError(
                f"SSLyze detected that client authentication was enabled by IIS:"
                f" {server_info.tls_probing_result.client_auth_requirement}.")
    else:
        raise ValueError(
            f"Unexpected value: {server_software_running_on_localhost}")

    # Queue all scan commands
    print("Starting scan.")
    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_info=server_info,
        scan_commands=ScanCommandsRepository.get_all_scan_commands(),
    )
    scanner.queue_scan(server_scan_req)

    # Retrieve the result
    for server_scan_result in scanner.get_results():
        successful_cmds_count = len(server_scan_result.scan_commands_results)
        errored_cmds_count = len(server_scan_result.scan_commands_errors)
        print(
            f"Finished scan with {successful_cmds_count} results and {errored_cmds_count} errors."
        )

        # Crash if any scan commands triggered an error that's not due to client authentication being required
        triggered_unexpected_error = False
        for scan_command, error in server_scan_result.scan_commands_errors.items(
        ):
            if error.reason != ScanCommandErrorReasonEnum.CLIENT_CERTIFICATE_NEEDED:
                triggered_unexpected_error = True
                print(
                    f"\nError when running {scan_command}: {error.reason.name}."
                )
                if error.exception_trace:
                    exc_trace = ""
                    for line in error.exception_trace.format(chain=False):
                        exc_trace += f"       {line}"
                    print(exc_trace)

                print("\n")

        if triggered_unexpected_error:
            raise RuntimeError("The scan triggered unexpected errors")
        else:
            # The CLIENT_CERTIFICATE_NEEDED errors are expected, because of how Apache2 is configured
            print("OK: Triggered CLIENT_CERTIFICATE_NEEDED errors only.")

        # Crash if SSLyze didn't complete the scan commands that are supposed to work even when we don't provide a
        # client certificate
        if server_software_running_on_localhost == WebServerSoftwareEnum.APACHE2:
            expected_scan_command_results = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
                ScanCommand.SSL_3_0_CIPHER_SUITES,
                ScanCommand.SSL_2_0_CIPHER_SUITES,
                ScanCommand.OPENSSL_CCS_INJECTION,
                ScanCommand.HEARTBLEED,
                ScanCommand.ELLIPTIC_CURVES,
                ScanCommand.TLS_FALLBACK_SCSV,
                ScanCommand.CERTIFICATE_INFO,
                ScanCommand.TLS_COMPRESSION,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.NGINX:
            # With nginx, when configured to require client authentication, more scan commands work because unlike
            # Apache2, it does complete a full TLS handshake even when a client cert was not provided. It then returns
            # an error page at the HTTP layer.
            expected_scan_command_results = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
                ScanCommand.SSL_3_0_CIPHER_SUITES,
                ScanCommand.SSL_2_0_CIPHER_SUITES,
                ScanCommand.OPENSSL_CCS_INJECTION,
                ScanCommand.HEARTBLEED,
                ScanCommand.ELLIPTIC_CURVES,
                ScanCommand.TLS_FALLBACK_SCSV,
                ScanCommand.CERTIFICATE_INFO,
                ScanCommand.TLS_COMPRESSION,
                ScanCommand.SESSION_RESUMPTION,
                ScanCommand.TLS_1_3_EARLY_DATA,
                ScanCommand.HTTP_HEADERS,
                ScanCommand.SESSION_RESUMPTION_RATE,
                ScanCommand.SESSION_RENEGOTIATION,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # With IIS, client authentication is not enabled so all scan commands should succeed
            expected_scan_command_results = ScanCommandsRepository.get_all_scan_commands(
            )  # type: ignore
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        completed_scan_command_results = server_scan_result.scan_commands_results.keys(
        )
        if completed_scan_command_results != expected_scan_command_results:
            raise RuntimeError(
                f"SSLyze did not complete all the expected scan commands: {completed_scan_command_results}"
            )
        else:
            print("OK: Completed all the expected scan commands.")

        # Ensure the right TLS versions were detected by SSLyze as enabled
        # https://github.com/nabla-c0d3/sslyze/issues/472
        if server_software_running_on_localhost in [
                WebServerSoftwareEnum.APACHE2, WebServerSoftwareEnum.NGINX
        ]:
            # Apache and nginx are configured to only enable TLS 1.2 and TLS 1.3
            expected_enabled_tls_scan_commands = {
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
            }
        elif server_software_running_on_localhost == WebServerSoftwareEnum.IIS:
            # TLS 1.3 is not supported by IIS
            expected_enabled_tls_scan_commands = {
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES,
            }
        else:
            raise ValueError(
                f"Unexpected value: {server_software_running_on_localhost}")

        for ciphers_scan_cmd in expected_enabled_tls_scan_commands:
            scan_cmd_result = server_scan_result.scan_commands_results[
                ciphers_scan_cmd]  # type: ignore
            if not scan_cmd_result.accepted_cipher_suites:
                raise RuntimeError(
                    f"SSLyze did not detect {scan_cmd_result.tls_version_used.name} to be enabled on the server."
                )
            else:
                print(
                    f"OK: Scan command {ciphers_scan_cmd} detected cipher suites."
                )

        # Ensure a JSON output can be generated from the results
        json_output = _SslyzeOutputAsJson(
            server_scan_results=[server_scan_result],
            server_connectivity_errors=[],
            total_scan_time=3,
        )
        json_output_as_dict = asdict(json_output)
        json.dumps(json_output_as_dict,
                   cls=JsonEncoder,
                   sort_keys=True,
                   indent=4,
                   ensure_ascii=True)
        print("OK: Was able to generate JSON output.")
Esempio n. 19
0
def ssl_scan(self, target):
  print("Running SSL Scan")
  # Define the server that you want to scan
  server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
    target, 443)

  try:
    # Do connectivity testing to ensure SSLyze is able to connect
    try:
      server_info = ServerConnectivityTester().perform(server_location)
    except ConnectionToServerFailed as e:
      # Could not connect to the server; abort
      print(f"Error connecting to {server_location}: {e.error_message}")
      return

    # Then queue some scan commands for the server
    scanner = Scanner()
    server_scan_req = ServerScanRequest(server_info=server_info, scan_commands={
      ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES,
      ScanCommand.TLS_1_0_CIPHER_SUITES, ScanCommand.TLS_1_1_CIPHER_SUITES,
      ScanCommand.TLS_1_2_CIPHER_SUITES, ScanCommand.TLS_1_3_CIPHER_SUITES,
      ScanCommand.HEARTBLEED, ScanCommand.HTTP_HEADERS}, )
    scanner.queue_scan(server_scan_req)

    # Then retrieve the results
    for server_scan_result in scanner.get_results():
      print(
        f"\nResults for {server_scan_result.server_info.server_location.hostname}:")

      heartbleed_vuln = server_scan_result.scan_commands_results[
        ScanCommand.HEARTBLEED].is_vulnerable_to_heartbleed
      print(f"\nIs vulnerable to heartbleed? {heartbleed_vuln}")

      print("\nAccepted cipher suites for TLS 1.0:")
      for accepted_cipher_suite in server_scan_result.scan_commands_results[
        ScanCommand.TLS_1_0_CIPHER_SUITES].accepted_cipher_suites:
        print(f"* {accepted_cipher_suite.cipher_suite.name}")

      print("\nAccepted cipher suites for TLS 1.1:")
      for accepted_cipher_suite in server_scan_result.scan_commands_results[
        ScanCommand.TLS_1_1_CIPHER_SUITES].accepted_cipher_suites:
        print(f"* {accepted_cipher_suite.cipher_suite.name}")

      print("\nAccepted cipher suites for TLS 1.2:")
      for accepted_cipher_suite in server_scan_result.scan_commands_results[
        ScanCommand.TLS_1_2_CIPHER_SUITES].accepted_cipher_suites:
        print(f"* {accepted_cipher_suite.cipher_suite.name}")

      print("\nAccepted cipher suites for TLS 1.3:")
      for accepted_cipher_suite in server_scan_result.scan_commands_results[
        ScanCommand.TLS_1_3_CIPHER_SUITES].accepted_cipher_suites:
        print(f"* {accepted_cipher_suite.cipher_suite.name}")

      # SSL 2.0 results
      ssl2_result = server_scan_result.scan_commands_results[
        ScanCommand.SSL_2_0_CIPHER_SUITES]
      print("\nAccepted cipher suites for SSL 2.0:")
      for accepted_cipher_suite in ssl2_result.accepted_cipher_suites:
        print(f"* {accepted_cipher_suite.cipher_suite.name}")

      # Certificate info results
      certinfo_result = server_scan_result.scan_commands_results[
        ScanCommand.CERTIFICATE_INFO]
      print("\nCertificate info:")
      for cert_deployment in certinfo_result.certificate_deployments:
        print(
          f"Leaf certificate: \n{cert_deployment.received_certificate_chain_as_pem[0]}")

  except Exception as e:
    self.handle_exception(e, "Error running SSL scan")
    pass
Esempio n. 20
0
def main() -> None:
    # Parse the supplied command line
    date_scans_started = datetime.utcnow()
    sslyze_parser = CommandLineParser(__version__)
    try:
        parsed_command_line = sslyze_parser.parse_command_line()
    except CommandLineParsingError as e:
        print(e.get_error_msg())
        return

    # Setup the observer to print to the console, if needed
    scanner_observers = []
    if not parsed_command_line.should_disable_console_output:
        observer_for_console_output = ObserverToGenerateConsoleOutput(
            file_to=sys.stdout, json_path_out=parsed_command_line.json_path_out
        )
        observer_for_console_output.command_line_parsed(parsed_command_line=parsed_command_line)

        scanner_observers.append(observer_for_console_output)

    # Setup the scanner
    sslyze_scanner = Scanner(
        per_server_concurrent_connections_limit=parsed_command_line.per_server_concurrent_connections_limit,
        concurrent_server_scans_limit=parsed_command_line.concurrent_server_scans_limit,
        observers=scanner_observers,
    )

    # Queue the scans
    all_server_scan_requests = []
    for server_location, network_config in parsed_command_line.servers_to_scans:
        scan_request = ServerScanRequest(
            server_location=server_location,
            network_configuration=network_config,
            scan_commands=parsed_command_line.scan_commands,
            scan_commands_extra_arguments=parsed_command_line.scan_commands_extra_arguments,
        )
        all_server_scan_requests.append(scan_request)

    # If there are servers that we were able to resolve, scan them
    all_server_scan_results = []
    if all_server_scan_requests:
        sslyze_scanner.queue_scans(all_server_scan_requests)
        for result in sslyze_scanner.get_results():
            # Results are actually displayed by the observer; here we just store them
            all_server_scan_results.append(result)

    # Write results to a JSON file if needed
    json_file_out: Optional[TextIO] = None
    if parsed_command_line.should_print_json_to_console:
        json_file_out = sys.stdout
    elif parsed_command_line.json_path_out:
        json_file_out = parsed_command_line.json_path_out.open("wt", encoding="utf-8")

    if json_file_out:
        json_output = SslyzeOutputAsJson(
            server_scan_results=[ServerScanResultAsJson.from_orm(result) for result in all_server_scan_results],
            invalid_server_strings=[
                InvalidServerStringAsJson.from_orm(bad_server) for bad_server in parsed_command_line.invalid_servers
            ],
            date_scans_started=date_scans_started,
            date_scans_completed=datetime.utcnow(),
        )
        json_output_as_str = json_output.json(sort_keys=True, indent=4, ensure_ascii=True)
        json_file_out.write(json_output_as_str)

    # If we printed the JSON results to the console, don't run the Mozilla compliance check so we return valid JSON
    if parsed_command_line.should_print_json_to_console:
        sys.exit(0)

    if not all_server_scan_results:
        # There are no results to present: all supplied server strings were invalid?
        sys.exit(0)

    # Check the results against the Mozilla config if needed
    are_all_servers_compliant = True
    # TODO(AD): Expose format_title method
    title = ObserverToGenerateConsoleOutput._format_title("Compliance against Mozilla TLS configuration")
    print()
    print(title)
    if not parsed_command_line.check_against_mozilla_config:
        print("    Disabled; use --mozilla_config={old, intermediate, modern}.\n")
    else:

        print(
            f'    Checking results against Mozilla\'s "{parsed_command_line.check_against_mozilla_config}"'
            f" configuration. See https://ssl-config.mozilla.org/ for more details.\n"
        )
        mozilla_checker = MozillaTlsConfigurationChecker.get_default()
        for server_scan_result in all_server_scan_results:
            try:
                mozilla_checker.check_server(
                    against_config=parsed_command_line.check_against_mozilla_config,
                    server_scan_result=server_scan_result,
                )
                print(f"    {server_scan_result.server_location.display_string}: OK - Compliant.\n")

            except ServerNotCompliantWithMozillaTlsConfiguration as e:
                are_all_servers_compliant = False
                print(f"    {server_scan_result.server_location.display_string}: FAILED - Not compliant.")
                for criteria, error_description in e.issues.items():
                    print(f"        * {criteria}: {error_description}")
                print()

            except ServerScanResultIncomplete:
                are_all_servers_compliant = False
                print(
                    f"    {server_scan_result.server_location.display_string}: ERROR - Scan did not run successfully;"
                    f" review the scan logs above."
                )

    if not are_all_servers_compliant:
        # Return a non-zero error code to signal failure (for example to fail a CI/CD pipeline)
        sys.exit(1)
Esempio n. 21
0
def run(url):
    scan_result = {"name": __plugin__, "sequence": SEQUENCE, "result": []}
    error_result = {"name": __plugin__, "sequence": SEQUENCE, "result": []}
    error_result["result"] = [{
        "name":
        "Error",
        "result": [{
            "name": f"{__plugin__} can't scan this website"
        }]
    }]
    result_map = {
        "https": {
            "name": "Enabled HTTPS",
            "sequence": 0,
            "result": []
        },
        "effective": {
            "name": "Effective",
            "sequence": 1,
            "result": []
        },
        "subject": {
            "name": "Subject",
            "sequence": 2,
            "result": []
        },
        "issuer": {
            "name": "Issuer",
            "sequence": 3,
            "result": []
        },
        "public": {
            "name": "Public key algorithm",
            "sequence": 4,
            "result": [],
        },
        "signature": {
            "name": "Signature hash algorithm",
            "sequence": 5,
            "result": [],
        },
        "before": {
            "name": "Not valid before (UTC)",
            "sequence": 6,
            "result": [],
        },
        "after": {
            "name": "Not valid after (UTC)",
            "sequence": 7,
            "result": [],
        },
        "tls1_2": {
            "name": "Accepted TLS1.2 cipher suites",
            "sequence": 8,
            "result": []
        },
        "tls1_3": {
            "name": "Accepted TLS1.3 cipher suites",
            "sequence": 9,
            "result": []
        },
        "pfs": {
            "name": "Perfect Forward Secrecy (PFS)",
            "sequence": 10,
            "result": [],
        },
        "ats": {
            "name": "App Transport Security (ATS)",
            "sequence": 11,
            "result": [],
        },
        "tls1_3_early_data": {
            "name": "Support TLS1.3 early data",
            "sequence": 12,
            "result": []
        },
        "match": {
            "name": "Leaf certificate subject matches hostname",
            "sequence": 13,
            "result": [],
        },
        "ocsp": {
            "name": "OCSP Must-Staple",
            "sequence": 14,
            "result": [],
        },
        "fallback": {
            "name": "The TLS_FALLBACK_SCSV mechanism",
            "sequence": 15,
            "result": []
        },
        "ccs": {
            "name": "The OpenSSL CCS Injection vulnerability",
            "sequence": 16,
            "result": []
        },
        "heartbleed": {
            "name": "The Heartbleed vulnerability",
            "sequence": 17,
            "result": []
        },
        "crime": {
            "name": "The CRIME vulnerability",
            "sequence": 18,
            "result": []
        },
        "robot": {
            "name": "The ROBOT vulnerability",
            "sequence": 19,
            "result": []
        },
    }

    server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
        url.netloc, 443)
    try:
        server_info = ServerConnectivityTester().perform(server_location)
    except ConnectionToServerFailed as e:
        return error_result

    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_info,
        {
            ScanCommand.CERTIFICATE_INFO,
            ScanCommand.TLS_1_2_CIPHER_SUITES,
            ScanCommand.TLS_1_3_CIPHER_SUITES,
            ScanCommand.TLS_1_3_EARLY_DATA,
            ScanCommand.TLS_FALLBACK_SCSV,
            ScanCommand.OPENSSL_CCS_INJECTION,
            ScanCommand.HEARTBLEED,
            ScanCommand.TLS_COMPRESSION,
            ScanCommand.ROBOT,
        },
    )
    scanner.queue_scan(server_scan_req)
    for server_scan_result in scanner.get_results():
        certificate_result = server_scan_result.scan_commands_results[
            ScanCommand.CERTIFICATE_INFO]
        tls_1_2_cipher_result = server_scan_result.scan_commands_results[
            ScanCommand.TLS_1_2_CIPHER_SUITES]
        tls_1_3_cipher_result = server_scan_result.scan_commands_results[
            ScanCommand.TLS_1_3_CIPHER_SUITES]
        tls_1_3_early_result = server_scan_result.scan_commands_results[
            ScanCommand.TLS_1_3_EARLY_DATA]
        tls_fallback_result = server_scan_result.scan_commands_results[
            ScanCommand.TLS_FALLBACK_SCSV]
        ccs_result = server_scan_result.scan_commands_results[
            ScanCommand.OPENSSL_CCS_INJECTION]
        heartbleed_result = server_scan_result.scan_commands_results[
            ScanCommand.HEARTBLEED]
        crime_result = server_scan_result.scan_commands_results[
            ScanCommand.TLS_COMPRESSION]
        robot_result = server_scan_result.scan_commands_results[
            ScanCommand.ROBOT]

    for certificate_deployment in certificate_result.certificate_deployments:
        for certificate_info in certificate_deployment.received_certificate_chain:
            result_map["subject"]["result"] = [{
                "name":
                certificate_info.subject.rfc4514_string()
            }]
            result_map["issuer"]["result"] = [{
                "name":
                certificate_info.issuer.rfc4514_string()
            }]
            public_key = certificate_info.public_key()
            public_key_name = type(public_key).__name__[1:][:-9]
            if "key_size" in dir(public_key):
                result_map["public"]["result"] = [{
                    "name":
                    f"{public_key_name}{certificate_info.public_key().key_size}"
                }]
            else:
                result_map["public"]["result"] = [{"name": public_key_name}]
            result_map["signature"]["result"] = [{
                "name":
                certificate_info.signature_hash_algorithm.name.upper()
            }]
            if datetime.now(
            ) > certificate_info.not_valid_before and datetime.now(
            ) < certificate_info.not_valid_after:
                result_map["effective"]["result"] = True
            result_map["before"]["result"] = [{
                "name":
                datetime.strftime(certificate_info.not_valid_before,
                                  "%Y-%m-%d %H:%M:%S")
            }]
            result_map["after"]["result"] = [{
                "name":
                datetime.strftime(certificate_info.not_valid_after,
                                  "%Y-%m-%d %H:%M:%S")
            }]
            break
        result_map["https"]["result"] = True
        result_map["match"][
            "result"] = certificate_deployment.leaf_certificate_subject_matches_hostname
        result_map["ocsp"][
            "result"] = certificate_deployment.leaf_certificate_has_must_staple_extension
        break

    tls_1_2_cipher_list = [
        accepted_cipher_suite.cipher_suite.name for accepted_cipher_suite in
        tls_1_2_cipher_result.accepted_cipher_suites
    ]
    tls_1_3_cipher_list = [
        accepted_cipher_suite.cipher_suite.name for accepted_cipher_suite in
        tls_1_3_cipher_result.accepted_cipher_suites
    ]
    result_map["tls1_2"]["result"] = [{
        "name": tls_1_2_cipher
    } for tls_1_2_cipher in tls_1_2_cipher_list
                                      ] if tls_1_2_cipher_list else False
    result_map["tls1_3"]["result"] = [{
        "name": tls_1_3_cipher
    } for tls_1_3_cipher in tls_1_3_cipher_list
                                      ] if tls_1_3_cipher_list else False
    cipher_list = tls_1_2_cipher_list + tls_1_3_cipher_list
    for cipher in cipher_list:
        if "DHE" in cipher:
            result_map["pfs"]["result"] = True
    if set(cipher_list).intersection(ATS_CIPHER_SET):
        result_map["ats"]["result"] = True
    result_map["tls1_3_early_data"][
        "result"] = tls_1_3_early_result.supports_early_data
    result_map["fallback"][
        "result"] = tls_fallback_result.supports_fallback_scsv
    result_map["ccs"]["result"] = not ccs_result.is_vulnerable_to_ccs_injection
    result_map["heartbleed"][
        "result"] = not heartbleed_result.is_vulnerable_to_heartbleed
    result_map["crime"]["result"] = not crime_result.supports_compression
    result_map["robot"]["result"] = [{"name": robot_result.robot_result.name}]

    scan_result["result"] = sorted([item for item in result_map.values()],
                                   key=lambda x: x.get("sequence", 0))

    return scan_result
Esempio n. 22
0
def analyze(hostname: str, port: int) -> List[Tuple[int, str]]:
    results = []
    # Define the server that you want to scan
    try:
        server_location = ServerNetworkLocation(hostname, port)
    except ServerHostnameCouldNotBeResolved:
        log_red(_("Could not resolve {0}"), hostname)
        return results

    # Then queue some scan commands for the server
    scanner = Scanner()
    server_scan_req = ServerScanRequest(
        server_location=server_location,
        scan_commands={
            ScanCommand.CERTIFICATE_INFO, ScanCommand.SSL_2_0_CIPHER_SUITES,
            ScanCommand.SSL_3_0_CIPHER_SUITES,
            ScanCommand.TLS_1_0_CIPHER_SUITES,
            ScanCommand.TLS_1_1_CIPHER_SUITES,
            ScanCommand.TLS_1_2_CIPHER_SUITES,
            ScanCommand.TLS_1_3_CIPHER_SUITES, ScanCommand.ROBOT,
            ScanCommand.HEARTBLEED, ScanCommand.TLS_COMPRESSION,
            ScanCommand.TLS_FALLBACK_SCSV, ScanCommand.TLS_1_3_EARLY_DATA,
            ScanCommand.OPENSSL_CCS_INJECTION,
            ScanCommand.SESSION_RENEGOTIATION, ScanCommand.HTTP_HEADERS
        },
        network_configuration=ServerNetworkConfiguration(
            tls_server_name_indication=server_location.hostname,
            network_timeout=5,
            network_max_retries=2))
    scanner.queue_scans([server_scan_req])

    # TLS 1.2 / 1.3 results
    good_protocols = {
        ScanCommand.TLS_1_2_CIPHER_SUITES: "TLS v1.2",
        ScanCommand.TLS_1_3_CIPHER_SUITES: "TLS v1.3"
    }

    # https://blog.mozilla.org/security/2014/10/14/the-poodle-attack-and-the-end-of-ssl-3-0/
    # https://blog.qualys.com/product-tech/2018/11/19/grade-change-for-tls-1-0-and-tls-1-1-protocols
    bad_protocols = {
        ScanCommand.SSL_2_0_CIPHER_SUITES: "SSL v2",
        ScanCommand.SSL_3_0_CIPHER_SUITES: "SSL v3",
        ScanCommand.TLS_1_0_CIPHER_SUITES: "TLS v1.0",
        ScanCommand.TLS_1_1_CIPHER_SUITES: "TLS v1.1"
    }

    # Then retrieve the results
    for result in scanner.get_results():
        log_blue("\n" + _("Results for") +
                 f" {result.server_location.hostname}:")
        deprecated_protocols = []

        if result.connectivity_error_trace:
            # Stuff like connection timeout
            log_red(result.connectivity_error_trace)
            continue

        for scan_command in result.scan_result.__annotations__:
            scan_results = getattr(result.scan_result, scan_command)

            if scan_results.error_reason:
                log_red(scan_results.error_reason)
                continue

            if scan_results.status != ScanCommandAttemptStatusEnum.COMPLETED:
                continue

            if scan_command == ScanCommand.CERTIFICATE_INFO:
                for level, message in process_certificate_info(
                        scan_results.result):
                    results.append((level, message))
            elif scan_command in bad_protocols:
                if scan_results.result.accepted_cipher_suites:
                    deprecated_protocols.append(bad_protocols[scan_command])
            elif scan_command == ScanCommand.ROBOT:
                if scan_results.result.robot_result in (
                        RobotScanResultEnum.VULNERABLE_WEAK_ORACLE,
                        RobotScanResultEnum.VULNERABLE_STRONG_ORACLE):
                    message = _("Server is vulnerable to ROBOT attack")
                    log_red(message)
                    results.append((CRITICAL_LEVEL, message))
            elif scan_command == ScanCommand.HEARTBLEED:
                if scan_results.result.is_vulnerable_to_heartbleed:
                    message = _("Server is vulnerable to Heartbleed attack")
                    log_red(message)
                    results.append((CRITICAL_LEVEL, message))
            elif scan_command == ScanCommand.TLS_COMPRESSION:
                if scan_results.result.supports_compression:
                    message = _(
                        "Server is vulnerable to CRIME attack (compression is supported)"
                    )
                    log_red(message)
                    results.append((CRITICAL_LEVEL, message))
            elif scan_command == ScanCommand.TLS_FALLBACK_SCSV:
                if not scan_results.result.supports_fallback_scsv:
                    message = _(
                        "Server is vulnerable to downgrade attacks (support for TLS_FALLBACK_SCSV is missing)"
                    )
                    log_red(message)
                    results.append((CRITICAL_LEVEL, message))
            elif scan_command == ScanCommand.TLS_1_3_EARLY_DATA:
                # https://blog.trailofbits.com/2019/03/25/what-application-developers-need-to-know-about-tls-early-data-0rtt/
                if scan_results.result.supports_early_data:
                    message = _(
                        "TLS 1.3 Early Data (0RTT) is vulnerable to replay attacks"
                    )
                    log_orange(message)
                    results.append((MEDIUM_LEVEL, message))
            elif scan_command == ScanCommand.OPENSSL_CCS_INJECTION:
                if scan_results.result.is_vulnerable_to_ccs_injection:
                    message = _(
                        "Server is vulnerable to OpenSSL CCS (CVE-2014-0224)")
                    log_red(message)
                    results.append((CRITICAL_LEVEL, message))
            elif scan_command == ScanCommand.SESSION_RENEGOTIATION:
                if scan_results.result.is_vulnerable_to_client_renegotiation_dos:
                    message = _(
                        "Server honors client-initiated renegotiations (vulnerable to DoS attacks)"
                    )
                    log_red(message)
                    results.append((HIGH_LEVEL, message))
                if not scan_results.result.supports_secure_renegotiation:
                    message = _("Server doesn't support secure renegotiations")
                    log_orange(message)
                    results.append((MEDIUM_LEVEL, message))
            elif scan_command == ScanCommand.HTTP_HEADERS:
                if scan_results.result.strict_transport_security_header is None:
                    message = _("Strict Transport Security (HSTS) is not set")
                    log_red(message)
                    results.append((HIGH_LEVEL, message))
            elif scan_command in good_protocols:
                for level, message in process_cipher_suites(
                        scan_results.result, good_protocols[scan_command]):
                    results.append((level, message))

        if deprecated_protocols:
            message = _("The following protocols are deprecated and/or insecure and should be deactivated:") + \
                      " " + ", ".join(deprecated_protocols)
            log_red(message)
            results.append((CRITICAL_LEVEL, message))

    return results
Esempio n. 23
0
def scan_runner(seq, host):
    hostname = host.decode("utf-8")
    servers_to_scan = []
    server_location = None
    try:
        if r.hget(seq, "ipaddr"):
            server_location = ServerNetworkLocationViaDirectConnection(
                hostname, 443,
                r.hget(seq, "ipaddr").decode("utf-8"))
        else:
            server_location = ServerNetworkLocationViaDirectConnection.with_ip_address_lookup(
                hostname, 443)
            r.hset(seq, "ipaddr", server_location.ip_address)
        #Initialize with hostname, port int and ip address str
        #print(server_location)
    except Exception as e:
        print(e)
        r.hset(seq, "STATUS", 2)
    try:
        server_info = ServerConnectivityTester().perform(server_location)
        servers_to_scan.append(server_info)
    except ConnectionToServerFailed as e:
        if 'Probing failed' in str(e):
            r.hset(seq, "STATUS", 31)
        else:
            r.hset(seq, "STATUS", 32)
        return

    scanner = Scanner()

    # Then queue some scan commands for each server
    for server_info in servers_to_scan:
        server_scan_req = ServerScanRequest(
            server_info=server_info,
            scan_commands={
                ScanCommand.TLS_1_3_CIPHER_SUITES,
                ScanCommand.TLS_1_2_CIPHER_SUITES,
                ScanCommand.TLS_1_1_CIPHER_SUITES,
                ScanCommand.TLS_1_0_CIPHER_SUITES
            },
        )
        scanner.queue_scan(server_scan_req)

    # Then retrieve the result of the scan commands for each server
    for server_scan_result in scanner.get_results():
        try:
            tls1_3_result = server_scan_result.scan_commands_results[
                ScanCommand.TLS_1_3_CIPHER_SUITES]
            cipherstr = ""
            if tls1_3_result.accepted_cipher_suites:
                for accepted_cipher_suite in tls1_3_result.accepted_cipher_suites:
                    cipherstr = cipherstr + str(
                        accepted_cipher_suite.cipher_suite.name) + " "
                r.hset(seq, "TLS1_3", cipherstr)

            tls1_2_result = server_scan_result.scan_commands_results[
                ScanCommand.TLS_1_2_CIPHER_SUITES]
            cipherstr = ""
            if tls1_2_result.accepted_cipher_suites:
                for accepted_cipher_suite in tls1_2_result.accepted_cipher_suites:
                    cipherstr = cipherstr + str(
                        accepted_cipher_suite.cipher_suite.name) + " "
                r.hset(seq, "TLS1_2", cipherstr)

            tls1_1_result = server_scan_result.scan_commands_results[
                ScanCommand.TLS_1_1_CIPHER_SUITES]
            cipherstr = ""
            if tls1_1_result.accepted_cipher_suites:
                for accepted_cipher_suite in tls1_1_result.accepted_cipher_suites:
                    cipherstr = cipherstr + str(
                        accepted_cipher_suite.cipher_suite.name) + " "
                r.hset(seq, "TLS1_1", cipherstr)

            tls1_0_result = server_scan_result.scan_commands_results[
                ScanCommand.TLS_1_0_CIPHER_SUITES]
            cipherstr = ""
            if tls1_0_result.accepted_cipher_suites:
                for accepted_cipher_suite in tls1_0_result.accepted_cipher_suites:
                    cipherstr = cipherstr + str(
                        accepted_cipher_suite.cipher_suite.name) + " "
                r.hset(seq, "TLS1_0", cipherstr)
            r.hset(seq, "STATUS", 1)

        except KeyError:
            r.hset(seq, "STATUS", 4)

        # Scan commands that were run with errors
        for scan_command, error in server_scan_result.scan_commands_errors.items(
        ):
            r.hset(seq, "STATUS", 5)