Exemplo n.º 1
0
    def __run_for_multi_targets(self, args):
        """Run attack against multiple targets from the database"""

        # Get Mission from which targets must be extracted
        mission = self.sqlsess.query(Mission)\
                    .filter(Mission.name == args.mission).first()
        if mission:
            logger.info('Extracting targets from mission "{mission}" ...'.format(
                mission=mission.name))
        else:
            raise AttackException('Mission {mission} does not exist into the ' \
                'database'.format(mission=args.mission))

        # Initialize Services requester and add filter if provided
        req = ServicesRequester(self.sqlsess)
        req.select_mission(args.mission)

        if args.filters_combined:
            for filt in args.filter:
                logger.info('Applying filters on mission scope: {filter}'.format(
                    filter=filt))
            if len(args.filter) > 1:
                logger.info('Note: Logical OR is applied between each filter')
            req.add_filter(args.filters_combined)

        # Retrieve targeted services from database
        services = req.get_results()
        if not services:
            raise AttackException('There is no matching service to target into the ' \
                'database')

        # Add each targeted service into Attack scope 
        for service in services:

            # Update credentials, options, products if specified in command-line
            if args.creds:
                for c in args.creds[service.name]: 
                    service.add_credential(c.clone())
            if args.users:
                for u in args.users[service.name]: 
                    service.add_credential(u.clone())
            if args.products:
                for p in args.products[service.name]: 
                    service.add_product(p.clone())
            if args.options:
                for o in args.options[service.name]: 
                    service.add_option(o.clone())

            # Initialize Target 
            try:
                target = Target(service, self.settings.services)
            except TargetException as e:
                logger.error(e)
                continue

            self.attack_scope.add_target(target)

        # Run the attack
        self.attack_scope.attack()
Exemplo n.º 2
0
    def run(self):

        # Extract HTTP services from the mission in database
        req = ServicesRequester(self.sqlsession)
        req.select_mission(self.mission_name)
        filter_ = Filter(FilterOperator.AND)
        filter_.add_condition(Condition('http', FilterData.SERVICE_EXACT))
        req.add_filter(filter_)
        services = req.get_results()

        if len(services) == 0:
            return

        logger.info('Taking web page screenshots for HTTP services (total: ' \
            '{nb})...'.format(nb=len(services)))

        screenshoter = WebScreenshoter()
        if not screenshoter.create_driver():
            logger.error('No screenshot will be added to the report')
            return

        i = 1
        for s in services:
            if s.screenshot is not None \
                    and s.screenshot.status == ScreenStatus.OK \
                    and s.screenshot.image is not None \
                    and s.screenshot.thumbnail is not None:
                logger.info('[{i}/{nb}] Screenshot already in database for {url}'.format(
                    i=i, nb=len(services), url=s.url))

            else:
                logger.info('[{i}/{nb}] Taking screenshot for {url}...'.format(
                    i=i, nb=len(services), url=s.url))
                status, screen = screenshoter.take_screenshot(s.url)

                # Create Screenshot entry in database if necessary
                if s.screenshot is None:
                    screenshot = Screenshot(status=status)
                    self.sqlsession.add(screenshot)
                    s.screenshot = screenshot
                    self.sqlsession.commit()

                # Create thumbnail if status is OK
                if status == ScreenStatus.OK:
                    thumb = ImageUtils.create_thumbnail(screen, 300, 300)
                    if not thumb:
                        status = ScreenStatus.ERROR
                    s.screenshot.status = status
                    s.screenshot.image = screen
                    s.screenshot.thumbnail = thumb
                else:
                    s.screenshot.status = status
            self.sqlsession.commit()

            i += 1
Exemplo n.º 3
0
    def __init__(self,
                 settings,
                 arguments,
                 sqlsession,
                 mission,
                 filter_categories=None,
                 filter_checks=None,
                 attack_profile=None,
                 fast_mode=False):
        """
        Construct AttackScope object

        :param Settings settings: Settings
        :param ArgumentsParser arguments: Arguments from command-line
        :param Session sqlsession: SQLAlchemy session
        :param str mission: Mission name
        :param list filter_categories: Selection of categories of checks to run 
            (default is None, for all categories)
        :param list filter_checks: Selection of checks to run
            (default is None, for all checks)
        :param AttackProfile attack_profile: Attack profile
            (default is None, meaning no profile)
        :param bool fast_mode: Set to true to disable prompts
        """
        self.settings = settings
        self.arguments = arguments
        self.sqlsess = sqlsession
        self.mission_name = mission
        self.services_requester = ServicesRequester(self.sqlsess)
        self.targets = list()
        self.current_targetid = 1
        self.filter_categories = filter_categories
        self.filter_checks = filter_checks
        self.attack_profile = attack_profile
        self.fast_mode = fast_mode

        self.services_requester.select_mission(self.mission_name)
Exemplo n.º 4
0
    def __run_for_single_target(self, args):
        """Run attack against a single target specified into args"""
        
        req = ServicesRequester(self.sqlsess)
        mission = None

        # Get Mission if target must be added into a mission scope
        if args.add:
            mission = self.sqlsess.query(Mission).filter(Mission.name == args.add).first()
            if not mission:
                raise AttackException('The specified mission does not exist in the ' \
                    'database. You should create it if needed')

        # Create new Service/Host objects (if service already exist, 
        # will be merged by ServicesRequester.add_target)
        url = args.target_ip_or_url if args.target_mode == TargetMode.URL else ''
        ip  = args.target_ip_or_url if args.target_mode == TargetMode.IP else ''
        service = Service(
            name=args.service,
            port=int(args.target_port),
            protocol=self.settings.services.get_protocol2(args.service),
            url=url)
        host = Host(ip=ip) # Will be updated when initializing Target()
        host.services.append(service)

        # Update context (credentials, options, products) if specified in command-line
        if args.creds:
            for c in args.creds[args.service]:
                self.sqlsess.add(c)
                service.credentials.append(c)
        if args.users:
            for u in args.users[args.service]: 
                self.sqlsess.add(u)
                service.credentials.append(u)
        if args.products:
            for p in args.products[args.service]: 
                self.sqlsess.add(p)
                service.products.append(p)
        if args.options:
            for o in args.options[args.service]: 
                self.sqlsess.add(o)
                service.options.append(o)

        # Initialize Target
        try:
            target = Target(service, self.settings.services)
        except TargetException as e:
            logger.error(e)
            sys.exit(1)

        # Check Target and update its information:
        # - Reverse DNS lookup: by default
        # - Port check: always
        # - Nmap service detection: by default
        # - HTML title grabbing: always
        # - Web technologies detection: always
        # - Context initialization via SmartStart: always
        reachable = target.smart_check(
            reverse_dns_lookup=(args.reverse_dns is None or args.reverse_dns == 'on'),
            availability_check=True, 
            nmap_banner_grabbing=(args.nmap_banner_grab is None \
                or args.nmap_banner_grab == 'on'),
            html_title_grabbing=True,
            web_technos_detection=True,
            smart_context_initialize=True)

        # Display availability status, exit if not reachable
        if args.target_mode == TargetMode.IP:
            msg = 'Target service {neg}reachable: {target}'.format(
                neg='not ' if not reachable else '',
                target=target)
        else:
            msg = 'Target URL {url} is {neg}reachable'.format(
                url=target.get_url(),
                neg='not ' if not reachable else '')

        if reachable:
            logger.success(msg)
        else: 
            logger.error(msg)
            return

        # Commit the target with updated information inside the appropriate 
        # mission in the database
        if mission:
            logger.info('Results from this attack will be saved under mission ' \
                '"{mission}" in database'.format(mission=mission.name))
            req.select_mission(mission.name)
            req.add_target(target)

        # Run the attack
        self.attack_scope.add_target(target)
        self.attack_scope.attack()
        return
Exemplo n.º 5
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
Exemplo n.º 6
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
Exemplo n.º 7
0
    def __generate_table_services(self):
        """
        Generate the table with all services registered in the mission
        """
        req = ServicesRequester(self.sqlsession)
        req.select_mission(self.mission)
        services = req.get_results()

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

                hostname = service.host.hostname \
                    if service.host.ip != service.host.hostname else ''

                # Number of checks
                if len(service.results) > 0:
                    nb_checks = len(service.results)
                else:
                    nb_checks = '<span class="mdi mdi-window-close"></span>'

                # Number of creds
                nb_userpass = service.get_nb_credentials(single_username=False)
                nb_usernames = service.get_nb_credentials(single_username=True)
                nb_creds = '{}{}{}'.format(
                    '<span class="text-green">{}</span>'.format(str(nb_userpass)) \
                        if nb_userpass > 0 else '',
                    '/' if nb_userpass > 0 and nb_usernames > 0 else '',
                    '<span class="text-yellow">{}</span>'.format(
                        str(nb_usernames)) if nb_usernames > 0 else '')
                #if nb_creds == '':
                #    nb_creds = '<span class="mdi mdi-window-close"></span>'

                # Number of vulns
                if len(service.vulns) > 0:
                    nb_vulns = '<span class="text-green">{}</span>'.format(
                        len(service.vulns))
                else:
                    #nb_vulns = '<span class="mdi mdi-window-close"></span>'
                    nb_vulns = ''

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

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

                # Technologies
                technos = ''
                # For HTTP, respect a given order for technos for better readability
                if service.name == 'http':
                    product_types = (
                        'web-server',
                        'web-appserver',
                        # 'web-application-firewall', Displayed only in "web" tab
                        # for better readability
                        'web-cms',
                        'web-language',
                        'web-framework',
                        'web-jslib')
                    for t in product_types:
                        product = service.get_product(t)
                        if product:
                            technos += '<span class="badge badge-{type} badge-light">' \
                                '{name}{version}</span>'.format(
                                    type=t,
                                    name=product.name,
                                    version=' '+str(product.version) \
                                        if product.version else '')
                else:
                    for p in service.products:
                        technos += '<span class="badge badge-generic badge-light">' \
                            '{name}{version}</span>'.format(
                                type=p.type,
                                name=p.name,
                                version=' '+str(p.version) if p.version else '')

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

                # 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)

                html += """
                <tr{clickable}>
                    <td class="font-weight-bold">{ip}</td>
                    <td>{hostname}</th>
                    <td class="font-weight-bold">{port} /{proto}</td>
                    <td>{service}</td>
                    <td>{enc}</td>
                    <td>{banner}</td>
                    <td>{technos}</td>
                    <td>{url}</td>
                    <td>{comment}</td>
                    <td>{nb_checks}</td>
                    <td>{nb_creds}</td>
                    <td>{nb_vulns}</td>
                </tr>
                """.format(
                    clickable=' class="clickable-row" data-href="{results}"'.format(
                        results=results) if len(service.results) > 0 else '',
                    ip=service.host.ip,
                    hostname=hostname,
                    port=service.port,
                    proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(
                        service.protocol),
                    service=service_name,
                    enc=enc,
                    banner=service.banner,
                    technos=technos,
                    url='<a href="{}" title="{}">{}</a>'.format(
                        service.url, service.url, StringUtils.shorten(service.url, 40)) \
                        if service.url else '',
                    comment=StringUtils.shorten(comment, 40),
                    nb_checks=nb_checks,
                    nb_creds=nb_creds,
                    nb_vulns=nb_vulns)

        return html
Exemplo n.º 8
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="5">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)

                # Web technos
                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)

                # 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="{title}" class="image-link">
                        <img src="{screenthumb}" class="border rounded">
                    </a>
                    """.format(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>{title}</td>
                    <td>{webtechnos}</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 '',
                    title=StringUtils.shorten(service.html_title, 40),
                    webtechnos=webtechnos,
                    screenshot=screenshot,
                    checks=len(service.results))

        return html
Exemplo n.º 9
0
    def __generate_table_services(self):
        """
        Generate the table with all services registered in the mission
        """

        req = ServicesRequester(self.sqlsession)
        req.select_mission(self.mission)
        services = req.get_results()

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

                # Creds numbers
                nb_userpass = service.get_nb_credentials(single_username=False)
                nb_usernames = service.get_nb_credentials(single_username=True)
                nb_creds = '{}{}{}'.format(
                    '<span class="text-green">{}</span>'.format(str(nb_userpass)) \
                        if nb_userpass > 0 else '',
                    '/' if nb_userpass > 0 and nb_usernames > 0 else '',
                    '<span class="text-yellow">{}</span> user(s)'.format(
                        str(nb_usernames)) if nb_usernames > 0 else '')

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

                # 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)

                html += """
                <tr{clickable}>
                    <td>{ip}</td>
                    <td>{port}</td>
                    <td>{proto}</td>
                    <td>{service}</td>
                    <td>{banner}</td>
                    <td>{url}</td>
                    <td>{comment}</td>
                    <td>{checks}</td>
                    <td>{creds}</td>
                </tr>
                """.format(
                    clickable=' class="clickable-row" data-href="{results}"'.format(
                        results=results) if len(service.results) > 0 else '',
                    ip=service.host.ip,
                    port=service.port,
                    proto={Protocol.TCP: 'tcp', Protocol.UDP: 'udp'}.get(
                        service.protocol),
                    service=service.name,
                    banner=service.banner,
                    url='<a href="{}" title="{}">{}</a>'.format(
                        service.url, service.url, StringUtils.shorten(service.url, 50)) \
                        if service.url else '',
                    comment=StringUtils.shorten(comment, 40),
                    checks=len(service.results),
                    creds=nb_creds)
        return html
    def __run_for_single_target(self, args):
        """
        Run attack against a single target specified into argss
        """
        req = ServicesRequester(self.sqlsess)
        mission = None

        # Get Mission if target must be added into a mission scope
        if args.add:
            mission = self.sqlsess.query(Mission).filter(
                Mission.name == args.add).first()
            if not mission:
                raise AttackException(
                    'The specified mission does not exist in the database. You should create it if needed'
                )

        # Create new Service/Host objects (if service already exist, will be merged by ServicesRequester.add_target)
        service = Service(
            name=args.service,
            port=int(args.target_port),
            protocol={
                'tcp': Protocol.TCP,
                'udp': Protocol.UDP
            }.get(self.settings.services.get_protocol(args.service)),
            url=args.target_ip_or_url
            if args.target_mode == TargetMode.URL else '')
        host = Host(
            ip=args.target_ip_or_url if args.target_mode == TargetMode.IP else
            '')  # Will be updated when initializing Target()
        host.services.append(service)

        # Update credentials and options if needed
        for c in self.creds[args.service]:
            service.credentials.append(c)
        for u in self.users[args.service]:
            service.credentials.append(u)
        for o in self.options[args.service]:
            service.options.append(o)

        # Initialize Target and check if reachable
        target = Target(service, self.settings.services)
        if args.disable_banner_grab:
            logger.info('Check if target is reachable...')
        else:
            logger.info(
                'Check if target is reachable and grab banner using Nmap...')
        reachable = target.smart_check(
            grab_banner_nmap=not args.disable_banner_grab)

        if args.target_mode == TargetMode.IP:
            msg = 'Target {neg}reachable: host {ip} | port {port}/{proto} | service {service}'.format(
                neg='not ' if not reachable else '',
                ip=target.get_ip(),
                port=target.get_port(),
                proto=target.get_protocol(),
                service=target.get_service_name())
        else:
            msg = 'Target URL {url} is {neg}reachable'.format(
                url=target.get_url(), neg='not ' if not reachable else '')

        if reachable:
            service.up = True
            logger.success(msg)
        else:
            raise AttackException(msg)

        # Commit new data into database if target must be added to a mission
        if mission:
            logger.info(
                'Results from this attack will be saved under mission "{mission}" in database'
                .format(mission=mission.name))
            req.select_mission(mission.name)
            req.add_target(target)

        # Run the attack
        self.attack_scope.add_target(target)
        self.attack_scope.attack()
    def __run_for_multi_targets(self, args):
        """
        Run attack against multiple targets from the database
        """

        # Get Mission from which targets must be extracted
        mission = self.sqlsess.query(Mission).filter(
            Mission.name == args.mission).first()
        if mission:
            logger.info(
                'Extracting targets from mission "{mission}" ...'.format(
                    mission=mission.name))
        else:
            raise AttackException(
                'Mission {mission} does not exist into the database'.format(
                    mission=args.mission))

        # Initialize Services requester and add filter if provided
        requester = ServicesRequester(self.sqlsess)
        requester.select_mission(args.mission)
        if args.filters_combined:
            for filt in args.filter:
                logger.info(
                    'Applying filters on mission scope: {filter}'.format(
                        filter=filt))
            if len(args.filter) > 1:
                logger.info('Logical or is applied between each filter')
            requester.add_filter(args.filters_combined)

        # Retrieve targeted services from database
        services = requester.get_results()
        if not services:
            raise AttackException(
                'There is no matching service to target into the database')

        # Add each targeted service into Attack scope
        logger.info('Checking if targets are reachable...')
        for service in services:
            # Update credentials and options if needed
            for c in self.creds[service.name]:
                service.credentials.append(c)
            for u in self.users[service.name]:
                service.credentials.append(u)
            for o in self.options[service.name]:
                service.options.append(o)

            # Initialize Target and check if reachable
            target = Target(service, self.settings.services)
            service.up = target.smart_check(grab_banner_nmap=False)
            self.sqlsess.commit()

            msg = 'host {ip} | port {port}/{proto} | service {service}'.format(
                ip=target.get_ip(),
                port=target.get_port(),
                proto=target.get_protocol(),
                service=target.get_service_name())
            if service.up:
                logger.success('Target reachable: ' + msg)
            else:
                logger.warning('Target not reachable (skipped): ' + msg)
                continue

            # Update info into database if needed
            #requester.add_target(target)

            self.attack_scope.add_target(target)

        self.attack_scope.attack()
Exemplo n.º 12
0
class AttackScope:
    """Stores all targets selected for the current attack."""
    def __init__(self,
                 settings,
                 arguments,
                 sqlsession,
                 mission,
                 filter_categories=None,
                 filter_checks=None,
                 attack_profile=None,
                 fast_mode=False):
        """
        Construct AttackScope object

        :param Settings settings: Settings
        :param ArgumentsParser arguments: Arguments from command-line
        :param Session sqlsession: SQLAlchemy session
        :param str mission: Mission name
        :param list filter_categories: Selection of categories of checks to run 
            (default is None, for all categories)
        :param list filter_checks: Selection of checks to run
            (default is None, for all checks)
        :param AttackProfile attack_profile: Attack profile
            (default is None, meaning no profile)
        :param bool fast_mode: Set to true to disable prompts
        """
        self.settings = settings
        self.arguments = arguments
        self.sqlsess = sqlsession
        self.mission_name = mission
        self.services_requester = ServicesRequester(self.sqlsess)
        self.targets = list()
        self.current_targetid = 1
        self.filter_categories = filter_categories
        self.filter_checks = filter_checks
        self.attack_profile = attack_profile
        self.fast_mode = fast_mode

        self.services_requester.select_mission(self.mission_name)

    #------------------------------------------------------------------------------------

    def add_target(self, target):
        """
        Add a target to the scope.

        :param Target target: Target to add
        """
        self.targets.append(target)

    #------------------------------------------------------------------------------------
    # Run Methods

    def attack(self):
        """Run the attack against all targets in the scope"""

        # Initialize top status/progress bar
        # If single target (total=None), the counter format will be used instead of
        # the progress bar format
        attack_progress = manager.counter(
            total=len(self.targets) + 1 if len(self.targets) > 1 else None,
            desc='',
            unit='target',
            bar_format=STATUSBAR_FORMAT,  # For multi targets
            counter_format=STATUSBAR_FORMAT_SINGLE)  # For single target

        time.sleep(.5)  # hack for progress bar display

        # Loop over the targets
        for i in range(1, len(self.targets) + 1):

            # In Multi-targets mode:
            # Display summary table and prompt for target selection
            # (not if too many target to avoid poor output)
            if 2 <= len(self.targets) <= 15:
                self.show_summary()

            if not self.fast_mode and len(self.targets) > 1:
                self.current_targetid = Output.prompt_choice_range(
                    'Attack target # (Ctrl+C to quit) ? [{default}] '.format(
                        default=self.current_targetid), 1, len(self.targets),
                    self.current_targetid)

            target = self.targets[self.current_targetid - 1]

            # Update status/progress bar
            status = 'Current target [{cur}/{total}]: {target}'.format(
                cur=i, total=len(self.targets), target=target)

            attack_progress.desc = '{status}{fill}'.format(
                status=status, fill=' ' * (DESC_LENGTH - len(status)))
            attack_progress.update()
            print()

            # Check the current target
            # For single target mode: already done in AttackController
            if len(self.targets) > 1:
                # By default, do NOT perform reverve DNS lookup & Nmap banner grabbing
                # because we assume it has been added via Nmap results in most cases
                # and thus, has already been done (behaviour can be changed with opt)
                reachable = target.smart_check(
                    reverse_dns=(self.arguments.args.reverse_dns == 'on'),
                    availability_check=True,
                    grab_banner_nmap=(
                        self.arguments.args.nmap_banner_grab == 'on'),
                    web_technos_detection=False)

                if target.service.name == 'http':
                    msg = 'Target URL {url} is {neg}reachable'.format(
                        url=target.get_url(),
                        neg='not ' if not reachable else '')
                else:
                    msg = 'Target {neg}reachable: {target}'.format(
                        neg='not ' if not reachable else '', target=target)

                # Update info into database if needed
                self.services_requester.add_target(target)

                if reachable:
                    #target.service.up = True
                    logger.success(msg)
                else:
                    # Skip target if not reachable
                    logger.error(msg)
                    continue

            # In Single-target mode: Display summary table and prompt for confirmation
            if len(self.targets) == 1:
                self.show_summary()

                if not self.fast_mode:
                    if Output.prompt_confirm('Start attack ?', default=True):
                        self.current_targetid = 1
                    else:
                        logger.warning('Attack canceled !')
                        sys.exit(1)

            # Launch the attack on the selected target
            self.__attack_target(target, attack_progress)
            self.current_targetid += 1
            self.current_targetid = self.current_targetid % len(self.targets)

        attack_progress.update()
        time.sleep(.5)

        attack_progress.close()
        manager.stop()  # Clear progress bars

    def __attack_target(self, target, attack_progress):
        """
        Run security checks against one target.

        :param Target target: The Target
        :param enlighten.Counter attack_progress: Attack progress
        """

        # Print target information
        target.print_http_headers()
        target.print_context()

        # Run start method from SmartModule
        start = SmartStart(target.service, self.sqlsess)
        start.run()

        # Run security checks
        service_checks = self.settings.services.get_service_checks(
            target.get_service_name())
        service_checks.run(target,
                           self.arguments,
                           self.sqlsess,
                           filter_categories=self.filter_categories,
                           filter_checks=self.filter_checks,
                           attack_profile=self.attack_profile,
                           attack_progress=attack_progress)

    #------------------------------------------------------------------------------------
    # Output methods

    def show_summary(self):
        """Display a table showing the summary of the attack scope."""
        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(StringUtils.wrap(target.get_host(), 50),
                    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(), 55),
                    color=pointer_color, attrs=pointer_attr),
                Output.colored(StringUtils.wrap(target.get_url(), 50),
                    color=pointer_color, attrs=pointer_attr),
            ])
            id_ += 1

        print()
        Output.table(columns, data, hrules=False)
        print()
Exemplo n.º 13
0
    def __run_for_single_target(self, args):
        """Run attack against a single target specified into args"""
        
        req = ServicesRequester(self.sqlsess)
        mission = None

        # Get Mission if target must be added into a mission scope
        if args.add:
            mission = self.sqlsess.query(Mission).filter(Mission.name == args.add).first()
            if not mission:
                raise AttackException('The specified mission does not exist in the ' \
                    'database. You should create it if needed')

        # Create new Service/Host objects (if service already exist, 
        # will be merged by ServicesRequester.add_target)
        url = args.target_ip_or_url if args.target_mode == TargetMode.URL else ''
        ip  = args.target_ip_or_url if args.target_mode == TargetMode.IP else ''
        service = Service(name=args.service,
                          port=int(args.target_port),
                          protocol=self.settings.services.get_protocol2(args.service),
                          url=url)
        host = Host(ip=ip) # Will be updated when initializing Target()
        host.services.append(service)

        # Update credentials, options, products if specified in command-line
        for c in self.creds[args.service]    : service.credentials.append(c)
        for u in self.users[args.service]    : service.credentials.append(u)
        for p in self.products[args.service] : service.products.append(p)
        for o in self.options[args.service]  : service.options.append(o)

        # Initialize Target
        try:
            target = Target(service, self.settings.services)
        except TargetException as e:
            logger.error(e)
            sys.exit(1)

        # Check if target is reachable 
        # (by default, perform reverve DNS lookup & Nmap banner grabbing)
        reachable = target.smart_check(
            reverse_dns=(args.reverse_dns is None or args.reverse_dns == 'on'),
            availability_check=True,
            grab_banner_nmap=(args.nmap_banner_grab is None \
                or args.nmap_banner_grab == 'on'))

        if args.target_mode == TargetMode.IP:
            msg = 'Target {neg}reachable: {target}'.format(
                neg='not ' if not reachable else '',
                target=target)
        else:
            msg = 'Target URL {url} is {neg}reachable'.format(
                url=target.get_url(),
                neg='not ' if not reachable else '')

        if reachable:
            logger.success(msg)
        else: 
            # Skip target if not reachable
            logger.error(msg)
            return

        # Commit new data into database if target must be added to a mission
        if mission:
            logger.info('Results from this attack will be saved under mission ' \
                '"{mission}" in database'.format(mission=mission.name))
            req.select_mission(mission.name)
            req.add_target(target)

        # Run the attack
        self.attack_scope.add_target(target)
        self.attack_scope.attack()
    def do_services(self, args):
        """Services in the current mission scope"""
        print()
        req = ServicesRequester(self.sqlsess)
        req.select_mission(self.current_mission)

        # Logical AND is applied between all specified filtering options
        filter_ = Filter(FilterOperator.AND)
        if args.names:
            for n in args.names:
                if not self.settings.services.is_service_supported(n, multi=False):
                    logger.error('Service {name} is not valid/supported'.format(name=n.lower()))
                    return
            filter_.add_condition(Condition(args.names, FilterData.SERVICE_EXACT))

        if args.order:
            req.order_by(args.order)

        if args.hostname:
            # OR between submitted hostnames
            filter_.add_condition(Condition(args.hostname.split(','), FilterData.HOST))
        if args.ip:
            # OR between submitted ips/ranges
            filter_.add_condition(Condition(args.ip.split(','), FilterData.IP))
        if args.port:
            # OR between ports/port-ranges
            filter_.add_condition(Condition(args.port.split(','), FilterData.PORT))
        if args.proto:
            filter_.add_condition(Condition(args.proto, FilterData.PROTOCOL))
        if args.up:
            filter_.add_condition(Condition(args.up, FilterData.UP))
        if args.search:
            filter_search = Filter(FilterOperator.OR)
            filter_search.add_condition(Condition(args.search, FilterData.HOST))
            filter_search.add_condition(Condition(args.search, FilterData.BANNER))
            filter_search.add_condition(Condition(args.search, FilterData.URL))
            filter_search.add_condition(Condition(args.search, FilterData.COMMENT_SERVICE))
            filter_.add_condition(filter_search)

        try:
            req.add_filter(filter_)
        except FilterException as e:
            logger.error(e)
            return

        # Operations
        if args.add:
            host, port, service = args.add
            if NetUtils.is_valid_ip(host):
                ip = host
                hostname = NetUtils.reverse_dns_lookup(ip) 
                logger.info('Reverse DNS lookup on IP {ip}: {hostname}'.format(ip=ip, hostname=hostname))
            else:
                ip = NetUtils.dns_lookup(host)
                if not ip:
                    logger.error('Cannot resolve hostname')
                    return
                hostname = host
                logger.info('DNS lookup on {hostname}: IP {ip}'.format(hostname=host, ip=ip))

            if not NetUtils.is_valid_port(port):
                logger.error('Port is invalid, not in range [0-65535]')
            elif not self.settings.services.is_service_supported(service, multi=False):
                logger.error('Service {name} is not valid/supported'.format(name=service.lower()))
            else:
                req.add_service(ip, hostname, port, self.settings.services.get_protocol(service), service)
        elif args.url:
            args.url = WebUtils.add_prefix_http(args.url)
            if not WebUtils.is_valid_url(args.url):
                logger.error('URL is invalid')
            else:
                req.add_url(args.url)
        elif args.delete:
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to delete ALL services in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.delete()
        elif args.comment:
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to edit comment for ALL services in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.edit_comment(args.comment)
        elif args.https:
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to apply switch for ALL URLs in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.switch_https()         
        elif args.addcred:
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to add same creds for ALL services in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.add_cred(args.addcred[0], args.addcred[1], None) 
        elif args.addcred_http:
            if not req.are_only_http_services_selected():
                logger.warning('Some non-HTTP services are selected. Use --addcred instead for non-HTTP services')
                return
            if not self.settings.services.is_valid_authentication_type(args.addcred_http[2]):
                logger.warning('Invalid HTTP authentication type')
                logger.info('List of supported authentication types: ')
                for auth_type in self.settings.services.get_authentication_types('http'):
                    logger.info('- {type}'.format(type=auth_type))
                return
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to add same creds for ALL HTTP services in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.add_cred(args.addcred_http[0], args.addcred_http[1], args.addcred_http[2]) 
        elif args.adduser:
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to add same username for ALL services in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.add_cred(args.adduser[0], None, None)
        elif args.adduser_http:
            if not req.are_only_http_services_selected():
                logger.warning('Some non-HTTP services are selected. Use --adduser instead for non-HTTP services')
                return
            if not self.settings.services.is_valid_authentication_type(args.adduser_http[1]):
                logger.warning('Invalid HTTP authentication type')
                logger.info('List of supported authentication types: ')
                for auth_type in self.settings.services.get_authentication_types('http'):
                    logger.info('- {type}'.format(type=auth_type))
                return
            if not req.filter_applied:
                if not Output.prompt_confirm('No filter applied. Are you sure you want to add same username for ALL HTTP services in current mission ?', default=False):
                    logger.info('Canceled')
                    return
            req.add_cred(args.adduser_http[0], None, args.adduser_http[1]) 
        else:
            req.show()                      

        print()
Exemplo n.º 15
0
class AttackScope:
    """Stores all targets selected for the current attack."""

    def __init__(self, 
                 settings, 
                 arguments,
                 sqlsession,
                 mission,
                 filter_categories=None, 
                 filter_checks=None, 
                 attack_profile=None,
                 fast_mode=False):
        """
        Construct AttackScope object

        :param Settings settings: Settings
        :param ArgumentsParser arguments: Arguments from command-line
        :param Session sqlsession: SQLAlchemy session
        :param str mission: Mission name
        :param list filter_categories: Selection of categories of checks to run 
            (default is None, for all categories)
        :param list filter_checks: Selection of checks to run
            (default is None, for all checks)
        :param AttackProfile attack_profile: Attack profile
            (default is None, meaning no profile)
        :param bool fast_mode: Set to true to disable prompts
        """
        self.settings            = settings
        self.arguments           = arguments
        self.sqlsess             = sqlsession
        self.mission_name        = mission
        self.services_requester  = ServicesRequester(self.sqlsess)
        self.targets             = list()
        self.current_targetid    = 1
        self.filter_categories   = filter_categories
        self.filter_checks       = filter_checks
        self.attack_profile      = attack_profile
        self.fast_mode           = fast_mode

        self.services_requester.select_mission(self.mission_name)


    #------------------------------------------------------------------------------------

    def add_target(self, target):
        """
        Add a target to the scope.

        :param Target target: Target to add
        """ 
        self.targets.append(target)

    #------------------------------------------------------------------------------------
    # Run Methods

    def attack(self):
        """Run the attack against all targets in the scope"""

        # Initialize top status/progress bar
        # If single target (total=None), the counter format will be used instead of 
        # the progress bar format
        attack_progress = manager.counter(
            total=len(self.targets)+1 if len(self.targets) > 1 else None, 
            desc='', 
            unit='target',
            bar_format=STATUSBAR_FORMAT, # For multi targets
            counter_format=STATUSBAR_FORMAT_SINGLE) # For single target

        time.sleep(.5) # hack for progress bar display

        # Loop over the targets
        for i in range(1,len(self.targets)+1):

            # Display table with targets
            self.show_summary()

            # Prompt for target selection
            if not self.fast_mode:
                if len(self.targets) == 1:
                    if Output.prompt_confirm('Start attack ?', default=True):
                        self.current_targetid = 1
                    else:
                        logger.warning('Attack canceled !')
                        sys.exit(1)

                else:
                    self.current_targetid = Output.prompt_choice_range(
                        'Attack target # (Ctrl+C to quit) ? [{default}] '.format(
                            default=self.current_targetid), 
                        1, len(self.targets), self.current_targetid)

            target = self.targets[self.current_targetid-1]

            # Update status/progress bar
            status = 'Current target [{cur}/{total}]: {target}'.format(
                    cur    = i,
                    total  = len(self.targets),
                    target = target)

            attack_progress.desc = '{status}{fill}'.format(
                status = status,
                fill   = ' '*(DESC_LENGTH-len(status)))
            attack_progress.update()
            print()

            # Check the selected target and update its information
            # This is done for targets loaded from the database in multi-targets mode
            # (For single target, done before adding it to a mission in AttackController)
            #
            # - Reverse DNS lookup: not by default (should have already been done)
            # - Port check: always (target might not been reachable anymore)
            # - Nmap service detection: not by default (should have already been done)
            # - HTML title grabbing: always
            # - Web technologies detection: always
            # - Context initialization via SmartStart: always
            if len(self.targets) > 1:
                reachable = target.smart_check(
                    reverse_dns_lookup=(self.arguments.args.reverse_dns == 'on'), 
                    availability_check=True, 
                    nmap_banner_grabbing=(self.arguments.args.nmap_banner_grab == 'on'),
                    html_title_grabbing=True,
                    web_technos_detection=True,
                    smart_context_initialize=True)

                # Update info into database if needed
                self.services_requester.add_target(target)

                # Display availability status, skip if not reachable
                if target.service.name == 'http':
                    msg = 'Target URL {url} is {neg}reachable'.format(
                        url=target.get_url(),
                        neg='not ' if not reachable else '')
                else:
                    msg = 'Target {neg}reachable: {target}'.format(
                        neg='not ' if not reachable else '',
                        target=target)

                if reachable:
                    logger.success(msg)
                else: 
                    logger.error(msg)
                    self.__next_target()
                    continue

            # Launch the attack on the selected target
            self.__attack_target(target, attack_progress)

            # Move to next target
            self.__next_target()


        # Clear progress bars
        attack_progress.update()
        time.sleep(.5)

        attack_progress.close()
        manager.stop() 


    def __attack_target(self, target, attack_progress):
        """
        Run security checks against one target.

        :param Target target: The Target
        :param enlighten.Counter attack_progress: Attack progress
        """
                   
        # Print target information
        target.print_http_headers()
        target.print_context()

        # Run security checks
        service_checks = self.settings.services.get_service_checks(
            target.get_service_name())
        service_checks.run(target, 
                           self.arguments,
                           self.sqlsess,
                           filter_categories=self.filter_categories, 
                           filter_checks=self.filter_checks,
                           attack_profile=self.attack_profile,
                           attack_progress=attack_progress)


    def __next_target(self):
        """
        Move to next target by incrementing current target id
        """
        if self.current_targetid == len(self.targets):
            self.current_targetid = 1
        else:
            self.current_targetid += 1


    #------------------------------------------------------------------------------------
    # Output methods

    def show_summary(self):
        """
        Display a table showing the summary of the attack scope.
        The table has a max size defined in lib.core.Config, to avoid displaying an
        unreadable summary when large amount of targets have been loaded.
        """
        if len(self.targets) > ATTACK_SUMMARY_TABLE_MAX_SIZE:
            id_min = self.current_targetid-2
            if id_min < 1:  
                id_min = 1

            id_max = self.current_targetid+ATTACK_SUMMARY_TABLE_MAX_SIZE-1 \
                -(self.current_targetid-id_min)
            if id_max > len(self.targets):
                id_min = id_min-(id_max-len(self.targets))
                id_max = len(self.targets)
        else:
            id_min = 1
            id_max = len(self.targets)

        data = list()
        columns = [
            'id',
            'IP',
            'Hostname',
            'Port',
            'Proto',
            'Service',
            'Banner',
            'URL',
        ]
        id_ = 1
        for target in self.targets:
            if id_ < id_min:
                id_ += 1
                continue
            if id_ > id_max:
                break

            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(StringUtils.wrap(target.get_host(), 50), 
                    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(), 55), 
                    color=pointer_color, attrs=pointer_attr),
                Output.colored(StringUtils.wrap(target.get_url(), 50), 
                    color=pointer_color, attrs=pointer_attr),
            ])
            id_ += 1

        print()
        Output.table(columns, data, hrules=False)
        if len(self.targets) > ATTACK_SUMMARY_TABLE_MAX_SIZE:
            logger.info('Table has been truncated. Total number of loaded ' \
                'targets: {}'.format(len(self.targets)))
        print()