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