Пример #1
0
class ClonePageDialog(gui_utilities.GladeGObject):
    """
	Display a dialog for cloning a web page. The logic for the cloning operation
	is provided by the :py:mod:`.web_cloner` module.
	"""
    dependencies = gui_utilities.GladeDependencies(
        children=('button_cancel', 'entry_clone_directory', 'label_status',
                  'spinner_status', 'treeview_resources'),
        top_level=('StockExecuteImage', 'StockStopImage'))
    view_columns = (
        extras.ColumnDefinitionString('Resource Path'),
        extras.ColumnDefinitionString('MIME Type'),
        extras.ColumnDefinitionBytes('Size'),
    )
    top_gobject = 'dialog'

    def __init__(self, *args, **kwargs):
        super(ClonePageDialog, self).__init__(*args, **kwargs)
        self.resources = Gtk.ListStore(*tuple(column.g_type
                                              for column in self.view_columns))
        treeview = self.gobjects['treeview_resources']
        treeview.set_model(self.resources)
        self.treeview_manager = managers.TreeViewManager(treeview)
        self.treeview_manager.set_column_titles(
            tuple(column.title for column in self.view_columns),
            renderers=tuple(column.cell_renderer()
                            for column in self.view_columns))
        self.popup_menu = self.treeview_manager.get_popup_menu()

        self.button_cancel = self.gobjects['button_cancel']
        self.entry_directory = self.gobjects['entry_clone_directory']
        # managed separately to be kept out of the config
        self.entry_target = self.gtk_builder_get('entry_target')
        self.label_status = self.gobjects['label_status']
        self.spinner_status = self.gobjects['spinner_status']

    def set_status(self, status_text, spinner_active=False):
        self.label_status.set_text("Status: {0}".format(status_text))
        self.spinner_status.set_property('visible', spinner_active)
        self.spinner_status.set_property('active', spinner_active)

    def interact(self):
        self.dialog.show_all()
        self.set_status('Waiting')
        if not web_cloner.has_webkit2:
            gui_utilities.show_dialog_error(
                'WebKit2GTK+ Is Unavailable', self.dialog,
                'The WebKit2GTK+ package is not available.')
            self.dialog.destroy()
            return
        while self.dialog.run() == Gtk.ResponseType.APPLY:
            target_url = self.entry_target.get_text()
            if not target_url:
                gui_utilities.show_dialog_error('Missing Information',
                                                self.dialog,
                                                'Please set the target URL.')
                self.set_status('Missing Information')
                continue
            dest_dir = self.entry_directory.get_text()
            if not dest_dir:
                gui_utilities.show_dialog_error(
                    'Missing Information', self.dialog,
                    'Please set the destination directory.')
                self.set_status('Missing Information')
                continue
            if not os.access(dest_dir, os.W_OK):
                gui_utilities.show_dialog_error(
                    'Invalid Directory', self.dialog,
                    'Can not write to the specified directory.')
                self.set_status('Invalid Directory')
                continue
            self.objects_save_to_config()

            self.set_status('Cloning', spinner_active=True)
            cloner = web_cloner.WebPageCloner(target_url, dest_dir)
            signal_id = self.button_cancel.connect(
                'clicked', lambda _: cloner.stop_cloning())
            original_label = self.button_cancel.get_label()
            self.button_cancel.set_label('Cancel')
            cloner.wait()
            self.button_cancel.set_label(original_label)
            self.button_cancel.disconnect(signal_id)

            if cloner.load_failed:
                self.set_status('Failed')
                gui_utilities.show_dialog_error(
                    'Operation Failed', self.dialog,
                    'The web page clone operation failed.')
                continue
            for resource in cloner.cloned_resources.values():
                if gui_utilities.gtk_list_store_search(self.resources,
                                                       resource.resource,
                                                       column=0):
                    continue
                self.resources.append(
                    _ModelNamedRow(path=resource.resource,
                                   mime_type=resource.mime_type or 'N/A',
                                   size=resource.size))
            self.set_status('Done')
            gui_utilities.gtk_sync()
        if len(self.resources) and gui_utilities.show_dialog_yes_no(
                'Transfer Cloned Pages', self.dialog,
                'Would you like to start the SFTP client\nto upload the cloned pages?'
        ):
            self.application.emit('sftp-client-start')
        self.dialog.destroy()

    def signal_multi_set_directory(self, _):
        dialog = extras.FileChooserDialog('Destination Directory', self.dialog)
        response = dialog.run_quick_select_directory()
        dialog.destroy()
        if response:
            self.entry_directory.set_text(response['target_path'])
Пример #2
0
class CampaignSelectionDialog(gui_utilities.GladeGObject):
    """
	Display a dialog which allows a new campaign to be created or an
	existing campaign to be opened.
	"""
    dependencies = gui_utilities.GladeDependencies(
        children=('button_new_campaign', 'button_select',
                  'drawingarea_color_key', 'label_campaign_info',
                  'menubutton_filter', 'treeview_campaigns'),
        top_level=('StockAddImage', ))
    view_columns = (extras.ColumnDefinitionString('Campaign Name'),
                    extras.ColumnDefinitionString('Company'),
                    extras.ColumnDefinitionString('Type'),
                    extras.ColumnDefinitionInteger('Messages'),
                    extras.ColumnDefinitionString('Created By'),
                    extras.ColumnDefinitionDatetime('Creation Date'),
                    extras.ColumnDefinitionDatetime('Expiration'))
    top_gobject = 'dialog'

    def __init__(self, *args, **kwargs):
        super(CampaignSelectionDialog, self).__init__(*args, **kwargs)
        treeview = self.gobjects['treeview_campaigns']
        self.treeview_manager = managers.TreeViewManager(
            treeview,
            cb_delete=self._prompt_to_delete_row,
            cb_refresh=self.load_campaigns)
        self.treeview_manager.set_column_titles(
            tuple(column.title for column in self.view_columns),
            column_offset=4,
            renderers=tuple(column.cell_renderer()
                            for column in self.view_columns))
        treeview.set_tooltip_column(1)
        self.treeview_manager.set_column_color(background=2, foreground=3)
        self.popup_menu = self.treeview_manager.get_popup_menu()
        self._creation_assistant = None

        view_column_types = tuple(column.g_type
                                  for column in self.view_columns)
        self._tv_model = Gtk.ListStore(str, str, Gdk.RGBA, Gdk.RGBA,
                                       *view_column_types)

        # create and set the filter for expired campaigns
        self._tv_model_filter = self._tv_model.filter_new()
        self._tv_model_filter.set_visible_func(self._filter_campaigns)
        tree_model_sort = Gtk.TreeModelSort(model=self._tv_model_filter)
        for idx, column in enumerate(self.view_columns, 4):
            if column.sort_function is not None:
                tree_model_sort.set_sort_func(idx, column.sort_function, idx)
        # default sort is descending by campaign creation date
        tree_model_sort.set_sort_column_id(9, Gtk.SortType.DESCENDING)
        treeview.set_model(tree_model_sort)

        # setup menus for filtering campaigns and load campaigns
        self.get_popup_filter_menu()
        self.load_campaigns()

    def get_popup_filter_menu(self):
        # create filter menu and menuitems
        filter_menu = Gtk.Menu()
        menu_item_expired = Gtk.CheckMenuItem('Expired campaigns')
        menu_item_user = Gtk.CheckMenuItem('Your campaigns')
        menu_item_other = Gtk.CheckMenuItem('Other campaigns')
        self.filter_menu_items = {
            'expired_campaigns': menu_item_expired,
            'your_campaigns': menu_item_user,
            'other_campaigns': menu_item_other
        }
        # set up the menuitems and add it to the menubutton
        for menus in self.filter_menu_items:
            filter_menu.append(self.filter_menu_items[menus])
            self.filter_menu_items[menus].connect(
                'toggled', self.signal_checkbutton_toggled)
            self.filter_menu_items[menus].show()
        self.filter_menu_items['expired_campaigns'].set_active(
            self.config['filter.campaign.expired'])
        self.filter_menu_items['your_campaigns'].set_active(
            self.config['filter.campaign.user'])
        self.filter_menu_items['other_campaigns'].set_active(
            self.config['filter.campaign.other_users'])
        self.gobjects['menubutton_filter'].set_popup(filter_menu)
        filter_menu.connect('destroy', self._save_filter)

    def _save_filter(self, _):
        self.config['filter.campaign.expired'] = self.filter_menu_items[
            'expired_campaigns'].get_active()
        self.config['filter.campaign.user'] = self.filter_menu_items[
            'your_campaigns'].get_active()
        self.config['filter.campaign.other_users'] = self.filter_menu_items[
            'other_campaigns'].get_active()

    def _filter_campaigns(self, model, tree_iter, _):
        named_row = _ModelNamedRow(*model[tree_iter])
        username = self.config['server_username']
        if not self.filter_menu_items['your_campaigns'].get_active():
            if username == named_row.user:
                return False
        if not self.filter_menu_items['other_campaigns'].get_active():
            if username != named_row.user:
                return False
        if named_row.expiration is None:
            return True
        if named_row.expiration < datetime.datetime.now():
            if not self.filter_menu_items['expired_campaigns'].get_active():
                return False
        return True

    def _highlight_campaign(self, campaign_name):
        treeview = self.gobjects['treeview_campaigns']
        model = treeview.get_model()
        model_iter = gui_utilities.gtk_list_store_search(
            model, campaign_name, column=_ModelNamedRow._fields.index('name'))
        if model_iter:
            treeview.set_cursor(model.get_path(model_iter), None, False)
            return True
        return False

    def _prompt_to_delete_row(self, treeview, selection):
        (model, tree_iter) = selection.get_selected()
        if not tree_iter:
            return
        campaign_id = model.get_value(tree_iter, 0)
        if self.config.get('campaign_id') == campaign_id:
            gui_utilities.show_dialog_warning(
                'Can Not Delete Campaign', self.dialog,
                'Can not delete the current campaign.')
            return
        if not gui_utilities.show_dialog_yes_no(
                'Delete This Campaign?', self.dialog,
                'This action is irreversible, all campaign data will be lost.'
        ):
            return
        self.application.emit('campaign-delete', campaign_id)
        self.load_campaigns()
        self._highlight_campaign(self.config.get('campaign_name'))

    def load_campaigns(self):
        """Load campaigns from the remote server and populate the :py:class:`Gtk.TreeView`."""
        store = self._tv_model
        store.clear()
        style_context = self.dialog.get_style_context()
        bg_color = gui_utilities.gtk_style_context_get_color(
            style_context, 'theme_color_tv_bg', default=ColorHexCode.WHITE)
        fg_color = gui_utilities.gtk_style_context_get_color(
            style_context, 'theme_color_tv_fg', default=ColorHexCode.BLACK)
        hlbg_color = gui_utilities.gtk_style_context_get_color(
            style_context,
            'theme_color_tv_hlbg',
            default=ColorHexCode.LIGHT_YELLOW)
        hlfg_color = gui_utilities.gtk_style_context_get_color(
            style_context, 'theme_color_tv_hlfg', default=ColorHexCode.BLACK)
        now = datetime.datetime.now()
        campaigns = self.application.rpc.graphql_find_file(
            'get_campaigns.graphql')
        for campaign in campaigns['db']['campaigns']['edges']:
            campaign = campaign['node']
            created_ts = utilities.datetime_utc_to_local(campaign['created'])
            expiration_ts = campaign['expiration']
            is_expired = False
            if expiration_ts is not None:
                expiration_ts = utilities.datetime_utc_to_local(expiration_ts)
                is_expired = expiration_ts < now
            store.append(
                _ModelNamedRow(
                    id=str(campaign['id']),
                    description=(html.escape(campaign['description'],
                                             quote=True)
                                 if campaign['description'] else None),
                    bg_color=(hlbg_color if is_expired else bg_color),
                    fg_color=(hlfg_color if is_expired else fg_color),
                    name=campaign['name'],
                    company=(campaign['company']['name']
                             if campaign['company'] is not None else None),
                    type=(campaign['campaignType']['name']
                          if campaign['campaignType'] is not None else None),
                    messages=campaign['messages']['total'],
                    user=campaign['user']['name'],
                    created=created_ts,
                    expiration=expiration_ts,
                ))
        self.gobjects['label_campaign_info'].set_text(
            "Showing {0} of {1:,} Campaign{2}".format(
                len(self._tv_model_filter), len(self._tv_model),
                ('' if len(self._tv_model) == 1 else 's')))

    def signal_assistant_destroy(self, _, campaign_creation_assistant):
        self._creation_assistant = None
        campaign_name = campaign_creation_assistant.campaign_name
        if not campaign_name:
            return
        self.load_campaigns()
        self._highlight_campaign(campaign_name)
        self.dialog.response(Gtk.ResponseType.APPLY)

    def signal_button_clicked(self, button):
        if self._creation_assistant is not None:
            gui_utilities.show_dialog_warning(
                'Campaign Creation Assistant', self.dialog,
                'The campaign creation assistant is already active.')
            return
        assistant = CampaignAssistant(self.application)
        assistant.assistant.set_transient_for(self.dialog)
        assistant.assistant.set_modal(True)
        assistant.assistant.connect('destroy', self.signal_assistant_destroy,
                                    assistant)
        assistant.interact()
        self._creation_assistant = assistant

    def signal_checkbutton_toggled(self, _):
        self._tv_model_filter.refilter()
        self.gobjects['label_campaign_info'].set_text(
            "Showing {0:,} of {1:,} Campaign{2}".format(
                len(self._tv_model_filter), len(self._tv_model),
                ('' if len(self._tv_model) == 1 else 's')))

    def signal_drawingarea_draw(self, drawingarea, context):
        width, height = drawingarea.get_size_request()
        context.rectangle(0, 0, width, height)
        context.stroke_preserve()
        style_context = self.dialog.get_style_context()
        hlbg_color = gui_utilities.gtk_style_context_get_color(
            style_context,
            'theme_color_tv_hlbg',
            default=ColorHexCode.LIGHT_YELLOW)
        context.set_source_rgb(hlbg_color.red, hlbg_color.green,
                               hlbg_color.blue)
        context.fill()

    def signal_treeview_row_activated(self, treeview, treeview_column,
                                      treepath):
        self.gobjects['button_select'].emit('clicked')

    def interact(self):
        self._highlight_campaign(self.config.get('campaign_name'))
        self.dialog.show_all()
        response = self.dialog.run()
        old_campaign_id = self.config.get('campaign_id')
        while response != Gtk.ResponseType.CANCEL:
            treeview = self.gobjects['treeview_campaigns']
            selection = treeview.get_selection()
            (model, tree_iter) = selection.get_selected()
            if tree_iter:
                break
            gui_utilities.show_dialog_error(
                'No Campaign Selected', self.dialog,
                'Either select a campaign or create a new one.')
            response = self.dialog.run()
        if response == Gtk.ResponseType.APPLY:
            named_row = _ModelNamedRow(*model[tree_iter])
            if named_row.id != str(old_campaign_id):
                self.config['campaign_id'] = named_row.id
                self.config['campaign_name'] = named_row.name
                self.application.emit('campaign-set', old_campaign_id,
                                      named_row.id)
        self.dialog.destroy()
        return response
Пример #3
0
class CampaignViewMessagesTab(CampaignViewGenericTableTab):
    """Display campaign information regarding sent messages."""
    table_name = 'messages'
    label_text = 'Messages'
    node_query = """\
	query getMessage($id: String!) {
		db {
			node: message(id: $id) {
				id
				targetEmail
				sent
				trained
				companyDepartment { name }
				opened
				openerIp
				openerUserAgent
				deliveryStatus
				deliveryDetails
			}
		}
	}
	"""
    table_query = """\
	query getMessages($campaign: String!, $count: Int!, $cursor: String) {
		db {
			campaign(id: $campaign) {
				messages(first: $count, after: $cursor) {
					total
					edges {
						node {
							id
							targetEmail
							sent
							trained
							companyDepartment { name }
							opened
							openerIp
							openerUserAgent
							deliveryStatus
							deliveryDetails
						}
					}
					pageInfo {
						endCursor
						hasNextPage
					}
				}
			}
		}
	}
	"""
    view_columns = (extras.ColumnDefinitionString('Email Address'),
                    extras.ColumnDefinitionDatetime('Sent'),
                    extras.ColumnDefinitionString('Trained', width=15),
                    extras.ColumnDefinitionString('Department'),
                    extras.ColumnDefinitionDatetime('Opened'),
                    extras.ColumnDefinitionString('Opener IP Address',
                                                  width=25),
                    extras.ColumnDefinitionString('Opener User Agent',
                                                  width=90),
                    extras.ColumnDefinitionString('Delivery Status'),
                    extras.ColumnDefinitionString('Delivery Details'))

    def format_node_data(self, node):
        department = node['companyDepartment']
        if department:
            department = department['name']
        row = (node['targetEmail'], _dt_field(node['sent']),
               ('Yes' if node['trained'] else ''), department,
               _dt_field(node['opened']), node['openerIp'],
               node['openerUserAgent'], node['deliveryStatus'],
               node['deliveryDetails'])
        return row
Пример #4
0
class CampaignViewVisitsTab(CampaignViewGenericTableTab):
    """Display campaign information regarding incoming visitors."""
    table_name = 'visits'
    label_text = 'Visits'
    node_query = """\
	query getVisit($id: String!) {
		db {
			node: visit(id: $id) {
				id
				message { targetEmail }
				ip
				count
				userAgent
				ipGeoloc { city }
				firstSeen
				lastSeen
			}
		}
	}
	"""
    table_query = """\
	query getVisits($campaign: String!, $count: Int!, $cursor: String) {
		db {
			campaign(id: $campaign) {
				visits(first: $count, after: $cursor) {
					total
					edges {
						node {
							id
							message { targetEmail }
							ip
							count
							userAgent
							ipGeoloc { city }
							firstSeen
							lastSeen
						}
					}
					pageInfo {
						endCursor
						hasNextPage
					}
				}
			}
		}
	}
	"""
    view_columns = (
        extras.ColumnDefinitionString('Email Address'),
        extras.ColumnDefinitionString('IP Address', width=25),
        extras.ColumnDefinitionInteger('Visit Count'),
        extras.ColumnDefinitionString('Visitor User Agent', width=90),
        extras.ColumnDefinitionString('Visitor Location'),
        extras.ColumnDefinitionDatetime('First Visit'),
        extras.ColumnDefinitionDatetime('Last Visit'),
    )

    def format_node_data(self, node):
        geo_location = UNKNOWN_LOCATION_STRING
        visitor_ip = node['ip']
        if visitor_ip is None:
            visitor_ip = ''
        else:
            visitor_ip = ipaddress.ip_address(visitor_ip)
            if visitor_ip.is_loopback:
                geo_location = 'N/A (Loopback)'
            elif visitor_ip.is_private:
                geo_location = 'N/A (Private)'
            elif isinstance(visitor_ip, ipaddress.IPv6Address):
                geo_location = 'N/A (IPv6 Address)'
            elif node['ipGeoloc']:
                geo_location = node['ipGeoloc']['city']
        row = (node['message']['targetEmail'], str(visitor_ip), node['count'],
               node['userAgent'], geo_location, _dt_field(node['firstSeen']),
               _dt_field(node['lastSeen']))
        return row
Пример #5
0
class CampaignViewCredentialsTab(CampaignViewGenericTableTab):
    """Display campaign information regarding submitted credentials."""
    table_name = 'credentials'
    label_text = 'Credentials'
    node_query = """\
	query getCredential($id: String!) {
		db {
			node: credential(id: $id) {
				id
				submitted
				message { targetEmail }
				username
				password
				mfaToken
				regexValidated
			}
		}
	}
	"""
    table_query = """\
	query getCredentials($campaign: String!, $count: Int!, $cursor: String) {
		db {
			campaign(id: $campaign) {
				credentials(first: $count, after: $cursor) {
					total
					edges {
						node {
							id
							message { targetEmail }
							submitted
							username
							password
							mfaToken
							regexValidated
						}
					}
					pageInfo {
						endCursor
						hasNextPage
					}
				}
			}
		}
	}
	"""
    secret_columns = ('Password', 'MFA Token')
    view_columns = (
        extras.ColumnDefinitionString('Email Address'),
        extras.ColumnDefinitionDatetime('Submitted'),
        extras.ColumnDefinitionString('Validation', width=20),
        extras.ColumnDefinitionString('Username'),
        extras.ColumnDefinitionString('Password'),
        extras.ColumnDefinitionString('MFA Token', width=20),
    )

    def __init__(self, *args, **kwargs):
        super(CampaignViewCredentialsTab, self).__init__(*args, **kwargs)
        treeview = self.gobjects['treeview_campaign']
        for column_name in self.secret_columns:
            treeview.get_column(
                self.view_column_titles.index(column_name)).set_property(
                    'visible', False)

    def format_node_data(self, node):
        regex_validated = ''
        if node['regexValidated'] is not None:
            regex_validated = 'Pass' if node['regexValidated'] else 'Fail'
        row = (node['message']['targetEmail'], _dt_field(node['submitted']),
               regex_validated, node['username'], node['password'],
               node['mfaToken'])
        return row

    def signal_button_toggled_show_secrets(self, button):
        treeview = self.gobjects['treeview_campaign']
        visible = button.get_property('active')
        for column_name in self.secret_columns:
            treeview.get_column(
                self.view_column_titles.index(column_name)).set_property(
                    'visible', visible)
Пример #6
0
class CampaignViewDeaddropTab(CampaignViewGenericTableTab):
    """Display campaign information regarding dead drop connections."""
    table_name = 'deaddrop_connections'
    label_text = 'Deaddrop Connections'
    node_query = """\
	query getDeaddropConnection($id: String!) {
		db {
			node: deaddropConnection(id: $id) {
				id
				deaddropDeployment { destination }
				count
				ip
				localUsername
				localHostname
				localIpAddresses
				firstSeen
				lastSeen
			}
		}
	}
	"""
    table_query = """\
	query getDeaddropConnections($campaign: String!, $count: Int!, $cursor: String) {
		db {
			campaign(id: $campaign) {
				deaddrop_connections: deaddropConnections(first: $count, after: $cursor) {
					total
					edges {
						node {
							id
							deaddropDeployment {
								id
								destination
							}
							count
							ip
							localUsername
							localHostname
							localIpAddresses
							firstSeen
							lastSeen
						}
					}
					pageInfo {
						endCursor
						hasNextPage
					}
				}
			}
		}
	}
	"""
    view_columns = (
        extras.ColumnDefinitionString('Destination'),
        extras.ColumnDefinitionInteger('Visit Count'),
        extras.ColumnDefinitionString('IP Address', width=25),
        extras.ColumnDefinitionString('Username'),
        extras.ColumnDefinitionString('Hostname'),
        extras.ColumnDefinitionString('Local IP Addresses'),
        extras.ColumnDefinitionDatetime('First Hit'),
        extras.ColumnDefinitionDatetime('Last Hit'),
    )

    def format_node_data(self, connection):
        deaddrop_destination = connection['deaddropDeployment']['destination']
        if not deaddrop_destination:
            return None
        row = (deaddrop_destination, connection['count'], connection['ip'],
               connection['localUsername'], connection['localHostname'],
               connection['localIpAddresses'],
               _dt_field(connection['firstSeen']),
               _dt_field(connection['lastSeen']))
        return row