Skip to content

Commit

Permalink
Merge dev into master for v1.8.0b
Browse files Browse the repository at this point in the history
  • Loading branch information
zeroSteiner committed May 22, 2017
2 parents 7ddb2d4 + a25e589 commit 7a0f459
Show file tree
Hide file tree
Showing 35 changed files with 671 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
*.mmdb
*.py[cod]
*.yml

.pylintrc
.python-version
build/*
dist/*
docs/build/*
Expand Down
2 changes: 1 addition & 1 deletion KingPhisher
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def main():

logger.debug("king phisher version: {0} python version: {1}.{2}.{3}".format(version.version, sys.version_info[0], sys.version_info[1], sys.version_info[2]))
if its.py_v2:
logger.warning('python 2.7 support is deprecated, see https://github.com/securestate/king-phisher/wiki/Python-3 for details')
logger.warning('this is the last release that will support python 2.7, see https://github.com/securestate/king-phisher/wiki/Python-3 for details')
logger.debug("client running in process: {0} main tid: 0x{1:x}".format(os.getpid(), threading.current_thread().ident))

start_time = time.time()
Expand Down
2 changes: 1 addition & 1 deletion KingPhisherServer
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def main():
log_file = _ex_config_logging(arguments, config, console_log_handler)
logger.debug("king phisher version: {0} python version: {1}.{2}.{3}".format(version.version, sys.version_info[0], sys.version_info[1], sys.version_info[2]))
if its.py_v2:
logger.warning('python 2.7 support is deprecated, see https://github.com/securestate/king-phisher/wiki/Python-3 for details')
logger.warning('this is the last release that will support python 2.7, see https://github.com/securestate/king-phisher/wiki/Python-3 for details')

# initialize the plugin manager
try:
Expand Down
4 changes: 3 additions & 1 deletion data/client/king_phisher/client_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"dashboard.bottom": "VisitsTimeline",
"dashboard.top_left": "Overview",
"dashboard.top_right": "VisitorInfo",
"mailer.attachment_file": null,
"mailer.attachment_file.post_processing": null,
"mailer.max_messages_per_connection": 5,
"mailer.message_type": "email",
"mailer.target_field": "to",
Expand All @@ -28,5 +30,5 @@
"ssh_server": "localhost:22",
"ssh_username": "",
"text_font": "monospace 11",
"text_source_theme": "cobalt"
"text_source.theme": "cobalt"
}
20 changes: 20 additions & 0 deletions data/client/king_phisher/completion/jinja.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"start": null,
"summary": null
},
"campaign": {
"id": null,
"name": null
},
"client": {
"company_name": null,
"email_address": null,
Expand All @@ -16,6 +20,20 @@
"message_id": null
},
"inline_image(": null,
"message": {
"attachment": null,
"importance": null,
"recipient": {
"bcc": null,
"cc": null,
"field": null,
"to": null
},
"sensitivity": null,
"subject": null,
"template": null,
"type": null
},
"message_type": null,
"sender": {
"email": null,
Expand All @@ -40,7 +58,9 @@
"credential_count": null,
"expiration": null,
"has_expired": null,
"id": null,
"message_count": null,
"name": null,
"visit_count": null
},
"credential_count": null,
Expand Down
18 changes: 17 additions & 1 deletion data/client/king_phisher/king-phisher-client.ui
Original file line number Diff line number Diff line change
Expand Up @@ -5372,7 +5372,7 @@ host key will store it for identification verification of future connections.</p
</object>
<object class="GtkDialog" id="HostKeyWarnDialog">
<property name="width_request">450</property>
<property name="height_request">300</property>
<property name="height_request">325</property>
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="default_width">450</property>
Expand Down Expand Up @@ -5554,6 +5554,22 @@ your communications. Please contact the system administrator.</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="HostKeyWarnDialog.checkbutton_accept_permanently">
<property name="label" translatable="yes">Accept This Key Permanently</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="inconsistent">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
Expand Down
1 change: 1 addition & 0 deletions docs/requirements.rtd.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ alembic>=0.8.10
blinker>=1.4
boltons>=17.0.0
dnspython>=1.15.0
email-validator>=1.0.2
geoip2>=2.4.2
geojson>=1.3.3
graphene>=1.1.3
Expand Down
19 changes: 18 additions & 1 deletion docs/source/change_log.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,27 @@ Phisher.
Version 1.x.x
-------------

Version 1.8.0
^^^^^^^^^^^^^

*In Progress*

* Install script now supports Red Hat Server 7
* Support the client on OS X by using Docker
* Support for issuing certificates with acme while the server is running
* Add a wrapping tool for certbot to make the process easier

Version 1.7.1
^^^^^^^^^^^^^

Released :release:`1.7.1` on April 14th, 2017

* Bug fix in the Windows build for HTTPS connections from the requests package

Version 1.7.0
^^^^^^^^^^^^^

Released :release:`1.6.0` on April 4th, 2017
Released :release:`1.7.0` on April 4th, 2017

* Better error messages for malformed server configuration files
* Support for sending to targets via To / CC / BCC fields
Expand Down
9 changes: 6 additions & 3 deletions docs/source/client/gobject_signals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ object.
:signal flags: ``SIGNAL_ACTION | SIGNAL_RUN_LAST``
:param str campaign_id: The ID of the campaign.

.. py:function:: campaign-set(campaign_id)
.. py:function:: campaign-set(old_campaign_id, new_campaign_id)
This signal is emitted when the user sets the current campaign. Subscribers
to this signal can use it to update and refresh information for the current
campaign.
campaign. The :py:attr:`~KingPhisherClientApplication.config` "campaign_id"
and "campaign_name" keys have already been updated with the new values when
this signal is emitted.

:signal flags: ``SIGNAL_RUN_FIRST``
:param str campaign_id: The ID of the campaign.
:param str old_campaign_id: The ID of the old campaign or None if the client is selecting one for the first time.
:param str new_campaign_id: The ID of the new campaign.

.. py:function:: config-load(load_defaults)
Expand Down
5 changes: 5 additions & 0 deletions docs/source/king_phisher/client/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ Classes
:show-inheritance:
:members:

.. autoclass:: king_phisher.client.plugins.ClientPluginMailerAttachment
:show-inheritance:
:members:
:special-members: __init__

.. autoclass:: king_phisher.client.plugins.ClientPluginManager
:show-inheritance:
:members:
2 changes: 2 additions & 0 deletions docs/source/king_phisher/spf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Data

.. autodata:: king_phisher.spf.MAX_QUERIES

.. autodata:: king_phisher.spf.MAX_QUERIES_VOID

.. autodata:: king_phisher.spf.QUALIFIERS
:annotation:

Expand Down
15 changes: 10 additions & 5 deletions king_phisher/client/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class KingPhisherClientApplication(_Gtk_Application):
'campaign-changed': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
'campaign-created': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
'campaign-delete': (GObject.SIGNAL_ACTION | GObject.SIGNAL_RUN_LAST, None, (str,)),
'campaign-set': (GObject.SIGNAL_RUN_FIRST, None, (str,)),
'campaign-set': (GObject.SIGNAL_RUN_FIRST, None, (str, str)),
'config-load': (GObject.SIGNAL_ACTION | GObject.SIGNAL_RUN_LAST, None, (bool,)),
'config-save': (GObject.SIGNAL_ACTION | GObject.SIGNAL_RUN_LAST, None, ()),
'credential-delete': (GObject.SIGNAL_ACTION | GObject.SIGNAL_RUN_LAST, None, (object,)),
Expand Down Expand Up @@ -139,6 +139,9 @@ def __init__(self, config_file=None, use_plugins=True, use_style=True):
self.logger.debug("gi.repository VTE version: {0}".format(rpc_terminal.Vte._version))
if graphs.has_matplotlib:
self.logger.debug("matplotlib version: {0}".format(graphs.matplotlib.__version__))
# do not negotiate a single instance application
# https://developer.gnome.org/gio/unstable/GApplication.html#G-APPLICATION-NON-UNIQUE:CAPS
self.set_flags(Gio.ApplicationFlags.NON_UNIQUE)
self.set_property('application-id', 'org.king-phisher.client')
self.set_property('register-session', True)
self.config_file = config_file or os.path.join(USER_DATA_PATH, 'config.json')
Expand Down Expand Up @@ -390,7 +393,7 @@ def do_activate(self):
)
self.config['plugins.enabled'] = enabled_plugins

def do_campaign_set(self, campaign_id):
def do_campaign_set(self, *_):
self.logger.info("campaign set to {0} (id: {1})".format(self.config['campaign_name'], self.config['campaign_id']))
self.emit('rpc-cache-clear')

Expand Down Expand Up @@ -428,8 +431,10 @@ def do_reload_css_style(self):
self.style_provider = self.load_style_css(theme_file)

def do_rpc_cache_clear(self):
if self.rpc:
self.rpc.cache_clear()
if not self.rpc:
return
self.rpc.cache_clear()
self.logger.debug('the rpc cache has been cleared')

def do_server_connected(self):
self.load_server_config()
Expand All @@ -447,7 +452,7 @@ def do_server_connected(self):
return True
campaign_info = self.rpc.remote_table_row('campaigns', self.config['campaign_id'], cache=True, refresh=True)
self.config['campaign_name'] = campaign_info.name
self.emit('campaign-set', self.config['campaign_id'])
self.emit('campaign-set', None, self.config['campaign_id'])
return

def do_shutdown(self):
Expand Down
8 changes: 4 additions & 4 deletions king_phisher/client/dialogs/campaign_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,11 @@ def interact(self):
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:
campaign_id = model.get_value(tree_iter, 0)
self.config['campaign_id'] = campaign_id
new_campaign_id = model.get_value(tree_iter, 0)
self.config['campaign_id'] = new_campaign_id
campaign_name = model.get_value(tree_iter, 1)
self.config['campaign_name'] = campaign_name
if not (campaign_id == old_campaign_id and campaign_name == old_campaign_name):
self.application.emit('campaign-set', campaign_id)
if not (new_campaign_id == old_campaign_id and campaign_name == old_campaign_name):
self.application.emit('campaign-set', old_campaign_id, new_campaign_id)
self.dialog.destroy()
return response
29 changes: 23 additions & 6 deletions king_phisher/client/dialogs/ssh_host_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ def key_details(self):
if key_type.startswith('ssh-'):
key_type = key_type[4:]
key_type = key_type.split('-', 1)[0].upper()
details += "{0} key fingerprint is SHA256:{1}.\n".format(key_type, base64.b64encode(hashlib.new('sha256', self.key.asbytes()).digest()).decode('utf-8'))
details += "{0} key fingerprint is MD5:{1}.\n".format(key_type, binascii.b2a_hex(hashlib.new('md5', self.key.asbytes()).digest()).decode('utf-8'))
details += "{0} key fingerprint is SHA256:{1}\n".format(key_type, base64.b64encode(hashlib.new('sha256', self.key.asbytes()).digest()).decode('utf-8'))
details += "{0} key fingerprint is MD5:{1}\n".format(key_type, binascii.b2a_hex(hashlib.new('md5', self.key.asbytes()).digest()).decode('utf-8'))
return details

def interact(self):
Expand All @@ -119,6 +119,17 @@ class HostKeyWarnDialog(BaseHostKeyDialog):
default_button = Gtk.ResponseType.REJECT
def signal_checkbutton_toggled(self, button):
self.gobjects['button_accept'].set_sensitive(button.get_property('active'))
cb_accept_permanently = self.gtk_builder_get('checkbutton_accept_permanently')
cb_accept_permanently.set_sensitive(button.get_property('active'))
cb_accept_permanently.set_property('active', False)
cb_accept_permanently.set_property('inconsistent', not button.get_property('active'))

@property
def accept_permanently(self):
cb_accept_permanently = self.gtk_builder_get('checkbutton_accept_permanently')
permanent = not cb_accept_permanently.get_property('inconsistent')
permanent &= cb_accept_permanently.get_property('active')
return permanent

class MissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
"""
Expand All @@ -141,12 +152,15 @@ def __init__(self, application):
def missing_host_key(self, client, hostname, key):
host_key_fingerprint = 'sha256:' + base64.b64encode(hashlib.new('sha256', key.asbytes()).digest()).decode('utf-8')
host_keys = paramiko.hostkeys.HostKeys()
host_keys_modified = False
known_hosts_file = self.application.config.get('ssh_known_hosts_file', os.path.join(GLib.get_user_config_dir(), 'king-phisher', 'known_hosts'))

if os.access(known_hosts_file, os.R_OK):
self.logger.debug('loading known ssh host keys from: ' + known_hosts_file)
host_keys.load(known_hosts_file)
else:
self.logger.warning('can not read known ssh host keys from: ' + known_hosts_file)

add_host_key = False
if host_keys.lookup(hostname):
if host_keys.check(hostname, key):
self.logger.debug("accepting known ssh host key {0} {1} {2}".format(hostname, key.get_name(), host_key_fingerprint))
Expand All @@ -155,14 +169,17 @@ def missing_host_key(self, client, hostname, key):
dialog = HostKeyWarnDialog(self.application, hostname, key)
if dialog.interact() != Gtk.ResponseType.ACCEPT:
raise errors.KingPhisherAbortError('bad ssh host key for ' + hostname)
add_host_key = dialog.accept_permanently
else:
dialog = HostKeyAcceptDialog(self.application, hostname, key)
if dialog.interact() != Gtk.ResponseType.ACCEPT:
raise errors.KingPhisherAbortError('unknown ssh host key not accepted by the user for ' + hostname)
host_keys.add(hostname, key.get_name(), key)
host_keys_modified = True
add_host_key = True

if host_keys_modified:
if add_host_key:
self.logger.debug("setting ssh host key {0} for {1}".format(key.get_name(), hostname))
host_keys.pop(hostname)
host_keys.add(hostname, key.get_name(), key)
try:
host_keys.save(known_hosts_file)
os.chmod(known_hosts_file, 0o600)
Expand Down
30 changes: 23 additions & 7 deletions king_phisher/client/mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ def iterate_targets(self, counting=False):
target = MessageTarget(
first_name=target_name[0].strip(),
last_name=target_name[1].strip(),
email_address=self.config['mailer.target_email_address'],
email_address=self.config['mailer.target_email_address'].strip(),
department=None,
uid=utilities.make_message_uid()
)
Expand All @@ -475,7 +475,7 @@ def iterate_targets(self, counting=False):
csv_reader = csv.DictReader(target_file_h, ('first_name', 'last_name', 'email_address', 'department'))
for line_no, raw_target in enumerate(csv_reader, 1):
if its.py_v2:
# this intentionally will cause a UnicodeDecodeError to be raised as is the behaviour in python 3.x
# this will intentionally cause a UnicodeDecodeError to be raised as is the behaviour in python 3.x
# when csv.DictReader is initialized
raw_target = dict((k, (v if v is None else v.decode('utf-8'))) for k, v in raw_target.items())
for required_field in ('first_name', 'last_name', 'email_address'):
Expand Down Expand Up @@ -513,6 +513,7 @@ def iterate_targets(self, counting=False):
self.logger.error("the configured target type '{0}' is unsupported".format(target_type))

def run(self):
self.logger.debug("mailer routine running in tid: 0x{0:x}".format(threading.current_thread().ident))
self.running.set()
self.should_stop.clear()
self.paused.clear()
Expand Down Expand Up @@ -672,13 +673,28 @@ def get_mime_attachments(self):
:rtype: :py:class:`.MessageAttachments`
"""
files = []
if self.config.get('mailer.attachment_file'):
attachment = self.config['mailer.attachment_file']
attachfile = mime.base.MIMEBase(*mimetypes.guess_type(attachment))
attachfile.set_payload(open(attachment, 'rb').read())
# allow the attachment_file.post_processing to be attached instead of
# attachment_file so attachment_file can be used as an input for
# arbitrary operations to modify without over writing the original
attachment_file = self.config.get('mailer.attachment_file.post_processing')
delete_attachment_file = False
if attachment_file is not None:
if not isinstance(attachment_file, str):
raise TypeError('config option mailer.attachment_file.post_processing is not a readable file')
if not os.path.isfile(attachment_file) and os.access(attachment_file, os.R_OK):
raise ValueError('config option mailer.attachment_file.post_processing is not a readable file')
self.config['mailer.attachment_file.post_processing'] = None
delete_attachment_file = True
else:
attachment_file = self.config.get('mailer.attachment_file')
if attachment_file:
attachfile = mime.base.MIMEBase(*mimetypes.guess_type(attachment_file))
attachfile.set_payload(open(attachment_file, 'rb').read())
encoders.encode_base64(attachfile)
attachfile.add_header('Content-Disposition', "attachment; filename=\"{0}\"".format(os.path.basename(attachment)))
attachfile.add_header('Content-Disposition', "attachment; filename=\"{0}\"".format(os.path.basename(attachment_file)))
files.append(attachfile)
if delete_attachment_file and os.access(attachment_file, os.W_OK):
os.remove(attachment_file)

images = []
for attachment_file, attachment_name in template_environment.attachment_images.items():
Expand Down
Loading

0 comments on commit 7a0f459

Please sign in to comment.