Ejemplo n.º 1
0
    def __parse_section_config(self, service, service_config):
        """
        Parse section [config] in <service_name>.conf, retrieve basic info about service
        (default port/protocol) and retrieve list of supported categories of checks for
        this service.

        :param str service: Service name
        :param defaultdict(str) service_config: Information about the service, updated 
            into this method
        :return: List of categories of checks
        :rtype: list(str)
        :raises SettingsException: Exception raised if any unrecoverable error is 
            encountered while parsing the section
        """
        log_prefix = '[{filename}{ext} | Section "config"]'.format(
            filename=service, ext=CONF_EXT)

        # Check presence of mandatory options in [config]
        optparsed = self.config_parsers[service].options('config')
        for opt in SERVICE_CHECKS_CONFIG_OPTIONS[MANDATORY]:
            if opt not in optparsed:
                raise SettingsException('{prefix} Missing mandatory option "{option}"' \
                    ', check the file'.format(prefix=log_prefix, option=opt))

        # Get port number
        default_port = self.config_parsers[service].safe_get_int(
            'config', 'default_port', None, None)

        if default_port is None or default_port < 0 or default_port > 65535:
            raise SettingsException('{prefix} Invalid value for option "default_port",' \
                ' must be in the range [0-65535]'.format(prefix=log_prefix))

        # Get protocol
        protocol = self.config_parsers[service].safe_get_lower(
            'config', 'protocol', 'tcp', ['tcp', 'udp'])

        # Get categories of checks as a list, clean each element
        categories = list(map(lambda x: StringUtils.clean(
            x.lower(), allowed_specials=('-', '_')), 
            self.config_parsers[service].safe_get_list('config', 'categories', ',', [])))

        if not categories:
            raise SettingsException('{prefix} Option "categories" must have at least '\
                'one category'.format(prefix=log_prefix))

        # Get authentication type (for HTTP) as a list, clean each element
        if 'auth_types' in optparsed:
            auth_types = list(map(lambda x: StringUtils.clean(
                x.lower(), allowed_specials=('-', '_')),
                self.config_parsers[service].safe_get_list(
                    'config', 'auth_types', ',', [])))
        else:
            auth_types = None

        # Update service configuration with parsed information
        service_config['default_port'] = default_port
        service_config['protocol']     = protocol
        service_config['auth_types']   = auth_types

        return categories
Ejemplo n.º 2
0
    def show(self):
        """Display selected hosts"""
        results = self.get_results()

        if not results:
            logger.warning('No host to display')
        else:
            data = list()
            columns = [
                'IP',
                'Hostname',
                'OS',
                'Type',
                'Vendor',
                'Comment',
                'TCP',
                'UDP',
            ]
            for r in results:
                data.append([
                    r.ip,
                    StringUtils.wrap(r.hostname, 45) if r.hostname != str(r.ip) else '',
                    StringUtils.wrap(r.os, 50),
                    r.type,
                    StringUtils.wrap(r.vendor, 30),
                    StringUtils.shorten(r.comment, 40),
                    r.get_nb_services(Protocol.TCP),
                    r.get_nb_services(Protocol.UDP),       
                ])
            Output.table(columns, data, hrules=False)
Ejemplo n.º 3
0
    def show(self):
        """Display selected credentials"""
        results = self.get_results()

        if not results:
            logger.warning('No credential to display')
        else:
            data = list()
            columns = [
                'IP',
                'Hostname',
                'Service',
                'Port',
                'Proto',
                'Type',
                'Username',
                'Password',
                'URL',
                'Comment',
            ]
            for r in results:
                data.append([
                    r.service.host.ip,
                    r.service.host.hostname \
                        if r.service.host.hostname != str(r.service.host.ip) else '',
                    r.service.name,
                    r.service.port,
                    {Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(r.service.protocol),
                    r.type or '',
                    '<empty>' if r.username == '' else r.username,
                    {'': '<empty>', None: '<???>'}.get(r.password, r.password),
                    StringUtils.wrap(r.service.url, 50),
                    StringUtils.wrap(r.comment, 50),
                ])
            Output.table(columns, data, hrules=False)
Ejemplo n.º 4
0
    def show(self):
        """Display selected services"""
        results = self.get_results()

        if not results:
            logger.warning('No service to display')
        else:
            data = list()
            columns = [
                'id',
                'IP',
                #'Hostname',
                'Port',
                'Proto',
                'Service',
                'Banner',
                'URL',
                'Comment/Title',
                'Checks',
                'Creds',
                'Vulns',
            ]
            for r in results:
                # Creds numbers
                nb_userpass = r.get_nb_credentials(single_username=False)
                nb_usernames = r.get_nb_credentials(single_username=True)
                nb_creds = '{}{}{}'.format(
                    '{}'.format(Output.colored(str(nb_userpass),  color='green' \
                            if nb_userpass > 0 else None)) if nb_userpass > 0 else '',
                    '/' if nb_userpass > 0 and nb_usernames > 0 else '',
                    '{} usr'.format(Output.colored(str(nb_usernames), color='yellow' \
                            if nb_usernames > 0 else None)) if nb_usernames > 0 else '')
                nb_vulns = Output.colored(str(len(r.vulns)), color='green' \
                    if len(r.vulns) > 0 else None) if len(r.vulns) > 0 else ''

                # Col "Comment/Title" (title is for HTML title for HTTP)
                if r.html_title:
                    comment = r.html_title
                else:
                    comment = r.comment

                data.append([
                    r.id,
                    r.host.ip,
                    #r.host.hostname,
                    r.port,
                    {
                        Protocol.TCP: 'tcp',
                        Protocol.UDP: 'udp'
                    }.get(r.protocol),
                    r.name,
                    StringUtils.wrap(r.banner, 55),
                    StringUtils.wrap(r.url, 50),
                    StringUtils.shorten(comment, 40),
                    len(r.results),
                    nb_creds,
                    nb_vulns,
                ])
            Output.table(columns, data, hrules=False)
Ejemplo n.º 5
0
    def __parse_section_products(self, service, service_config):
        """
        Parse section [products] in <service_name>.conf and retrieve supported values 
        for each product type.

        :param str service: Service name
        :param dict service_config: Service configuration, updated into this method
        :return: None
        :raises SettingsException: Exception raised if unconsistent values detected
        """

        # First, check if config file has a [products] section
        if not self.config_parsers[service].has_section('products'):
            service_config['products'] = dict()
            return

        log_prefix = '[{filename}{ext} | Section "products"]'.format(
            filename=service, ext=CONF_EXT)

        products = dict()
        optparsed = self.config_parsers[service].options('products')

        # Loop over product types in [products]
        for product_type in optparsed:

            # Clean the product type
            product_type = StringUtils.clean(product_type.lower(),
                                             allowed_specials=('-', '_'))

            # Get supported product names as a list.
            # Only some special chars allowed, spaces allowed
            # '/' is used to separate vendor name (optional) and product name
            product_names = self.config_parsers[service].safe_get_list(
                'products', product_type, ',', [])
            product_names = list(
                map(
                    lambda x: StringUtils.clean(
                        x, allowed_specials=('-', '_', '.', '/', '\\', ' ')),
                    product_names))

            if not product_names:
                raise SettingsException(
                    '{prefix} Option "{option}" is empty'.format(
                        prefix=log_prefix, option=opt))

            products[product_type] = product_names

        # Update service configuration with supported products
        service_config['products'] = products
        return
 def show_summary(self):
     """
     """
     data = list()
     columns = [
         'id',
         'IP',
         'Hostname',
         'Port',
         'Proto',
         'Service',
         'Banner',
         'URL',
     ]
     id_ = 1
     for target in self.targets:
         pointer_color = 'blue' if self.current_targetid == id_ else None
         pointer_attr = 'bold' if self.current_targetid == id_ else None
         data.append([
             Output.colored(
                 '>' +
                 str(id_) if self.current_targetid == id_ else str(id_),
                 color=pointer_color,
                 attrs=pointer_attr),
             Output.colored(target.get_ip(),
                            color=pointer_color,
                            attrs=pointer_attr),
             Output.colored(target.get_host(),
                            color=pointer_color,
                            attrs=pointer_attr),
             Output.colored(str(target.get_port()),
                            color=pointer_color,
                            attrs=pointer_attr),
             Output.colored(target.get_protocol(),
                            color=pointer_color,
                            attrs=pointer_attr),
             Output.colored(target.get_service_name(),
                            color=pointer_color,
                            attrs=pointer_attr),
             Output.colored(StringUtils.wrap(target.get_banner(), 70),
                            color=pointer_color,
                            attrs=pointer_attr),
             Output.colored(StringUtils.wrap(target.get_url(), 50),
                            color=pointer_color,
                            attrs=pointer_attr),
         ])
         id_ += 1
     Output.table(columns, data, hrules=False)
Ejemplo n.º 7
0
    def show(self):
        """Display selected vulnerabilities"""
        results = self.get_results()

        if not results:
            logger.warning('No vulnerability to display')
        else:
            data = list()
            columns = [
                'IP',
                'Service',
                'Port',
                'Proto',
                'Vulnerability',
            ]
            for r in results:
                data.append([
                    r.service.host.ip,
                    r.service.name,
                    r.service.port,
                    {
                        Protocol.TCP: 'tcp',
                        Protocol.UDP: 'udp'
                    }.get(r.service.protocol),
                    StringUtils.wrap(r.name, 140),
                ])
            Output.table(columns, data, hrules=False)
Ejemplo n.º 8
0
    def show_products(self, filter_service=None):
        """
        Display supported products in a table

        :param list filter_service: Filter on services (default: all)
        """
        data = list()
        columns = [
            'Type',
            'Product Names',
        ]
        services = self.list_services() if filter_service is None else [
            filter_service
        ]
        for service in services:
            products = self.services[service]['products']
            for product_type in products:
                names = sorted(
                    self.services[service]['products'][product_type])
                names = StringUtils.wrap(', '.join(names), 100)

                data.append([product_type, names])

        Output.title1('Available products for {filter}'.format(
            filter='all services' if filter_service is None \
                   else 'service ' + filter_service))

        if not data:
            logger.warning('No product')
        else:
            Output.table(columns, data)
Ejemplo n.º 9
0
    def show_categories(self, filter_service=None):
        """
        Show list of categories of checks for the given service or all services
        :param filter_service: None or given service
        :return: None
        """
        data = list()
        columns = [
            'Category',
            'Services',
        ]
        services = self.list_services() if filter_service is None else [
            filter_service
        ]
        svcbycat = defaultdict(list)
        for service in services:
            for category in self.services[service]['checks'].categories:
                svcbycat[category].append(service)

        for category in svcbycat:
            data.append([
                category,
                StringUtils.wrap(', '.join(svcbycat[category]), 100)
            ])

        Output.table(columns, data)
Ejemplo n.º 10
0
 def show(self, highlight=None):
     """
     Display selected missions.
     :param str highlight: Name of the mission to highlight
     """
     results = self.get_results()
     if not results:
         logger.warning('No matching mission')
     else:
         data = list()
         columns = [
             'Mission',
             'Creation date',
             'Comment',
             '# Hosts',
             '# Services',
         ]
         for mission in results:
             color = 'light_green' if mission.name == highlight else None
             data.append([
                 Output.colored(mission.name, color=color),
                 Output.colored(str(mission.creation_date), color=color),
                 Output.colored(StringUtils.wrap(mission.comment, 50), color=color),
                 Output.colored(len(mission.hosts), color=color),
                 Output.colored(mission.get_nb_services(), color=color),                
             ])
         Output.table(columns, data, hrules=False)
Ejemplo n.º 11
0
    def show(self, filter_service=None):
        """
        Display information about supported attack profiles

        :param str filter_service: Service name to filter with (default: no filter)
        """

        data = list()
        columns = [
            'Profile',
            'Description',
        ]

        for p in self.profiles:
            #print(p.checks)
            if not filter_service or p.is_service_supported(filter_service):
                data.append([
                    Output.colored(p.name, attrs='bold'),
                    StringUtils.wrap(p.description, 120)
                ])

        if filter_service:
            service = 'for service {}'.format(filter_service.upper())
        else:
            service = ''
        Output.title1('Attack Profiles {service}'.format(service=service))
        Output.table(columns, data, hrules=False)

        if not filter_service:
            print
            Output.print('Run "info --attack-profiles <service>" to see the attack ' \
                'profiles supported for a given service.')
Ejemplo n.º 12
0
    def __generate_sidebar_checks(self, service):
        """
        Generate the sidebar with the list of checks that have been run for the
        specified service.

        :param Service service: Service Model
        """
        req = ResultsRequester(self.sqlsession)
        req.select_mission(self.mission)

        # Filter on service id
        filter_ = Filter(FilterOperator.AND)
        filter_.add_condition(Condition(service.id, FilterData.SERVICE_ID))
        req.add_filter(filter_)
        results = req.get_results()

        html = ''
        i = 0
        for r in results:

            # Icon category
            icon = IconsMapping.get_icon_html('category', r.category)

            html += """
            <li{class_}>
                <a href="#{id}">{icon}{check}</a>
            </li>  
            """.format(class_=' class="active"' if i == 0 else '',
                       id=r.check,
                       icon=icon,
                       check=StringUtils.shorten(r.check, 28))
            i += 1

        return html
Ejemplo n.º 13
0
    def __parse_section_specific_options(self, service, service_config):
        """
        Parse section [specific_options] in <service_name>.conf and update service config
        :param service: Service name
        :param service_config: Dict storing info about service, updated into this method
        :return: None
        :raises SettingsException:
        """
        try:
            optparsed = self.config_parsers[service].options('specific_options')
        except configparser.NoSectionError:
            service_config['specific_options'] = dict()
            return 
        specific_options = dict()
        for opt in optparsed:
            option_type = self.config_parsers[service].safe_get_lower('specific_options', opt, None, None)
            if option_type.count(':') == 1:
                option_type, default_value = option_type.split(':')
            opt_clean   = StringUtils.clean(opt.lower(), allowed_specials=('-', '_'))

            if option_type == 'boolean' :  specific_options[opt_clean] = OptionType.BOOLEAN
            elif option_type == 'list'  :  specific_options[opt_clean] = OptionType.LIST
            elif option_type == 'var'   :  specific_options[opt_clean] = OptionType.VAR
            else:
                raise SettingsException('[{filename}{ext} | Section "specific_options"] Specific option named "{option}" has ' \
                    'an invalid type. Supported types are: boolean, list, var'.format(
                        filename = service, ext=CONF_EXT, option=opt))

        service_config['specific_options'] = specific_options
Ejemplo n.º 14
0
    def __parse_section_supported_list_options(self, service, service_config):
        """
        Parse section [supported_list_options] in <service_name>.conf and update service 
        configuration with supported values for specific options of type list.
        Must be called after self.__parse_section_config() and 
        self.__parse_section_specific_options().

        :param defaultdict(str) service_config: Information about the service, updated 
            into this method
        :return: None
        :raises SettingsException: Exception raised if any unrecoverable error is 
            encountered while parsing the section
        """

        # Get names of specific options of type list
        options_list = list(
            filter(
                lambda x: service_config['specific_options'][x] == OptionType.
                LIST, service_config['specific_options'].keys()))

        if not options_list:
            return
        elif not self.config_parsers[service].has_section(
                'supported_list_options'):
            raise SettingsException('[{filename}{ext}] Missing section ' \
                '[supported_list_options] to store supported values for specific ' \
                'options of type "list"'.format(filename=service, ext=CONF_EXT))

        log_prefix = '[{filename}{ext} | Section "supported_list_options"]'.format(
            filename=service, ext=CONF_EXT)

        supported_list_options = dict()
        optparsed = self.config_parsers[service].options(
            'supported_list_options')

        # Loop over specific options of type list
        for opt in options_list:

            # If missing option
            if 'supported_' + opt not in optparsed:
                raise SettingsException('{prefix} No option "supported_{option}" ' \
                    'is defined'.format(prefix=log_prefix, option=opt))

            # Values are put in lowercase, no spaces, no special chars (except -, _)
            values = list(
                map(
                    lambda x: StringUtils.clean(x.lower(),
                                                allowed_specials=('-', '_')),
                    self.config_parsers[service].safe_get_list(
                        'supported_list_options', 'supported_' + opt, ',',
                        [])))

            if not values:
                raise SettingsException('{prefix} Option "supported_{option}" is ' \
                    'empty'.format(prefix=log_prefix, option=opt))

            supported_list_options[opt] = values

        # Update service configuration with lists of supported values
        service_config['supported_list_options'] = supported_list_options
    def show(self):
        results = self.get_results()

        if not results:
            logger.warning('No service to display')
        else:
            data = list()
            columns = [
                'id',
                'IP',
                #'Hostname',
                'Port',
                'Proto',
                'Service',
                'Banner',
                'URL',
                'Comment',
                'Checks',
                'Creds',
            ]
            for r in results:
                nb_userpass = r.get_nb_credentials(single_username=False)
                nb_usernames = r.get_nb_credentials(single_username=True)
                nb_creds = '{}{}{}'.format(
                    '{}'.format(Output.colored(str(nb_userpass),  color='green' \
                            if nb_userpass > 0 else None)) if nb_userpass > 0 else '',
                    '/' if nb_userpass > 0 and nb_usernames > 0 else '',
                    '{} users'.format(Output.colored(str(nb_usernames), color='yellow' \
                            if nb_usernames > 0 else None)) if nb_usernames > 0 else '')

                data.append([
                    r.id,
                    r.host.ip,
                    #r.host.hostname,
                    r.port,
                    {
                        Protocol.TCP: 'tcp',
                        Protocol.UDP: 'udp'
                    }.get(r.protocol),
                    r.name,
                    StringUtils.wrap(r.banner, 65),
                    StringUtils.wrap(r.url, 50),
                    StringUtils.wrap(r.comment, 50),
                    len(r.results),
                    nb_creds,
                ])
            Output.table(columns, data, hrules=False)
Ejemplo n.º 16
0
    def grab_html_title(url):
        """Return HTML title from an URL"""
        try:
            r = requests.get(url, verify=False)
            html = bs4.BeautifulSoup(r.text, 'html.parser')

            # Remove non-ASCII characters and duplicate spaces
            title = StringUtils.remove_non_printable_chars(
                html.title.text.strip())
            title = " ".join(title.split())

            # Shorten if necessary
            title = StringUtils.shorten(title, 250)

            return title
        except:
            return ''
Ejemplo n.º 17
0
    def show_search_results(self, string, nb_words=12):
        """
        Display command outputs search results.
        For good readability, only some words surrounding the search string are 
        displayed.

        :param str string: Search string (accepts wildcard "%")
        :param int nb_words: Number of words surrounding the search string to show
        """
        results = self.query.filter(CommandOutput.output.ilike('%'+string+'%'))
        if not results:
            logger.error('No result')
        else:
            Output.title2('Search results:')

            data = list()
            columns = [
                'IP',
                'Port',
                'Proto',
                'Service',
                'Check id',
                'Category',
                'Check',
                'Matching text',
            ]
            for r in results:
                match = StringUtils.surrounding_text(r.outputraw, string, nb_words)
                # There might have several matches in one command result (one row
                # per match)
                for m in match:
                    data.append([
                        r.result.service.host.ip,
                        r.result.service.port,
                        {Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(
                            r.result.service.protocol),
                        r.result.service.name,
                        r.result.id,
                        r.result.category,
                        r.result.check,
                        StringUtils.wrap(m, 70),
                    ])

        print()
        Output.table(columns, data, hrules=False)
Ejemplo n.º 18
0
    def grab_html_title(url):
        try:
            r = requests.get(url, verify=False)
            html = bs4.BeautifulSoup(r.text, 'html.parser')

            return StringUtils.remove_non_printable_chars(
                html.title.text.strip())
        except:
            return ''
Ejemplo n.º 19
0
    def __parse_tool_options(self, section, tool_config):
        """
        Check and parse options from a given tool section
        :param section: Tool section into the toolbox settings file
        :param tool_config: A defaultdict(str) storing tool config which is updated into this method
        :return: Boolean indicating status
        """
        log_prefix = '[{filename}{ext} | Section "{section}"]'.format(
                        filename=TOOLBOX_CONF_FILE, ext=CONF_EXT, section=section)

        optparsed = self.config_parsers[TOOLBOX_CONF_FILE].options(section)
        for opt in TOOL_OPTIONS[MANDATORY]:
            if opt not in optparsed:
                logger.warning('{prefix} Missing mandatory option "{option}", tool is skipped'.format(
                    prefix=log_prefix, option=opt))
                return False

        tool_config['name_clean'] = section
        for opt in optparsed:
            if opt not in TOOL_OPTIONS[MANDATORY]+TOOL_OPTIONS[OPTIONAL]:
                logger.warning('{prefix} Option "{option}" is not supported, it will be ignored'.format(
                    prefix=log_prefix, option=opt))
                continue

            if opt in TOOL_OPTIONS[MANDATORY]:
                val = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None)
                if opt == 'name':
                    tool_config[opt]=StringUtils.clean(val, allowed_specials=['-', '_'])
                elif opt == 'description':
                    tool_config[opt] = val
                elif opt == 'target_service':
                    tool_config[opt] = val.lower()
                    if tool_config[opt] not in self.services.list_services(multi=True):
                        logger.warning('{prefix} Service specified in "target_service" is not supported, ' \
                            'tool is skipped'.format(prefix=log_prefix))
                        return False

                if not tool_config[opt]:
                    logger.warning('{prefix} Mandatory option "{option}" is empty, tool is skipped'.format(
                        prefix=log_prefix, option=opt))
                    return False



            elif opt == 'install':
                tool_config[opt] = Command(cmdtype = CMD_INSTALL, 
                                           cmdline = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None))

            elif opt == 'update':
                tool_config[opt] = Command(cmdtype = CMD_UPDATE,
                                           cmdline = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None))

            elif opt == 'check_command':
                tool_config[opt] = Command(cmdtype = CMD_CHECK,
                                           cmdline = self.config_parsers[TOOLBOX_CONF_FILE].safe_get(section, opt, '', None))

        return True
Ejemplo n.º 20
0
    def __parse_section_specific_options(self, service, service_config):
        """
        Parse section [specific_options] in <service_name>.conf and update service 
        configuration with supported specific options for the service and their
        respective types.

        :param str service: Service name
        :param defaultdict(str) service_config: Information about the service, updated 
            into this method
        :return: None
        :raises SettingsException: Exception raised if any unrecoverable error is 
            encountered while parsing the section
        """

        # Case when no [specific_options] can be found
        try:
            optparsed = self.config_parsers[service].options(
                'specific_options')
        except configparser.NoSectionError:
            service_config['specific_options'] = dict()
            return

        specific_options = dict()

        # Loop over supported specific options
        for opt in optparsed:
            # Get option type
            option_type = self.config_parsers[service].safe_get_lower(
                'specific_options', opt, None, None)

            # Handle case when default value is specified (for boolean)
            if option_type.count(':') == 1:
                option_type, default_value = option_type.split(':')

            opt_clean = StringUtils.clean(opt.lower(),
                                          allowed_specials=('-', '_'))

            if option_type == 'boolean':
                specific_options[opt_clean] = OptionType.BOOLEAN

            elif option_type == 'list':
                specific_options[opt_clean] = OptionType.LIST

            elif option_type == 'var':
                specific_options[opt_clean] = OptionType.VAR

            else:
                raise SettingsException('[{filename}{ext} | Section ' \
                    '"specific_options"]  Specific option named "{option}" has ' \
                    'an invalid type. Supported types are: boolean, list, var'.format(
                        filename = service, ext=CONF_EXT, option=opt))

        # Update service configuration with specific options names and types
        service_config['specific_options'] = specific_options
Ejemplo n.º 21
0
    def __generate_table_credentials(self):
        """
        Generate the table with all credentials registered in the mission 
        """

        req = CredentialsRequester(self.sqlsession)
        req.select_mission(self.mission)
        credentials = req.get_results()

        if len(credentials) == 0:
            html = """
            <tr class="notfound">
                <td colspan="10">No record found</td>
            </tr>
            """
        else:
            html = ''
            for cred in credentials:

                html += """
                <tr>
                    <td>{ip}</td>
                    <td>{hostname}</td>
                    <td>{service}</td>
                    <td>{port}</td>
                    <td>{proto}</td>
                    <td>{type}</td>
                    <td class="font-weight-bold">{username}</td>
                    <td class="font-weight-bold">{password}</td>
                    <td>{url}</td>
                    <td>{comment}</td>
                </tr>
                """.format(
                    ip=cred.service.host.ip,
                    hostname=cred.service.host.hostname \
                        if cred.service.host.hostname != str(cred.service.host.ip)\
                        else '',
                    service=cred.service.name,
                    port=cred.service.port,
                    proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(
                        cred.service.protocol),
                    type=cred.type or '',
                    username='******' if cred.username == '' else cred.username,
                    password={'': '<empty>', None: '<???>'}.get(
                        cred.password, cred.password),
                    url='<a href="{}" title="{}">{}</a>'.format(
                        cred.service.url, cred.service.url,
                        StringUtils.shorten(cred.service.url, 50)) \
                            if cred.service.url else '',
                    comment=cred.comment)

        return html
Ejemplo n.º 22
0
    def __parse_section_config(self, service, service_config):
        """
        Parse section [config] in <service_name>.conf, retrieve basic info about service
        (default port/protocol) and retrieve list of categories.
        :param service: Service name
        :param service_config: Dict storing info about service, updated into this method
        :return: List of categories of checks
        :raises SettingsException:
        """
        log_prefix = '[{filename}{ext} | Section "config"]'.format(filename=service, ext=CONF_EXT)

        optparsed = self.config_parsers[service].options('config')
        for opt in SERVICE_CHECKS_CONFIG_OPTIONS[MANDATORY]:
            if opt not in optparsed:
                raise SettingsException('{prefix} Missing mandatory option "{option}", check the file'.format(
                    prefix=log_prefix, option=opt))

        default_port = self.config_parsers[service].safe_get_int('config', 'default_port', None, None)
        protocol     = self.config_parsers[service].safe_get_lower('config', 'protocol', 'tcp', ['tcp', 'udp'])
        categories   = list(map(lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')),
                           self.config_parsers[service].safe_get_list('config', 'categories', ',', [])))
        auth_types   = list(map(lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')),
                           self.config_parsers[service].safe_get_list('config', 'auth_types', ',', []))) \
                       if 'auth_types' in optparsed else None

        if default_port is None or default_port < 0 or default_port > 65535:
            raise SettingsException('{prefix} Invalid value for option "default_port", must be in the range ' \
                '[0-65535]'.format(prefix=log_prefix))

        if not categories:
            raise SettingsException('{prefix} Option "categories" must have at least one category'.format(
                prefix=log_prefix))

        service_config['default_port'] = default_port
        service_config['protocol']     = protocol
        service_config['auth_types']   = auth_types
        return categories
Ejemplo n.º 23
0
 def delete(self):
     """Delete selected vulnerabilities"""
     results = self.get_results()
     if not results:
         logger.error('No matching vulnerability')
     else:
         for r in results:
             logger.info('Vulnerability deleted: "{vuln}" for service={service} ' \
                 'host={ip} port={port}/{proto}'.format(
                     vuln=StringUtils.shorten(r.name, 50),
                     service=r.service.name,
                     ip=r.service.host.ip,
                     port=r.service.port,
                     proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(
                         r.service.protocol)))
             self.sqlsess.delete(r)
         self.sqlsess.commit()
Ejemplo n.º 24
0
    def show_toolbox(self, filter_service=None):
        """
        Display a table showing the content of the toolbox.

        :param str filter_service: Service name to filter with (default: no filter)
        """
        if filter_service is not None and filter_service not in self.services:
            return

        data = list()
        columns = [
            'Name',
            'Service',
            'Status/Update',
            'Description',
        ]

        services = self.services if filter_service is None else [
            filter_service
        ]
        for service in services:
            for tool in self.tools[service]:

                # Install status style
                if tool.installed:
                    status = Output.colored('OK | ' +
                                            tool.last_update.split(' ')[0],
                                            color='green')
                else:
                    status = Output.colored('Not installed', color='red')

                # Add line for the tool
                data.append([
                    tool.name,
                    tool.target_service,
                    status,
                    StringUtils.wrap(tool.description, 120),  # Max line length
                ])

        Output.title1('Toolbox content - {filter}'.format(
            filter='all services' if filter_service is None \
                   else 'service ' + filter_service))

        Output.table(columns, data, hrules=False)
Ejemplo n.º 25
0
    def detect_vulns(self):
        """
        Detect vulnerability from command output
        Important: A command output might contain several vulnerabilities with the 
        same pattern.
        """
        if self.service.name in vulns_match.keys():

            if self.tool_name in vulns_match[self.service.name].keys():
                p = vulns_match[self.service.name][self.tool_name]

                for pattern in p.keys():

                    logger.debug('Search for vulns pattern: {pattern}'.format(
                        pattern=pattern))

                    # Important: Multiple search/match
                    #m = re.search(pattern, self.cmd_output, re.IGNORECASE)
                    try:
                        mall = re.finditer(pattern, self.cmd_output,
                                           re.IGNORECASE | re.MULTILINE)
                    except Exception as e:
                        logger.warning('Error with matchstring [{pattern}], you ' \
                            'should review it. Exception: {exception}'.format(
                                pattern=pattern, exception=e))
                        break

                    # Process each match
                    if mall:
                        for m in mall:
                            name = self.__replace_tokens_from_matchobj(
                                p[pattern], m)
                            if name is None:
                                continue

                            # Add vulnerability to context
                            logger.debug('Vuln pattern matches')
                            self.cu.add_vuln(
                                StringUtils.remove_non_printable_chars(name))
Ejemplo n.º 26
0
    def show_specific_options(self, filter_service=None):
        """
        Display supported specific options in a table.

        :param list filter_service: Filter on services (default: all)
        """
        data = list()
        columns = [
            'Option',
            'Service',
            'Supported values',
        ]
        services = self.list_services() if filter_service is None else [
            filter_service
        ]
        for service in services:
            options = self.services[service]['specific_options']
            for opt in options:
                if options[opt] == OptionType.BOOLEAN:
                    values = 'true, false'

                elif options[opt] == OptionType.LIST:
                    values = sorted(
                        self.services[service]['supported_list_options'][opt])
                    values = StringUtils.wrap(', '.join(values), 80)

                else:
                    values = '<anything>'
                data.append([opt, service, values])

        Output.title1('Available context-specific options for {filter}'.format(
            filter='all services' if filter_service is None \
                   else 'service ' + filter_service))

        if not data:
            logger.warning('No specific option')
        else:
            Output.table(columns, data, hrules=False)
Ejemplo n.º 27
0
    def __parse_section_supported_list_options(self, service, service_config):
        """
        Parse section [supported_list_options] in <service_name>.conf and retrieve 
        supported values for specific options of type list.
        Must be called after self.__parse_section_config() and self.__parse_section_specific_options()
        :param service: Service name
        :param service_config: Dict storing info about service, updated into this method
        :return: None
        :raises SettingsException:
        """
        options_list = list(filter(lambda x: service_config['specific_options'][x] == OptionType.LIST, 
                                   service_config['specific_options'].keys()))
        if not options_list:
            return dict()
        elif not self.config_parsers[service].has_section('supported_list_options'):
            raise SettingsException('[{filename}{ext}] Missing section [supported_list_options] to store supported ' \
                'values for specific options of type list'.format(filename=service, ext=CONF_EXT))

        log_prefix = '[{filename}{ext} | Section "supported_list_options"]'.format(filename=service, ext=CONF_EXT)
        supported_list_options = dict()
        optparsed = self.config_parsers[service].options('supported_list_options')

        for opt in options_list:
            if 'supported_'+opt not in optparsed:
                raise SettingsException('{prefix} No option "supported_{option}" is defined'.format(
                    prefix=log_prefix, option=opt))

            values = list(map(lambda x: StringUtils.clean(x.lower(), allowed_specials=('-', '_')), 
                         self.config_parsers[service].safe_get_list('supported_list_options', 
                         'supported_'+opt, ',', [])))
            if not values:
                raise SettingsException('{prefix} Option "supported_{option}" is empty'.format(
                    prefix=log_prefix, option=opt))
            supported_list_options[opt] = values

        service_config['supported_list_options'] = supported_list_options
Ejemplo n.º 28
0
    def __generate_table_credentials(self):
        """
        Generate the table with all credentials registered in the mission 
        """

        req = CredentialsRequester(self.sqlsession)
        req.select_mission(self.mission)
        credentials = req.get_results()

        if len(credentials) == 0:
            html = """
            <tr class="notfound">
                <td colspan="9">No record found</td>
            </tr>
            """
        else:
            html = ''
            for cred in credentials:

                # Service name
                service_name = IconsMapping.get_icon_html(
                    'service', cred.service.name)
                service_name += str(cred.service.name)

                # Add color to username/password
                username = '******' if cred.username == '' else cred.username
                username = '******'.format(
                    color='green' if cred.password is not None else 'yellow',
                    username=username)

                password = {
                    '': '&lt;empty&gt;',
                    None: '&lt;???&gt;'
                }.get(cred.password, cred.password)
                password = '******'.format(
                    color='green' if cred.password is not None else 'yellow',
                    password=password)

                html += """
                <tr>
                    <td>{ip}</td>
                    <td>{hostname}</td>
                    <td>{service}</td>
                    <td>{port} /{proto}</td>
                    <td>{type}</td>
                    <td class="font-weight-bold">{username}</td>
                    <td class="font-weight-bold">{password}</td>
                    <td>{url}</td>
                    <td>{comment}</td>
                </tr>
                """.format(
                    ip=cred.service.host.ip,
                    hostname=cred.service.host.hostname \
                        if cred.service.host.hostname != str(cred.service.host.ip)\
                        else '',
                    service=service_name,
                    port=cred.service.port,
                    proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(
                        cred.service.protocol),
                    type=cred.type or '',
                    username=username,
                    password=password,
                    url='<a href="{}" title="{}">{}</a>'.format(
                        cred.service.url, cred.service.url,
                        StringUtils.shorten(cred.service.url, 50)) \
                            if cred.service.url else '',
                    comment=cred.comment)

        return html
Ejemplo n.º 29
0
    def run(self):

        # Create report directory
        dirname = '{mission}-{datetime}'.format(
            mission=StringUtils.clean(self.mission.replace(' ', '_'),
                                      allowed_specials=('_', '-')),
            datetime=datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
        self.output_path = self.output_path + '/' + dirname

        if not FileUtils.create_directory(self.output_path):
            logger.error('Unable to create report directory: "{path}"'.format(
                path=self.output_path))
            return False

        # Retrieve all services in selected mission
        req = ServicesRequester(self.sqlsession)
        req.select_mission(self.mission)
        services = req.get_results()

        # Generate screenshots
        processor = ScreenshotsProcessor(self.mission, self.sqlsession)
        processor.run()

        screens_dir = self.output_path + '/screenshots'
        if not FileUtils.create_directory(screens_dir):
            logger.warning(
                'Unable to create screenshots directory: "{path}"'.format(
                    path=screens_dir))
        else:
            for service in services:
                if service.name == 'http' and service.screenshot is not None \
                        and service.screenshot.status == ScreenStatus.OK:

                    img_name = 'scren-{ip}-{port}-{id}'.format(
                        ip=str(service.host.ip),
                        port=service.port,
                        id=service.id)
                    path = screens_dir + '/' + img_name

                    ImageUtils.save_image(service.screenshot.image,
                                          path + '.png')
                    ImageUtils.save_image(service.screenshot.thumbnail,
                                          path + '.thumb.png')

        # Create index.html
        html = self.__generate_index()
        if FileUtils.write(self.output_path + '/index.html', html):
            logger.info('index.html file generated')
        else:
            logger.error('An error occured while generating index.html')
            return False

        # Create results-<service>.html (1 for each service)
        for service in services:
            # Useless to create page when no check has been run for the service
            if len(service.results) == 0:
                continue

            html = self.__generate_results_page(service)
            # Create a unique name for the service HTML file
            filename = 'results-{ip}-{port}-{service}-{id}.html'.format(
                ip=str(service.host.ip),
                port=service.port,
                service=service.name,
                id=service.id)
            if FileUtils.write(self.output_path + '/' + filename, html):
                logger.info(
                    '{filename} file generated'.format(filename=filename))
            else:
                logger.error(
                    'An error occured while generating {filename}'.format(
                        filename=filename))
                return False

        logger.success('HTML Report written with success in: {path}'.format(
            path=self.output_path))
        logger.info('Important: If running from Docker container, make sure to run ' \
            '"xhost +" on the host before')
        if Output.prompt_confirm('Would you like to open the report now ?',
                                 default=True):
            webbrowser.open(self.output_path + '/index.html')

        return True
Ejemplo n.º 30
0
    def __generate_table_web(self):
        """
        Generate the table with HTTP services registered in the mission
        """
        req = ServicesRequester(self.sqlsession)
        req.select_mission(self.mission)
        filter_ = Filter(FilterOperator.AND)
        filter_.add_condition(Condition('http', FilterData.SERVICE_EXACT))
        req.add_filter(filter_)
        services = req.get_results()

        if len(services) == 0:
            html = """
            <tr class="notfound">
                <td colspan="7">No record found</td>
            </tr>
            """
        else:
            html = ''

            # Unavailable thumbnail
            with open(REPORT_TPL_DIR + '/../img/unavailable.png', 'rb') as f:
                unavailable_b64 = base64.b64encode(f.read()).decode('ascii')

            for service in services:

                # Results HTML page name
                results = 'results-{ip}-{port}-{service}-{id}.html'.format(
                    ip=str(service.host.ip),
                    port=service.port,
                    service=service.name,
                    id=service.id)

                # Encrypted ? (SSL/TLS)
                enc = '<span class="mdi mdi-lock" title="SSL/TLS encrypted"></span>' \
                    if service.is_encrypted() else ''

                # Web technos (in a specific order)

                # try:
                #     technos = ast.literal_eval(service.web_technos)
                # except Exception as e:
                #     logger.debug('Error when retrieving "web_technos" field ' \
                #         'from db: {exc} for {service}'.format(
                #             exc=e, service=service))
                #     technos = list()

                # tmp = list()
                # for t in technos:
                #     tmp.append('{}{}{}'.format(
                #         t['name'],
                #         ' ' if t['version'] else '',
                #         t['version'] if t['version'] else ''))
                # webtechnos = ' | '.join(tmp)

                webtechnos = ''
                product_types = ('web-server', 'web-appserver', 'web-cms',
                                 'web-language', 'web-framework', 'web-jslib')
                for t in product_types:
                    product = service.get_product(t)
                    if product:
                        webtechnos += '<span class="badge badge-{type} badge-light">' \
                            '{name}{version}</span>'.format(
                                type=t,
                                name=product.name,
                                version=' '+str(product.version) \
                                    if product.version else '')

                # Web Application Firewall
                product = service.get_product('web-application-firewall')
                waf = ''
                if product:
                    waf = '<span class="badge badge-web-application-firewall ' \
                        'badge-light">{name}{version}</span>'.format(
                            name=product.name,
                            version=' '+str(product.version) \
                                if product.version else '')

                # Screenshot
                img_name = 'scren-{ip}-{port}-{id}'.format(ip=str(
                    service.host.ip),
                                                           port=service.port,
                                                           id=service.id)
                path = self.output_path + '/screenshots'

                if service.screenshot is not None \
                        and service.screenshot.status == ScreenStatus.OK \
                        and FileUtils.exists(path + '/' + img_name + '.png') \
                        and FileUtils.exists(path + '/' + img_name + '.thumb.png'):

                    screenshot = """
                    <a href="{screenlarge}" title="{url} - {title}" class="image-link">
                        <img src="{screenthumb}" class="border rounded">
                    </a>
                    """.format(url=service.url,
                               screenlarge='screenshots/' + img_name + '.png',
                               title=service.html_title,
                               screenthumb='screenshots/' + img_name +
                               '.thumb.png')

                else:
                    screenshot = """
                    <img src="data:image/png;base64,{unavailable}">
                    """.format(unavailable=unavailable_b64)

                # HTML for table row
                html += """
                <tr{clickable}>
                    <td>{url}</td>
                    <td>{enc}</td>
                    <td>{title}</td>
                    <td>{webtechnos}</td>
                    <td>{waf}</td>
                    <td>{screenshot}</td>
                    <td>{checks}</td>
                </tr>
                """.format(
                    clickable=' class="clickable-row" data-href="{results}"'.format(
                        results=results) if len(service.results) > 0 else '',
                    url='<a href="{}" title="{}">{}</a>'.format(
                        service.url, service.url, StringUtils.shorten(service.url, 50)) \
                        if service.url else '',
                    enc=enc,
                    title=StringUtils.shorten(service.html_title, 40),
                    webtechnos=webtechnos,
                    waf=waf,
                    screenshot=screenshot,
                    checks=len(service.results))

        return html