Example #1
0
    def test(self):
        """The final output only gets written at the end, when calling scans_completed(). Hence we need to call all the
        methods in the right order and validate the final output at the end.
        """
        output_file = StringIO()
        generator = XmlOutputGenerator(output_file)

        generator.command_line_parsed(None, MockCommandLineValues())

        failed_scan = FailedServerScan(
            server_string=u'unibadeéè.com',
            connection_exception=ServerConnectivityError(
                error_msg=u'Some érrôr'))
        generator.server_connectivity_test_failed(failed_scan)

        server_info = MockServerConnectivityInfo()
        generator.server_connectivity_test_succeeded(server_info)

        generator.scans_started()

        plugin_xml_out_1 = Element(u'plugin1', attrib={'test1': 'value1'})
        plugin_xml_out_1.text = u'Plugin ûnicôdé output'
        plugin_result_1 = MockPluginResult('plugin1', None, plugin_xml_out_1)
        plugin_xml_out_2 = Element(u'plugin2', attrib={'test2': 'value2'})
        plugin_xml_out_2.text = u'other plugin Output'
        plugin_result_2 = MockPluginResult('plugin2', None, plugin_xml_out_2)
        server_scan = CompletedServerScan(server_info,
                                          [plugin_result_1, plugin_result_2])
        generator.server_scan_completed(server_scan)

        scan_time = 1.3
        generator.scans_completed(scan_time)

        received_output = unicode(output_file.getvalue(), 'utf-8')
        output_file.close()

        # Ensure the output properly listed the connectivity error with unicode escaped as \u sequences
        self.assertIn(u'unibadeéè.com', received_output)
        self.assertIn(u'Some érrôr', received_output)

        # Ensure the output properly listed the online domain
        self.assertIn(server_info.hostname, received_output)
        self.assertIn(str(server_info.port), received_output)
        self.assertIn(server_info.ip_address, received_output)

        # Ensure the output displayed the plugin's XML output
        self.assertIn(plugin_result_1.plugin_command, received_output)
        self.assertIn(plugin_result_2.plugin_command, received_output)
        self.assertIn(plugin_result_1.as_xml().text, received_output)
        self.assertIn(plugin_result_2.as_xml().text, received_output)

        # Ensure the console output displayed the total scan time
        self.assertIn('totalScanTime="{}"'.format(scan_time), received_output)
        self.assertIn(
            'networkTimeout="{}"'.format(MockCommandLineValues().timeout),
            received_output)
        self.assertIn(
            'networkMaxRetries="{}"'.format(
                MockCommandLineValues().nb_retries), received_output)
Example #2
0
    def test(self):
        """The final output only gets written at the end, when calling scans_completed(). Hence we need to call all the
        methods in the right order and validate the final output at the end.
        """
        output_file = StringIO()
        generator = JsonOutputGenerator(output_file)

        generator.command_line_parsed(None, MockCommandLineValues())

        failed_scan = FailedServerScan(server_string=u'unibadeéè.com',
                                       connection_exception=ServerConnectivityError(error_msg=u'Some érrôr'))
        generator.server_connectivity_test_failed(failed_scan)

        server_info = MockServerConnectivityInfo()
        generator.server_connectivity_test_succeeded(server_info)

        generator.scans_started()

        # noinspection PyTypeChecker
        plugin_result_1 = MockPluginResult('plugin1', u'Plugin ûnicôdé output', None)
        # noinspection PyTypeChecker
        plugin_result_2 = MockPluginResult('plugin2', u'other plugin Output', None)
        # noinspection PyTypeChecker
        server_scan = CompletedServerScan(server_info, [plugin_result_1, plugin_result_2])
        generator.server_scan_completed(server_scan)

        scan_time = 1.3
        generator.scans_completed(scan_time)

        received_output = output_file.getvalue()
        output_file.close()

        # Ensure the output properly listed the connectivity error with unicode escaped as \u sequences
        self.assertIn(json.dumps(u'unibadeéè.com', ensure_ascii=True), received_output)
        self.assertIn(json.dumps(u'Some érrôr', ensure_ascii=True), received_output)

        # Ensure the output properly listed the online domain
        self.assertIn(json.dumps(server_info.hostname, ensure_ascii=True), received_output)
        self.assertIn(str(server_info.port), received_output)
        self.assertIn(server_info.ip_address, received_output)

        # Ensure the output displayed the plugin's attributes as JSON
        self.assertIn(plugin_result_1.plugin_command, received_output)
        self.assertIn(plugin_result_2.plugin_command, received_output)
        self.assertIn('"text_output":', received_output)
        self.assertIn(json.dumps(plugin_result_1.text_output, ensure_ascii=True), received_output)
        self.assertIn(plugin_result_2.text_output, received_output)

        # Ensure the console output displayed the total scan time
        self.assertIn(str(scan_time), received_output)
        self.assertIn('"network_timeout": "{}"'.format(MockCommandLineValues().timeout), received_output)
        self.assertIn('"network_max_retries": "{}"'.format(MockCommandLineValues().nb_retries), received_output)
    def test_server_connectivity_test_failed(self):
        output_file = StringIO()
        generator = ConsoleOutputGenerator(output_file)

        failed_scan = FailedServerScan(
            server_string=u'unicödeéè.com',
            connection_exception=ServerConnectivityError(
                error_msg=u'Some érrôr'))
        generator.server_connectivity_test_failed(failed_scan)

        received_output = output_file.getvalue()
        output_file.close()

        # Ensure the console output properly listed the connectivity error with unicode
        self.assertIn(u'unicödeéè.com', received_output)
        self.assertIn(u'Some érrôr', received_output)
        self.assertIn(u'discarding corresponding tasks', received_output)
    def parse_command_line(self):
        """Parses the command line used to launch SSLyze.
        """

        (args_command_list, args_target_list) = self._parser.parse_args()

        # Handle the --targets_in command line and fill args_target_list
        if args_command_list.targets_in:
            if args_target_list:
                raise CommandLineParsingError(
                    "Cannot use --targets_list and specify targets within the command line."
                )

            try:  # Read targets from a file
                with open(args_command_list.targets_in) as f:
                    for target in f.readlines():
                        if target.strip():  # Ignore empty lines
                            if not target.startswith(
                                    '#'):  # Ignore comment lines
                                args_target_list.append(target.strip())
            except IOError:
                raise CommandLineParsingError(
                    "Can't read targets from input file '{}.".format(
                        args_command_list.targets_in))

        if not args_target_list:
            raise CommandLineParsingError('No targets to scan.')

        # Handle the --regular command line parameter as a shortcut
        if self._parser.has_option('--regular'):
            if getattr(args_command_list, 'regular'):
                setattr(args_command_list, 'regular', False)
                for cmd in self.REGULAR_CMD:
                    setattr(args_command_list, cmd, True)

        # Sanity checks on the command line options
        # Prevent --quiet and --xml_out -
        if args_command_list.xml_file and args_command_list.xml_file == '-' and args_command_list.quiet:
            raise CommandLineParsingError(
                'Cannot use --quiet with --xml_out -.')

        # Prevent --quiet and --json_out -
        if args_command_list.json_file and args_command_list.json_file == '-' and args_command_list.quiet:
            raise CommandLineParsingError(
                'Cannot use --quiet with --json_out -.')

        # Prevent --xml_out - and --json_out -
        if args_command_list.json_file and args_command_list.json_file == '-' \
                and args_command_list.xml_file and args_command_list.xml_file == '-':
            raise CommandLineParsingError(
                'Cannot use --xml_out - with --json_out -.')

        # Sanity checks on the client cert options
        client_auth_creds = None
        if bool(args_command_list.cert) ^ bool(args_command_list.key):
            raise CommandLineParsingError(
                'No private key or certificate file were given. See --cert and --key.'
            )

        elif args_command_list.cert:
            # Private key formats
            if args_command_list.keyform == 'DER':
                key_type = SSL_FILETYPE_ASN1
            elif args_command_list.keyform == 'PEM':
                key_type = SSL_FILETYPE_PEM
            else:
                raise CommandLineParsingError(
                    '--keyform should be DER or PEM.')

            # Let's try to open the cert and key files
            try:
                client_auth_creds = ClientAuthenticationCredentials(
                    args_command_list.cert, args_command_list.key, key_type,
                    args_command_list.keypass)
            except ValueError as e:
                raise CommandLineParsingError(
                    'Invalid client authentication settings: {}.'.format(e[0]))

        # HTTP CONNECT proxy
        http_tunneling_settings = None
        if args_command_list.https_tunnel:
            try:
                http_tunneling_settings = HttpConnectTunnelingSettings.from_url(
                    args_command_list.https_tunnel)
            except ValueError as e:
                raise CommandLineParsingError(
                    'Invalid proxy URL for --https_tunnel: {}.'.format(e[0]))

        # STARTTLS
        tls_wrapped_protocol = TlsWrappedProtocolEnum.PLAIN_TLS
        if args_command_list.starttls:
            if args_command_list.starttls not in self.START_TLS_PROTOCOLS:
                raise CommandLineParsingError(self.START_TLS_USAGE)
            else:
                # StartTLS was specified
                if args_command_list.starttls in self.STARTTLS_PROTOCOL_DICT.keys(
                ):
                    # Protocol was given in the command line
                    tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[
                        args_command_list.starttls]

        # Number of connection retries
        if args_command_list.nb_retries < 1:
            raise CommandLineParsingError(
                'Cannot have a number smaller than 1 for --nb_retries.')

        # Create the server connectivity info for each specifed servers
        # A limitation when using the command line is that only one client_auth_credentials and http_tunneling_settings
        # can be specified, for all the servers to scan
        good_server_list = []
        bad_server_list = []
        for server_string in args_target_list:
            # Support unicode domains
            server_string = unicode(server_string, 'utf-8')
            try:
                good_server_list.append(
                    ServerConnectivityInfo.from_command_line(
                        server_string=server_string,
                        tls_wrapped_protocol=tls_wrapped_protocol,
                        tls_server_name_indication=args_command_list.sni,
                        xmpp_to_hostname=args_command_list.xmpp_to,
                        client_auth_credentials=client_auth_creds,
                        http_tunneling_settings=http_tunneling_settings))
            except ServerConnectivityError as e:
                # Will happen for example if the DNS lookup failed or the server string is malformed
                bad_server_list.append(FailedServerScan(server_string, e))
            except ValueError as e:
                # Will happen for example if xmpp_to is specified for a non-XMPP connection
                raise CommandLineParsingError(e[0])

        # Command line hacks
        # Handle --starttls=auto now that we parsed the server strings
        if args_command_list.starttls == 'auto':
            for server_info in good_server_list:
                # We use the port number to deduce the protocol
                if server_info.port in self.STARTTLS_PROTOCOL_DICT.keys():
                    server_info.tls_wrapped_protocol = self.STARTTLS_PROTOCOL_DICT[
                        server_info.port]

        # Handle --http_get now that we parsed the server strings
        # Doing it here is hacky as the option is defined within PluginOpenSSLCipherSuites
        if args_command_list.http_get:
            for server_info in good_server_list:
                if server_info.port == 443:
                    server_info.tls_wrapped_protocol = TlsWrappedProtocolEnum.HTTPS

        return good_server_list, bad_server_list, args_command_list
Example #5
0
def main():
    # For py2exe builds
    freeze_support()

    # Handle SIGINT to terminate processes
    signal.signal(signal.SIGINT, sigint_handler)

    start_time = time()

    # Retrieve available plugins
    sslyze_plugins = PluginsFinder()
    available_plugins = sslyze_plugins.get_plugins()
    available_commands = sslyze_plugins.get_commands()

    # Create the command line parser and the list of available options
    sslyze_parser = CommandLineParser(available_plugins, __version__)
    try:
        good_server_list, bad_server_list, args_command_list = sslyze_parser.parse_command_line()
    except CommandLineParsingError as e:
        print e.get_error_msg()
        return

    output_hub = OutputHub()
    output_hub.command_line_parsed(available_plugins, args_command_list)


    # Initialize the pool of processes that will run each plugin
    if args_command_list.https_tunnel:
        # Maximum one process to not kill the proxy
        plugins_process_pool = PluginsProcessPool(sslyze_plugins, args_command_list.nb_retries,
                                                  args_command_list.timeout, max_processes_nb=1)
    else:
        plugins_process_pool = PluginsProcessPool(sslyze_plugins, args_command_list.nb_retries,
                                                  args_command_list.timeout)


    # Figure out which hosts are up and fill the task queue with work to do
    connectivity_tester = ServersConnectivityTester(good_server_list)
    connectivity_tester.start_connectivity_testing(network_timeout=args_command_list.timeout)

    # Store and print server whose command line string was bad
    for failed_scan in bad_server_list:
        output_hub.server_connectivity_test_failed(failed_scan)

    # Store and print servers we were able to connect to
    online_servers_list = []
    for server_connectivity_info in connectivity_tester.get_reachable_servers():
        online_servers_list.append(server_connectivity_info)
        output_hub.server_connectivity_test_succeeded(server_connectivity_info)

        # Send tasks to worker processes
        for plugin_command in available_commands:
            if getattr(args_command_list, plugin_command):
                # Get this plugin's options if there's any
                plugin_options_dict = {}
                for option in available_commands[plugin_command].get_interface().get_options():
                    # Was this option set ?
                    if getattr(args_command_list,option.dest):
                        plugin_options_dict[option.dest] = getattr(args_command_list, option.dest)

                plugins_process_pool.queue_plugin_task(server_connectivity_info, plugin_command, plugin_options_dict)


    # Store and print servers we were NOT able to connect to
    for tentative_server_info, exception in connectivity_tester.get_invalid_servers():
        failed_scan = FailedServerScan(tentative_server_info.server_string, exception)
        output_hub.server_connectivity_test_failed(failed_scan)


    # Keep track of how many tasks have to be performed for each target
    task_num = 0
    output_hub.scans_started()
    for command in available_commands:
        if getattr(args_command_list, command):
            task_num += 1


    # Each host has a list of results
    result_dict = {}
    # We cannot use the server_info object directly as its address will change due to multiprocessing
    RESULT_KEY_FORMAT = u'{hostname}:{ip_address}:{port}'.format
    for server_info in online_servers_list:
        result_dict[RESULT_KEY_FORMAT(hostname=server_info.hostname, ip_address=server_info.ip_address,
                                      port=server_info.port)] = []

    # Process the results as they come
    for plugin_result in plugins_process_pool.get_results():
        server_info = plugin_result.server_info
        result_dict[RESULT_KEY_FORMAT(hostname=server_info.hostname, ip_address=server_info.ip_address,
                                      port=server_info.port)].append(plugin_result)

        plugin_result_list = result_dict[RESULT_KEY_FORMAT(hostname=server_info.hostname,
                                                           ip_address=server_info.ip_address,
                                                           port=server_info.port)]

        if len(plugin_result_list) == task_num:
            # Done with this server; send the result to the output hub
            output_hub.server_scan_completed(CompletedServerScan(server_info, plugin_result_list))


    # All done
    exec_time = time()-start_time
    output_hub.scans_completed(exec_time)