class Plugin(plugins.ClientPlugin): authors = ['Spencer McIntyre'] # the plugins author classifiers = ['Plugin :: Client' ] # the list of classifiers for categorization title = 'Hello World!' # the title of the plugin to be shown to users description = """ A 'hello world' plugin to serve as a basic template and demonstration. This plugin will display a message box when King Phisher exits. """ # a description of the plugin to be shown to users homepage = 'https://github.com/securestate/king-phisher-plugins' # an optional home page options = [ # specify options which can be configured through the GUI plugins.ClientOptionString( 'name', # the name of the option as it will appear in the configuration 'The name to which to say goodbye.', # the description of the option as shown to users default='Alice Liddle', # a default value for the option display_name='Your Name' # a name of the option as shown to users ), plugins.ClientOptionBoolean('validiction', 'Whether or not this plugin say good bye.', default=True, display_name='Say Good Bye'), plugins.ClientOptionInteger('some_number', 'An example number option.', default=1337, display_name='A Number'), plugins.ClientOptionPort('tcp_port', 'The TCP port to connect to.', default=80, display_name='Connection Port') ] req_min_py_version = '3.3.0' # (optional) specify the required minimum version of python req_min_version = '1.4.0' # (optional) specify the required minimum version of king phisher req_packages = { # (optional) specify a dictionary of required package names 'advancedhttpserver': has_ahs # set from within the exception handler when importing } req_platforms = ('Linux', 'Windows' ) # (optional) specify the supported platforms version = '1.0' # (optional) specify this plugin's version # this is the primary plugin entry point which is executed when the plugin is enabled def initialize(self): print('Hello World!') self.signal_connect('exit', self.signal_exit) # it is necessary to return True to indicate that the initialization was successful # this allows the plugin to check its options and return false to indicate a failure return True # this is a cleanup method to allow the plugin to close any open resources def finalize(self): print('Good Bye World!') # the plugin connects this handler to the applications 'exit' signal def signal_exit(self, app): # check the 'validiction' option in the configuration if not self.config['validiction']: return gui_utilities.show_dialog_info( "Good bye {0}!".format(self.config['name']), app.get_active_window())
class Plugin( getattr(plugins, 'ClientPluginMailerAttachment', plugins.ClientPlugin)): authors = ['Ryan Hanson', 'Spencer McIntyre', 'Erik Daguerre'] classifiers = ['Plugin :: Client :: Email :: Attachment', 'Script :: CLI'] title = 'Phishery DOCX URL Injector' description = """ Inject Word Document Template URLs into DOCX files. The Phishery technique is used to place multiple document template URLs into the word document (one per-line from the plugin settings). """ homepage = 'https://github.com/securestate/king-phisher-plugins' options = [ plugins.ClientOptionString( 'target_url', 'An optional target URLs. The default is the phishing URLs.', default='{{ url.webserver }}', display_name='Target URLs', **({ 'multiline': True } if api_compatible else {})), plugins.ClientOptionBoolean( 'add_landing_pages', 'Add all document URLs as landing pages to track visits.', default=True, display_name='Add Landing Pages') ] reference_urls = ['https://github.com/ryhanson/phishery'] req_min_version = min_version version = '2.2.0' def initialize(self): mailer_tab = self.application.main_tabs['mailer'] self.text_insert = mailer_tab.tabs['send_messages'].text_insert self.signal_connect('send-precheck', self.signal_send_precheck, gobject=mailer_tab) return True def _get_target_url(self, target): target_url = self.config['target_url'].strip() if target_url: return self.render_template_string(target_url, target=target, description='target url') target_url = self.application.config['mailer.webserver_url'] if target is not None: target_url += '?id=' + target.uid return target_url def process_attachment_file(self, input_path, output_path, target=None): if not path_is_doc_file(input_path): return target_url = self._get_target_url(target) if target_url is None: self.logger.warning( 'failed to get the target url, can not inject into the docx file' ) return phishery_inject(input_path, target_url, output_file=output_path) self.logger.info('wrote the patched file to: ' + output_path + ('' if target is None else ' with uid: ' + target.uid)) def signal_send_precheck(self, _): input_path = self.application.config['mailer.attachment_file'] if not path_is_doc_file(input_path): self.text_insert( 'The attachment is not compatible with the phishery plugin.\n') return False target_url = self._get_target_url(None) if target_url is None: self.text_insert('The phishery target URL is invalid.\n') return False if not self.config['add_landing_pages']: return True document_urls = target_url.split() for document_url in document_urls: parsed_url = urllib.parse.urlparse(document_url) hostname = parsed_url.netloc landing_page = parsed_url.path landing_page.lstrip('/') self.application.rpc('campaign/landing_page/new', self.application.config['campaign_id'], hostname, landing_page) return True
class Plugin(plugins.ClientPlugin): authors = ['Spencer McIntyre', 'Mike Stringer'] classifiers = ['Plugin :: Client :: Email :: Spam Evasion'] title = 'Message Padding' description = """ Add and modify custom HTML messages from a file to reduce Spam Assassin scores. This plugin interacts with the message content to append a long series of randomly generated sentences to meet the ideal image-text ratio. """ homepage = 'https://github.com/securestate/king-phisher-plugins' options = [ plugins.ClientOptionString( 'corpus', description='Text file containing text to generate dynamic padding', default=os.path.join(PLUGIN_PATH, 'corpus.txt'), display_name='Corpus File'), plugins.ClientOptionBoolean( 'dynamic_padding', description= 'Sets whether dynamically generated or static padding is appended to the messaged', default=True) ] req_min_version = '1.10.0' version = '1.0' req_packages = {'markovify': has_markovify} def initialize(self): mailer_tab = self.application.main_tabs['mailer'] self.signal_connect('message-create', self.signal_message_create, gobject=mailer_tab) if os.path.isfile(os.path.realpath(self.config['corpus'])): self.corpus = os.path.realpath(self.config['corpus']) else: self.corpus = None self.logger.debug('corpus file: ' + repr(self.corpus)) if self.corpus: self.dynamic = self.config['dynamic_padding'] else: if self.config['dynamic_padding']: self.logger.warning( 'the corpus file is unavailable, ignoring the dynamic padding setting' ) self.dynamic = False return True def signal_message_create(self, mailer_tab, target, message): for part in message.walk(): if not part.get_content_type().startswith('text/html'): continue payload_string = part.payload_string tag = '</html>' if tag not in payload_string: self.logger.warning('can not find ' + tag + ' tag to anchor the message padding') continue part.payload_string = payload_string.replace( tag, self.make_padding() + tag) def make_padding(self): if self.dynamic: f = open(self.corpus, 'r') text = markovify.Text(f) self.logger.info('generating dynamic padding from corpus') pad = '<p style="font-size: 0px">' for i in range(1, 50): temp = text.make_sentence() if temp is not None: pad += ' ' + temp if i % 5 == 0: pad += ' </br>' else: pad += ' </br>' pad += ' </p>' self.logger.info('dynamic padding generated successfully') f.close() else: self.logger.warning('message created using static padding') pad = STATIC_PADDING return pad
class Plugin( getattr(plugins, 'ClientPluginMailerAttachment', plugins.ClientPlugin)): authors = ['Ryan Hanson', 'Spencer McIntyre', 'Erik Daguerre'] classifiers = ['Plugin :: Client :: Email :: Attachment', 'Script :: CLI'] title = 'Phishery DOCX URL Injector' description = """ Inject Word Document Template URLs into DOCX files. The Phishery technique is used to place multiple document template URLs into the word document (one per line from the plugin settings).\n\n * HTTP URL\n\n The Jinja variable {{ url.webserver }} can be used for an HTTP URL to track when documents are opened.\n Note that to only track opened documents, DO NOT put a URL link into the phishing email to the landing page. This will ensure that visits are only registered for instance where the document is opened.\n\n * HTTPS URL\n\n The Jinja variable {{ url.webserver }} can be used for an HTTPS landing page that requires basic authentication.\n Note that for HTTPS URLs, the King Phisher server needs to be configured with a proper, trusted SSL certificate for the user to be presented with the basic authentication prompt.\n\n * FILE URL\n\n Utilizing the file://yourtargetserver/somepath URL format will capture SMB credentials.\n Note that King Phisher does not support SMB, and utilization of SMB requires that a separate capture/sniffer application such as Metasploit's auxiliary/server/capture/smb module will have to be used to capture NTLM hashes. The plugin and King Phisher will only support injecting the URL path into the document. """ homepage = 'https://github.com/securestate/king-phisher-plugins' options = [ plugins.ClientOptionString( 'target_url', 'An optional target URLs. The default is the phishing URLs.', default='{{ url.webserver }}', display_name='Target URLs', **({ 'multiline': True } if api_compatible else {})), plugins.ClientOptionBoolean( 'add_landing_pages', 'Add all document URLs as landing pages to track visits.', default=True, display_name='Add Landing Pages') ] reference_urls = ['https://github.com/ryhanson/phishery'] req_min_version = min_version version = '2.1.0' def initialize(self): mailer_tab = self.application.main_tabs['mailer'] self.text_insert = mailer_tab.tabs['send_messages'].text_insert self.signal_connect('send-precheck', self.signal_send_precheck, gobject=mailer_tab) return True def _get_target_url(self, target): target_url = self.config['target_url'].strip() if target_url: return self.render_template_string(target_url, target=target, description='target url') target_url = self.application.config['mailer.webserver_url'] if target is not None: target_url += '?id=' + target.uid return target_url def process_attachment_file(self, input_path, output_path, target=None): if not path_is_doc_file(input_path): return target_url = self._get_target_url(target) if target_url is None: self.logger.warning( 'failed to get the target url, can not inject into the docx file' ) return phishery_inject(input_path, target_url, output_file=output_path) self.logger.info('wrote the patched file to: ' + output_path + ('' if target is None else ' with uid: ' + target.uid)) def signal_send_precheck(self, _): input_path = self.application.config['mailer.attachment_file'] if not path_is_doc_file(input_path): self.text_insert( 'The attachment is not compatible with the phishery plugin.\n') return False target_url = self._get_target_url(None) if target_url is None: self.text_insert('The phishery target URL is invalid.\n') return False if not self.config['add_landing_pages']: return True document_urls = target_url.split() for document_url in document_urls: parsed_url = urllib.parse.urlparse(document_url) hostname = parsed_url.netloc landing_page = parsed_url.path landing_page.lstrip('/') self.application.rpc('campaign/landing_page/new', self.application.config['campaign_id'], hostname, landing_page) return True
class Plugin(plugins.ClientPlugin): authors = ['Spencer McIntyre'] title = 'Campaign Message Configuration Manager' description = """ Store campaign message configurations for their respective campaigns. This allows users to switch between campaigns while keeping each of the message configurations and restoring them when the user returns to the original campaign. New campaigns can either be created with customizable default settings or from the existing configuration (see the "Transfer Settings" option). """ homepage = 'https://github.com/securestate/king-phisher-plugins' options = [ plugins.ClientOptionBoolean( 'transfer_options', 'Whether or not to keep the current settings for new campaigns.', default=True, display_name='Transfer Settings') ] req_min_version = '1.10.0b1' version = '1.0.1' def initialize(self): self.signal_connect('campaign-set', self.signal_kpc_campaign_set) self.storage = self.load_storage() # create the submenu for setting and clearing the default config submenu = 'Tools > Message Configuration' self.add_submenu(submenu) self.menu_items = { 'set_defaults': self.add_menu_item(submenu + ' > Set Defaults', self.menu_item_set_defaults), 'clear_defaults': self.add_menu_item(submenu + ' > Clear Defaults', self.menu_item_clear_defaults) } return True def finalize(self): self.set_campaign_config(self.get_current_config(), self.application.config['campaign_id']) self.save_storage() def menu_item_clear_defaults(self, _): proceed = gui_utilities.show_dialog_yes_no( 'Clear Default Campaign Configuration?', self.application.get_active_window(), 'Are you sure you want to clear the default\n'\ + 'message configuration for new campaigns?' ) if not proceed: return self.storage['default'] = {} def menu_item_set_defaults(self, _): proceed = gui_utilities.show_dialog_yes_no( 'Set The Default Campaign Configuration?', self.application.get_active_window(), 'Are you sure you want to set the default\n'\ + 'message configuration for new campaigns?' ) if not proceed: return self.storage['default'] = self.get_current_config() @property def storage_file_path(self): """The path on disk of where to store the plugin's data.""" return os.path.join(self.application.user_data_path, 'campaign_message_config.json') def load_default_config(self): """ Load the default configuration to use when settings are missing. This will load the user's configured defaults and fail back to the core ones distributed with the application. :return: The default configuration. :rtype: dict """ default_client_config = find.data_file('client_config.json') with open(default_client_config, 'r') as tmp_file: default_client_config = serializers.JSON.load(tmp_file) users_defaults = self.storage['default'] for key, value in users_defaults.items(): if not is_managed_key(key): continue if key not in default_client_config: continue default_client_config[key] = value return default_client_config def load_storage(self): """ Load this plugin's stored data from disk. :return: The plugin's stored data. :rtype: dict """ storage = {'campaigns': {}, 'default': {}} file_path = self.storage_file_path if os.path.isfile(file_path) and os.access(file_path, os.R_OK): self.logger.debug( 'loading campaign messages configuration file: ' + file_path) with open(file_path, 'r') as file_h: storage = serializers.JSON.load(file_h) else: self.logger.debug('campaigns configuration file not found') return storage def save_storage(self): """Save this plugin's stored data to disk.""" file_path = self.storage_file_path self.logger.debug('writing campaign messages configuration file: ' + file_path) with open(file_path, 'w') as file_h: serializers.JSON.dump(self.storage, file_h, pretty=True) def get_campaign_config(self, campaign_id=None): """ Get the message configuration for a specific campaign. If *campaign_id* is not specified, then the current campaign is used. If not settings are available for the specified campaign, an empty dictionary is returned. :param str campaign_id: The ID of the campaign. :return: The campaign's message configuration or an empty dictionary. :rtype: dict """ if campaign_id is None: campaign_id = self.application.config['campaign_id'] campaign_id = str(campaign_id) if not self.storage.get('campaigns'): return {} return self.storage['campaigns'].get(campaign_id, {}).get('configuration') def set_campaign_config(self, config, campaign_id=None): """ Add the message configuration into the plugin's storage data and associate it with the specified campaign. If *campaign_id* is not specified, then the current campaign is used. :param dict config: :param str campaign_id: The ID of the campaign. """ if campaign_id is None: campaign_id = self.application.config['campaign_id'] campaign_id = str(campaign_id) config = { 'created': datetime.datetime.utcnow(), 'configuration': config } self.storage['campaigns'][campaign_id] = config def get_message_config_tab(self): main_window = self.application.main_window mailer_tab = main_window.tabs['mailer'] return mailer_tab.tabs['config'] mailer_config_tab.objects_save_to_config() def get_current_config(self): """ Get the current configuration options that are managed. This saves the settings from the message configuration tab to the standard configuration then returns a new dictionary with all of the managed settings. :return: The current configuration options. :rtype: dict """ app_config = self.application.config mailer_config_tab = self.get_message_config_tab() mailer_config_tab.objects_save_to_config() current_config = dict((key, value) for key, value in app_config.items() if is_managed_key(key)) return current_config def signal_kpc_campaign_set(self, app, old_campaign_id, new_campaign_id): dft_config = self.load_default_config() app_config = self.application.config mailer_config_tab = self.get_message_config_tab() if old_campaign_id is not None: # switching campaigns self.set_campaign_config(self.get_current_config(), old_campaign_id) self.save_storage() new_campaign_config = self.get_campaign_config(new_campaign_id) if new_campaign_config: for key in app_config.keys(): if not is_managed_key(key): continue if key in new_campaign_config: app_config[key] = new_campaign_config[key] elif key in dft_config: app_config[key] = dft_config[key] elif not self.config['transfer_options']: for key in app_config.keys(): if not is_managed_key(key): continue app_config[key] = dft_config.get(key) mailer_config_tab.objects_load_from_config()
class Plugin(plugins.ClientPlugin): authors = ['Spencer McIntyre'] classifiers = ['Plugin :: Client :: Tool'] title = 'Blink(1) Notifications' description = """ A plugin which will flash a Blink(1) peripheral based on campaign events such as when a new visit is received or new credentials have been submitted. """ homepage = 'https://github.com/securestate/king-phisher-plugins' options = [ plugins.ClientOptionBoolean( 'filter_campaigns', 'Only show events for the current campaign.', default=True, display_name='Current Campaign Only'), plugins.ClientOptionEnum( 'color_visits', 'The color to flash the Blink(1) for new visits.', choices=COLORS, default='yellow', display_name='Visits Flash Color'), plugins.ClientOptionEnum( 'color_credentials', 'The color to flash the Blink(1) for new credentials.', choices=COLORS, default='red', display_name='Credentials Flash Color'), ] reference_urls = ['https://blink1.thingm.com/'] req_min_version = '1.6.0' req_packages = {'blink1': has_blink1} req_platforms = ('Linux', ) version = '1.1' def initialize(self): self._color = None try: self._blink1 = blink1.Blink1() self._blink1_off() except usb.core.USBError as error: gui_utilities.show_dialog_error( 'Connection Error', self.application.get_active_window(), 'Unable to connect to the Blink(1) device.') return False except blink1.BlinkConnectionFailed: gui_utilities.show_dialog_error( 'Connection Error', self.application.get_active_window(), 'Unable to find the Blink(1) device.') return False self._gsrc_id = None if self.application.server_events is None: self.signal_connect('server-connected', lambda app: self._connect_server_events()) else: self._connect_server_events() return True def finalize(self): self._blink1_off() self._blink1.close() self._blink1 = None def _blink1_set_color(self, color): try: self._blink1.fade_to_color(375, color) except usb.core.USBError as error: self.logger.warning( "encountered a USB error '{0}' while setting the color of the blink(1)" .format(error.strerror)) return if color != 'black': if self._gsrc_id is not None: GLib.source_remove(self._gsrc_id) self._gsrc_id = GLib.timeout_add(1625, self._blink1_off) self._color = color def _blink1_off(self): self._blink1_set_color('black') self._color = None def _blink1_off_timeout(self): self._gsrc_id = None self._blink1_off() return False def _connect_server_events(self): self.signal_connect_server_event('db-credentials', self.signal_db_credentials, ('inserted', ), ('id', 'campaign_id')) self.signal_connect_server_event('db-visits', self.signal_db_visits, ('inserted', ), ('id', 'campaign_id')) return True def _signal_db(self, color, rows): if self.config['filter_campaigns']: if all( str(row.campaign_id) != self.application.config['campaign_id'] for row in rows): return self._blink1_set_color(color) @server_events.event_type_filter('inserted', is_method=True) def signal_db_credentials(self, _, event_type, rows): self._signal_db(self.config['color_credentials'], rows) @server_events.event_type_filter('inserted', is_method=True) def signal_db_visits(self, _, event_type, rows): self._signal_db(self.config['color_visits'], rows)