def _set_ids(self): """ Handle lazy resolution of the ``*_id`` properties necessary to track information. """ self._visit_id = None kp_cookie_name = self.config.get('server.cookie_name') if kp_cookie_name in self.cookies: value = self.cookies[kp_cookie_name].value if db_manager.get_row_by_id(self._session, db_models.Visit, value): self._visit_id = value self._message_id = None msg_id = self.get_query('id') if msg_id == self.config.get('server.secret_id'): self._message_id = msg_id elif msg_id and db_manager.get_row_by_id(self._session, db_models.Message, msg_id): self._message_id = msg_id elif self._visit_id: visit = db_manager.get_row_by_id(self._session, db_models.Visit, self._visit_id) self._message_id = visit.message_id self._campaign_id = None if self._message_id and self._message_id != self.config.get('server.secret_id'): message = db_manager.get_row_by_id(self._session, db_models.Message, self._message_id) if message: self._campaign_id = message.campaign_id
def message_id(self): """ The message id that is associated with the current request's visitor. This is retrieved by looking at an 'id' parameter in the query and then by checking the :py:attr:`~.KingPhisherRequestHandler.visit_id` value in the database. If no message id is associated, this value is None. The resulting value will be either a confirmed valid value, or the value of the configurations server.secret_id for testing purposes. """ if hasattr(self, '_message_id'): return self._message_id self._message_id = None msg_id = self.get_query('id') if msg_id == self.config.get('server.secret_id'): self._message_id = msg_id return self._message_id session = db_manager.Session() if msg_id and db_manager.get_row_by_id(session, db_models.Message, msg_id): self._message_id = msg_id elif self.visit_id: visit = db_manager.get_row_by_id(session, db_models.Visit, self.visit_id) self._message_id = visit.message_id session.close() return self._message_id
def handle_page_visit(self): if not self.message_id: return if self.message_id == self.config.get('server.secret_id'): return if not self.campaign_id: return client_ip = self.get_client_ip() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) if campaign.has_expired: self.logger.info("ignoring page visit for expired campaign id: {0} from IP address: {1}".format(self.campaign_id, client_ip)) session.close() return self.logger.info("handling a page visit for campaign id: {0} from IP address: {1}".format(self.campaign_id, client_ip)) message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message.opened is None and self.config.get_if_exists('server.set_message_opened_on_visit', True): message.opened = db_models.current_timestamp() message.opener_ip = self.get_client_ip() message.opener_user_agent = self.headers.get('user-agent', None) set_new_visit = True visit_id = make_uid() if self.visit_id: set_new_visit = False query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost, page=self.request_path[1:]) if query.count(): visit = db_manager.get_row_by_id(session, db_models.Visit, self.visit_id) if visit.message_id == self.message_id: visit_id = self.visit_id visit.visit_count += 1 else: set_new_visit = True if set_new_visit: kp_cookie_name = self.config.get('server.cookie_name') cookie = "{0}={1}; Path=/; HttpOnly".format(kp_cookie_name, visit_id) self.send_header('Set-Cookie', cookie) visit = db_models.Visit(id=visit_id, campaign_id=self.campaign_id, message_id=self.message_id) visit.visitor_ip = client_ip visit.visitor_details = self.headers.get('user-agent', '') session.add(visit) visit_count = len(campaign.visits) if visit_count > 0 and ((visit_count in [1, 10, 25]) or ((visit_count % 50) == 0)): alert_text = "{0} visits reached for campaign: {{campaign_name}}".format(visit_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, self.campaign_id)) self._handle_page_visit_creds(session, visit_id) trained = self.get_query('trained') if isinstance(trained, str) and trained.lower() in ['1', 'true', 'yes']: message.trained = True session.commit() session.close()
def get_template_vars_client(self): """ Build a dictionary of variables for a client with an associated campaign. :return: The client specific template variables. :rtype: dict """ client_vars = { 'address': self.get_client_ip() } if not self.message_id: return client_vars credential_count = 0 expired_campaign = True visit_count = 0 result = None if self.message_id == self.config.get('server.secret_id'): client_vars['company_name'] = 'Wonderland Inc.' client_vars['company'] = {'name': 'Wonderland Inc.'} result = ('*****@*****.**', 'Alice', 'Liddle', 0) elif self.message_id: message = db_manager.get_row_by_id(self._session, db_models.Message, self.message_id) if message: campaign = message.campaign.to_dict() campaign['message_count'] = self._session.query(db_models.Message).filter_by(campaign_id=message.campaign.id).count() campaign['visit_count'] = self._session.query(db_models.Visit).filter_by(campaign_id=message.campaign.id).count() campaign['credential_count'] = self._session.query(db_models.Credential).filter_by(campaign_id=message.campaign.id).count() client_vars['campaign'] = campaign if message.campaign.company: client_vars['company_name'] = message.campaign.company.name client_vars['company'] = message.campaign.company.to_dict() result = (message.target_email, message.first_name, message.last_name, message.trained) query = self._session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) credential_count = query.count() expired_campaign = message.campaign.has_expired if not result: return client_vars client_vars['email_address'] = result[0] client_vars['first_name'] = result[1] client_vars['last_name'] = result[2] client_vars['is_trained'] = result[3] client_vars['message_id'] = self.message_id if self.visit_id: visit = db_manager.get_row_by_id(self._session, db_models.Visit, self.visit_id) client_vars['visit_id'] = visit.id visit_count = visit.count client_vars['credential_count'] = credential_count client_vars['visit_count'] = visit_count + (0 if expired_campaign else 1) return client_vars
def get_template_vars_client(self): """ Build a dictionary of variables for a client with an associated campaign. :return: The client specific template variables. :rtype: dict """ client_vars = { 'address': self.get_client_ip() } if not self.message_id: return client_vars visit_count = 0 result = None session = db_manager.Session() if self.message_id == self.config.get('server.secret_id'): client_vars['company_name'] = 'Wonderland Inc.' client_vars['company'] = {'name': 'Wonderland Inc.'} result = ('*****@*****.**', 'Alice', 'Liddle', 0) elif self.message_id: message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message: if message.campaign.company: client_vars['company_name'] = message.campaign.company.name client_vars['company'] = { 'name': message.campaign.company.name, 'url_email': message.campaign.company.url_email, 'url_main': message.campaign.company.url_main, 'url_remote_access': message.campaign.company.url_remote_access } result = (message.target_email, message.first_name, message.last_name, message.trained) if not result: session.close() return client_vars client_vars['email_address'] = result[0] client_vars['first_name'] = result[1] client_vars['last_name'] = result[2] client_vars['is_trained'] = result[3] client_vars['message_id'] = self.message_id if self.visit_id: visit = db_manager.get_row_by_id(session, db_models.Visit, self.visit_id) client_vars['visit_id'] = visit.id visit_count = visit.visit_count # increment the count preemptively client_vars['visit_count'] = visit_count + 1 session.close() return client_vars
def get_template_vars_client(self): """ Build a dictionary of variables for a client with an associated campaign. :return: The client specific template variables. :rtype: dict """ client_vars = {"address": self.get_client_ip()} if not self.message_id: return client_vars visit_count = 0 result = None session = db_manager.Session() if self.message_id == self.config.get("server.secret_id"): client_vars["company_name"] = "Wonderland Inc." client_vars["company"] = {"name": "Wonderland Inc."} result = ("*****@*****.**", "Alice", "Liddle", 0) elif self.message_id: message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message: if message.campaign.company: client_vars["company_name"] = message.campaign.company.name client_vars["company"] = { "name": message.campaign.company.name, "url_email": message.campaign.company.url_email, "url_main": message.campaign.company.url_main, "url_remote_access": message.campaign.company.url_remote_access, } result = (message.target_email, message.first_name, message.last_name, message.trained) if not result: session.close() return client_vars client_vars["email_address"] = result[0] client_vars["first_name"] = result[1] client_vars["last_name"] = result[2] client_vars["is_trained"] = result[3] client_vars["message_id"] = self.message_id if self.visit_id: visit = db_manager.get_row_by_id(session, db_models.Visit, self.visit_id) client_vars["visit_id"] = visit.id visit_count = visit.visit_count # increment the count preemptively client_vars["visit_count"] = visit_count + 1 session.close() return client_vars
def _respond_file_check_id(self): if not self.config.get('server.require_id'): return if self.message_id == self.config.get('server.secret_id'): return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.server.logger.warning('denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: self.server.logger.warning('denying request with not found due to invalid hostname') session.close() raise errors.KingPhisherAbortRequestError() if campaign.reject_after_credentials and self.visit_id == None: query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count(): self.server.logger.warning('denying request because credentials were already harvested') session.close() raise errors.KingPhisherAbortRequestError() session.close() return
def _respond_file_check_id(self): if re.match(r'^/\.well-known/acme-challenge/[a-zA-Z0-9\-_]{40,50}$', self.request_path): self.server.logger.info('received request for .well-known/acme-challenge') return if not self.config.get('server.require_id'): return if self.message_id == self.config.get('server.secret_id'): return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.server.logger.warning('denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: self.server.logger.warning('denying request with not found due to invalid hostname') session.close() raise errors.KingPhisherAbortRequestError() if campaign.has_expired: self.server.logger.warning('denying request because the campaign has expired') session.close() raise errors.KingPhisherAbortRequestError() if campaign.reject_after_credentials and self.visit_id is None: query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count(): self.server.logger.warning('denying request because credentials were already harvested') session.close() raise errors.KingPhisherAbortRequestError() session.close() return
def _handle_page_visit_creds(self, session, visit_id): username = None for pname in ['username', 'user', 'u']: username = (self.get_query(pname) or self.get_query(pname.title()) or self.get_query(pname.upper())) if username: break if not username: return password = None for pname in ['password', 'pass', 'p']: password = (self.get_query(pname) or self.get_query(pname.title()) or self.get_query(pname.upper())) if password: break password = (password or '') cred_count = 0 query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id, username=username, password=password) if query.count() == 0: cred = db_models.Credential(campaign_id=self.campaign_id, message_id=self.message_id, visit_id=visit_id) cred.username = username cred.password = password session.add(cred) campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) cred_count = len(campaign.credentials) if cred_count > 0 and ((cred_count in [1, 5, 10]) or ((cred_count % 25) == 0)): alert_text = "{0} credentials submitted for campaign: {{campaign_name}}".format(cred_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, self.campaign_id))
def rpc_database_delete_rows_by_id(handler, session, table_name, row_ids): """ Delete multiple rows from a table with the specified values in the id column. If a row id specified in *row_ids* does not exist, then it will be skipped and no error will be thrown. :param str table_name: The name of the database table to delete rows from. :param list row_ids: The row ids to delete. :return: The row ids that were deleted. :rtype: list """ table = database_table_objects.get(table_name) if not table: raise errors.KingPhisherAPIError("failed to get table object for: {0}".format(table_name)) deleted_rows = [] for row_id in row_ids: row = db_manager.get_row_by_id(session, table, row_id) if not row: continue if not row.session_has_permissions('d', handler.rpc_session): continue session.delete(row) deleted_rows.append(row_id) session.commit() return deleted_rows
def rpc_database_set_row_value(self, row_id, keys, values): """ Set values for a row in the specified table with an id of *row_id*. :param tuple keys: The column names of *values*. :param tuple values: The values to be updated in the row. """ if not isinstance(keys, (list, tuple)): keys = (keys,) if not isinstance(values, (list, tuple)): values = (values,) assert len(keys) == len(values) table_name = self.path.split('/')[-2] for key, value in zip(keys, values): assert key in DATABASE_TABLES[table_name] table = DATABASE_TABLE_OBJECTS.get(table_name) assert table session = db_manager.Session() row = db_manager.get_row_by_id(session, table, row_id) if not row: session.close() assert row for key, value in zip(keys, values): setattr(row, key, value) session.commit() session.close() return
def rpc_database_delete_rows_by_id(self, row_ids): """ Delete multiple rows from a table with the specified values in the id column. If a row id specified in *row_ids* does not exist, then it will be skipped and no error will be thrown. :param list row_ids: The row ids to delete. :return: The row ids that were deleted. :rtype: list """ table = DATABASE_TABLE_OBJECTS.get(self.path.split('/')[-3]) assert table deleted_rows = [] session = db_manager.Session() try: for row_id in row_ids: row = db_manager.get_row_by_id(session, table, row_id) if not row: continue session.delete(row) deleted_rows.append(row_id) session.commit() finally: session.close() return deleted_rows
def rpc_database_set_row_value(handler, session, table_name, row_id, keys, values): """ Set values for a row in the specified table with an id of *row_id*. :param str table_name: The name of the database table to set the values of the specified row. :param tuple keys: The column names of *values*. :param tuple values: The values to be updated in the row. """ if not isinstance(keys, (list, tuple)): keys = (keys,) if not isinstance(values, (list, tuple)): values = (values,) if len(keys) != len(values): raise errors.KingPhisherAPIError('the number of keys does not match the number of values') table = database_table_objects.get(table_name) if not table: raise errors.KingPhisherAPIError("failed to get table object for: {0}".format(table_name)) for key, value in zip(keys, values): if key not in database_tables[table_name]: raise errors.KingPhisherAPIError("column {0} is invalid for table {1}".format(key, table_name)) row = db_manager.get_row_by_id(session, table, row_id) if not row: raise errors.KingPhisherAPIError("failed to get row id: {0} from table: {1}".format(row_id, table_name)) row.assert_session_has_permissions('u', handler.rpc_session) for key, value in zip(keys, values): setattr(row, key, value) row.assert_session_has_permissions('u', handler.rpc_session) session.commit()
def rpc_login(self, session, username, password, otp=None): logger = logging.getLogger('KingPhisher.Server.Authentication') if not ipaddress.ip_address(self.client_address[0]).is_loopback: logger.warning("failed login request from {0} for user {1}, (invalid source address)".format(self.client_address[0], username)) raise ValueError('invalid source address for login') fail_default = (False, ConnectionErrorReason.ERROR_INVALID_CREDENTIALS, None) fail_otp = (False, ConnectionErrorReason.ERROR_INVALID_OTP, None) if not (username and password): logger.warning("failed login request from {0} for user {1}, (missing username or password)".format(self.client_address[0], username)) return fail_default if not self.server.forked_authenticator.authenticate(username, password): logger.warning("failed login request from {0} for user {1}, (authentication failed)".format(self.client_address[0], username)) return fail_default user = db_manager.get_row_by_id(session, db_models.User, username) if not user: logger.info('creating new user object with id: ' + username) user = db_models.User(id=username) session.add(user) session.commit() elif user.otp_secret: if otp is None: logger.debug("failed login request from {0} for user {1}, (missing otp)".format(self.client_address[0], username)) return fail_otp if not (isinstance(otp, str) and len(otp) == 6 and otp.isdigit()): logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(self.client_address[0], username)) return fail_otp totp = pyotp.TOTP(user.otp_secret) now = datetime.datetime.now() if not otp in (totp.at(now + datetime.timedelta(seconds=offset)) for offset in (0, -30, 30)): logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(self.client_address[0], username)) return fail_otp logger.info("successful login request from {0} for user {1}".format(self.client_address[0], username)) return True, ConnectionErrorReason.SUCCESS, self.server.session_manager.put(username)
def check_campaign(self, session, cid): campaign = db_manager.get_row_by_id(session, db_models.Campaign, cid) if campaign.has_expired: # the campaign can not be expired return False unique_targets = session.query(db_models.Message.target_email) unique_targets = unique_targets.filter_by(campaign_id=cid) unique_targets = float(unique_targets.distinct().count()) if unique_targets < 5: # the campaign needs at least 5 unique targets return False success_percentage = self.config['success_percentage'] success_percentage = min(success_percentage, 100) success_percentage = max(success_percentage, 0) success_percentage = float(success_percentage) / 100 unique_visits = session.query(db_models.Visit.message_id) unique_visits = unique_visits.filter_by(campaign_id=cid) unique_visits = float(unique_visits.distinct().count()) if unique_visits / unique_targets < success_percentage: # the campaign is not yet classified as successful return False if (unique_visits - 1) / unique_targets >= success_percentage: # the campaign has already been classified as successful return False return True
def issue_alert(self, campaign_id, table, count): """ Send a campaign alert for the specified table. :param int campaign_id: The campaign subscribers to send the alert to. :param str table: The type of event to use as the sender when it is forwarded. :param int count: The number associated with the event alert. """ session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) alert_subscriptions = tuple(subscription for subscription in campaign.alert_subscriptions if not subscription.has_expired) if not alert_subscriptions: self.server.logger.debug("no active alert subscriptions are present for campaign id: {0} ({1})".format(campaign.id, campaign.name)) session.close() return if not signals.campaign_alert.receivers: self.server.logger.warning('users are subscribed to campaign alerts, and no signal handlers are connected') session.close() return if not signals.campaign_alert.has_receivers_for(table): self.server.logger.info('users are subscribed to campaign alerts, and no signal handlers are connected for sender: ' + table) session.close() return for subscription in alert_subscriptions: results = signals.send_safe('campaign-alert', self.server.logger, table, alert_subscription=subscription, count=count) if any((result for (_, result) in results)): continue self.server.logger.warning("user {0} is subscribed to campaign alerts, and no signal handlers succeeded to send an alert".format(subscription.user.name)) session.close() return
def _respond_file_check_id(self): if re.match(r'^[._]metadata\.(json|yaml|yml)$', os.path.basename(self.request_path)): self.server.logger.warning('received request for template metadata file') raise errors.KingPhisherAbortRequestError() if re.match(r'^/\.well-known/acme-challenge/[a-zA-Z0-9\-_]{40,50}$', self.request_path): self.server.logger.info('received request for .well-known/acme-challenge') return if not self.config.get('server.require_id'): return if self.message_id == self.config.get('server.secret_id'): self.server.logger.debug('request received with the correct secret id') return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.server.logger.warning('denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() campaign = db_manager.get_row_by_id(self._session, db_models.Campaign, self.campaign_id) query = self._session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: self.server.logger.warning('denying request with not found due to invalid hostname') raise errors.KingPhisherAbortRequestError() if campaign.has_expired: self.server.logger.warning('denying request because the campaign has expired') raise errors.KingPhisherAbortRequestError() if campaign.max_credentials is not None and self.visit_id is None: query = self._session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count() >= campaign.max_credentials: self.server.logger.warning('denying request because the maximum number of credentials have already been harvested') raise errors.KingPhisherAbortRequestError() return
def handle_deaddrop_visit(self, query): self.send_response(200) self.end_headers() data = self.get_query('token') if not data: self.logger.warning('dead drop request received with no \'token\' parameter') return try: data = base64.b64decode('base64') except binascii.Error: self.logger.error('dead drop request received with invalid \'token\' data') return data = xor.xor_decode(data) try: data = json.loads(data) except ValueError: self.logger.error('dead drop request received with invalid \'token\' data') return session = db_manager.Session() deployment = db_manager.get_row_by_id(session, db_models.DeaddropDeployment, data.get('deaddrop_id')) if not deployment: session.close() self.logger.error('dead drop request received for an unknown campaign') return local_username = data.get('local_username') local_hostname = data.get('local_hostname') if local_username == None or local_hostname == None: session.close() self.logger.error('dead drop request received with missing data') return local_ip_addresses = data.get('local_ip_addresses') if isinstance(local_ip_addresses, (list, tuple)): local_ip_addresses = ' '.join(local_ip_addresses) query = session.query(db_models.DeaddropConnection) query = query.filter_by(id=deployment.id, local_username=local_username, local_hostname=local_hostname) connection = query.first() if connection: connection.visit_count += 1 else: connection = db_models.Connection(campaign_id=deployment.campaign_id, deployment_id=deployment.id) connection.visitor_ip = self.client_address connection.local_username = local_username connection.local_hostname = local_hostname connection.local_ip_addresses = local_ip_addresses session.add(connection) session.commit() query = session.query(db_models.DeaddropConnection) query = query.filter_by(campaign_id=deployment.campaign_id) visit_count = query.count() session.close() if visit_count > 0 and ((visit_count in [1, 3, 5]) or ((visit_count % 10) == 0)): alert_text = "{0} deaddrop connections reached for campaign: {{campaign_name}}".format(visit_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, campaign_id)) return
def issue_alert(self, campaign_id, table, count): """ Send a campaign alert for the specified table. :param int campaign_id: The campaign subscribers to send the alert to. :param str table: The type of event to use as the sender when it is forwarded. :param int count: The number associated with the event alert. """ campaign = db_manager.get_row_by_id(self._session, db_models.Campaign, campaign_id) _send_safe_campaign_alerts(campaign, 'campaign-alert', table, count=count) return
def rpc_database_delete_row_by_id(self, session, table_name, row_id): """ Delete a row from a table with the specified value in the id column. :param str table_name: The name of the database table to delete a row from. :param row_id: The id value. """ table = database_table_objects.get(table_name) assert table session.delete(db_manager.get_row_by_id(session, table, row_id)) session.commit()
def on_kp_db_event(self, sender, targets, session): for event in targets: message = db_manager.get_row_by_id(session, db_models.Message, event.message_id) target_email, campaign_name = self.check_mask(message) if sender == 'visits': message = "New visit from {0} for campaign '{1}'".format(target_email, campaign_name) elif sender == 'credentials': message = "New credentials received from {0} for campaign '{1}'".format(target_email, campaign_name) else: return self.send_notification(message)
def rpc_campaign_delete(self, campaign_id): """ Remove a campaign from the database and delete all associated information with it. .. warning:: This action can not be reversed and there is no confirmation before it takes place. """ session = db_manager.Session() session.delete(db_manager.get_row_by_id(session, db_models.Campaign, campaign_id)) session.commit() session.close() return
def rpc_database_delete_row_by_id(self, row_id): """ Delete a row from a table with the specified value in the id column. :param row_id: The id value. """ table = DATABASE_TABLE_OBJECTS.get(self.path.split('/')[-2]) assert table session = db_manager.Session() try: session.delete(db_manager.get_row_by_id(session, table, row_id)) session.commit() finally: session.close() return
def rpc_client_initialize(self, session): """ Initialize any client information necessary. :return: This method always returns True. :rtype: bool """ username = self.basic_auth_user if not username: return True if not db_manager.get_row_by_id(session, db_models.User, username): user = db_models.User(id=username) session.add(user) session.commit() return True
def test_get_row_by_id(self): self._init_db() session = db_manager.Session() user = db_models.User(id='alice') session.add(user) campaign_name = random_string(10) campaign = db_models.Campaign(name=campaign_name, user_id=user.id) session.add(campaign) session.commit() self.assertIsNotNone(campaign.id) campaign_id = campaign.id del campaign row = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) self.assertEqual(row.id, campaign_id) self.assertEqual(row.name, campaign_name)
def _handle_page_visit_creds(self, session, visit_id): username, password = self.get_query_creds() if username is None: return cred_count = 0 query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id, username=username, password=password) if query.count() == 0: cred = db_models.Credential(campaign_id=self.campaign_id, message_id=self.message_id, visit_id=visit_id) cred.username = username cred.password = password session.add(cred) campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) cred_count = len(campaign.credentials) if cred_count > 0 and ((cred_count in [1, 5, 10]) or ((cred_count % 25) == 0)): alert_text = "{0} credentials submitted for campaign: {{campaign_name}}".format(cred_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, self.campaign_id))
def issue_alert(self, campaign_id, table, count): """ Send a campaign alert for the specified table. :param int campaign_id: The campaign subscribers to send the alert to. :param str table: The type of event to use as the sender when it is forwarded. :param int count: The number associated with the event alert. """ session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) alert_subscriptions = tuple( subscription for subscription in campaign.alert_subscriptions if not subscription.has_expired) if not alert_subscriptions: self.server.logger.debug( "no active alert subscriptions are present for campaign id: {0} ({1})" .format(campaign.id, campaign.name)) session.close() return if not signals.campaign_alert.receivers: self.server.logger.warning( 'users are subscribed to campaign alerts, and no signal handlers are connected' ) session.close() return if not signals.campaign_alert.has_receivers_for(table): self.server.logger.info( 'users are subscribed to campaign alerts, and no signal handlers are connected for sender: ' + table) session.close() return for subscription in alert_subscriptions: results = signals.send_safe('campaign-alert', self.server.logger, table, alert_subscription=subscription, count=count) if any((result for (_, result) in results)): continue self.server.logger.warning( "user {0} is subscribed to campaign alerts, and no signal handlers succeeded to send an alert" .format(subscription.user.name)) session.close() return
def rpc_client_initialize(self): """ Initialize any client information necessary. :return: This method always returns True. :rtype: bool """ username = self.basic_auth_user if not username: return True session = db_manager.Session() if not db_manager.get_row_by_id(session, db_models.User, username): user = db_models.User(id=username) session.add(user) session.commit() session.close() return True
def visit_id(self): """ The visit id that is associated with the current request's visitor. This is retrieved by looking for the King Phisher cookie. If no cookie is set, this value is None. """ if hasattr(self, '_visit_id'): return self._visit_id self._visit_id = None kp_cookie_name = self.config.get('server.cookie_name') if kp_cookie_name in self.cookies: value = self.cookies[kp_cookie_name].value session = db_manager.Session() if db_manager.get_row_by_id(session, db_models.Visit, value): self._visit_id = value session.close() return self._visit_id
def test_get_row_by_id(self): self._init_db() session = db_manager.Session() user = db_models.User(name='alice') session.add(user) campaign_name = random_string(10) campaign = db_models.Campaign(name=campaign_name, user=user) session.add(campaign) session.commit() self.assertIsNotNone(campaign.id) campaign_id = campaign.id del campaign row = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) self.assertEqual(row.id, campaign_id) self.assertEqual(row.name, campaign_name)
def rpc_database_get_row_by_id(self, session, table_name, row_id): """ Retrieve a row from a given table with the specified value in the id column. :param str table_name: The name of the database table to retrieve a row from. :param row_id: The id value. :return: The specified row data. :rtype: dict """ table = database_table_objects.get(table_name) assert table columns = database_tables[table_name] row = db_manager.get_row_by_id(session, table, row_id) if row: row = dict(zip(columns, (getattr(row, c) for c in columns))) return row
def campaign_id(self): """ The campaign id that is associated with the current request's visitor. This is retrieved by looking up the :py:attr:`~.KingPhisherRequestHandler.message_id` value in the database. If no campaign is associated, this value is None. """ if hasattr(self, '_campaign_id'): return self._campaign_id self._campaign_id = None if self.message_id and self.message_id != self.config.get('server.secret_id'): session = db_manager.Session() message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message: self._campaign_id = message.campaign_id session.close() return self._campaign_id
def issue_alert(self, campaign_id, table, count): """ Send a campaign alert for the specified table. :param int campaign_id: The campaign subscribers to send the alert to. :param str table: The type of event to use as the sender when it is forwarded. :param int count: The number associated with the event alert. """ session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) _send_safe_campaign_alerts(campaign, 'campaign-alert', table, count=count) session.close() return
def rpc_database_get_row_by_id(self, session, table_name, row_id): """ Retrieve a row from a given table with the specified value in the id column. :param str table_name: The name of the database table to retrieve a row from. :param row_id: The id value. :return: The specified row data. :rtype: dict """ table = database_table_objects.get(table_name) assert table columns = database_tables[table_name] row = db_manager.get_row_by_id(session, table, row_id) if row: row.assert_session_has_permissions('r', self.rpc_session) row = dict(zip(columns, (getattr(row, c) for c in columns))) return row
def rpc_database_delete_row_by_id(self, session, table_name, row_id): """ Delete the row from the table with the specified value in the id column. If the row does not exist, no error is raised. :param str table_name: The name of the database table to delete a row from. :param row_id: The id value. """ table = database_table_objects.get(table_name) assert table row = db_manager.get_row_by_id(session, table, row_id) if row is None: logger = logging.getLogger('KingPhisher.Server.API.RPC') logger.debug("received delete request for non existing row with id {0} from table {1}".format(row_id, table_name)) return row.assert_session_has_permissions('d', self.rpc_session) session.delete(row) session.commit()
def get_template_vars_client(self): """ Build a dictionary of variables for a client with an associated campaign. :return: The client specific template variables. :rtype: dict """ if not self.message_id: return visit_count = 0 result = None if self.message_id == self.config.get('server.secret_id'): result = [ '*****@*****.**', 'Wonderland Inc.', 'Alice', 'Liddle', 0 ] elif self.message_id: session = db_manager.Session() message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message: visit_count = len(message.visits) result = [ message.target_email, message.company_name, message.first_name, message.last_name, message.trained ] session.close() if not result: return client_vars = {} client_vars['email_address'] = result[0] client_vars['company_name'] = result[1] client_vars['first_name'] = result[2] client_vars['last_name'] = result[3] client_vars['is_trained'] = result[4] client_vars['message_id'] = self.message_id client_vars['visit_count'] = visit_count if self.visit_id: client_vars['visit_id'] = self.visit_id else: # if the visit_id is not set then this is a new visit so increment the count preemptively client_vars['visit_count'] += 1 return client_vars
def rpc_database_insert_row_multi(handler, session, table_name, keys, rows, deconflict_ids=False): """ Insert multiple new rows into the specified table. If *deconflict_ids* is true, new id values will be assigned as necessary to merge the data into the database. This function will fail if constraints for the table are not met. :param str table_name: The name of the database table to insert data into. :param list keys: The column names of the values in *rows*. :param list rows: A list of rows, each row is a list of values ordered and identified by *keys* to be inserted. :return: List of ids of the newly inserted rows. :rtype: list """ _log_rpc_call(handler, 'rpc_database_insert_row_multi', table_name, keys, _REDACTED, deconflict_ids=deconflict_ids) inserted_rows = collections.deque() if not isinstance(keys, list): keys = list(keys) if not isinstance(rows, list): rows = list(rows) metatable = database_tables.get(table_name) if not metatable: raise errors.KingPhisherAPIError('failed to get table object for: {0}'.format(table_name)) for key in keys: if key not in metatable.column_names: raise errors.KingPhisherAPIError('column {0} is invalid for table {1}'.format(keys, table_name)) for row in rows: if len(row) != len(keys): raise errors.KingPhisherAPIError('row is not the same length as the number of values defined') row = dict(zip(keys, row)) if 'id' in row and db_manager.get_row_by_id(session, metatable.model, row['id']) is not None: if deconflict_ids: row['id'] = None else: raise errors.KingPhisherAPIError('row id conflicts with an existing value') table_row = metatable.model(**row) table_row.assert_session_has_permissions('c', handler.rpc_session) session.add(table_row) inserted_rows.append(table_row) session.commit() return [row.id for row in inserted_rows]
def _respond_file_check_id(self): if re.match(r'^/\.well-known/acme-challenge/[a-zA-Z0-9\-_]{40,50}$', self.request_path): self.server.logger.info( 'received request for .well-known/acme-challenge') return if not self.config.get('server.require_id'): return if self.message_id == self.config.get('server.secret_id'): return # a valid campaign_id requires a valid message_id if not self.campaign_id: self.server.logger.warning( 'denying request due to lack of a valid id') raise errors.KingPhisherAbortRequestError() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost) if query.count() == 0: self.server.logger.warning( 'denying request with not found due to invalid hostname') session.close() raise errors.KingPhisherAbortRequestError() if campaign.has_expired: self.server.logger.warning( 'denying request because the campaign has expired') session.close() raise errors.KingPhisherAbortRequestError() if campaign.reject_after_credentials and self.visit_id is None: query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) if query.count(): self.server.logger.warning( 'denying request because credentials were already harvested' ) session.close() raise errors.KingPhisherAbortRequestError() session.close() return
def rpc_database_get_row_by_id(self, row_id): """ Retrieve a row from a given table with the specified value in the id column. :param row_id: The id value. :return: The specified row data. :rtype: dict """ table_name = self.path.split('/')[-2] table = DATABASE_TABLE_OBJECTS.get(table_name) assert table columns = DATABASE_TABLES[table_name] session = db_manager.Session() row = db_manager.get_row_by_id(session, table, row_id) if row: row = dict(zip(columns, (getattr(row, c) for c in columns))) session.close() return row
def rpc_database_get_row_by_id(handler, session, table_name, row_id): """ Retrieve a row from a given table with the specified value in the id column. :param str table_name: The name of the database table to retrieve a row from. :param row_id: The id value. :return: The specified row data. :rtype: dict """ table = database_table_objects.get(table_name) if not table: raise errors.KingPhisherAPIError("failed to get table object for: {0}".format(table_name)) columns = database_tables[table_name] row = db_manager.get_row_by_id(session, table, row_id) if row: row.assert_session_has_permissions('r', handler.rpc_session) row = dict(zip(columns, (getattr(row, c) for c in columns))) return row
def rpc_database_get_row_by_id(handler, session, table_name, row_id): """ Retrieve a row from a given table with the specified value in the id column. :param str table_name: The name of the database table to retrieve a row from. :param row_id: The id value. :return: The specified row data. :rtype: dict """ metatable = handler.server.tables_api.get(table_name) if not metatable: raise errors.KingPhisherAPIError("failed to get table object for: {0}".format(table_name)) row = db_manager.get_row_by_id(session, metatable.model, row_id) if row: row.assert_session_has_permissions('r', handler.rpc_session) row = dict(zip(metatable.column_names, (getattr(row, c) for c in metatable.column_names))) elif metatable.model.is_private: raise errors.KingPhisherPermissionError() return row
def _handle_page_visit_creds(self, campaign, visit_id): query_creds = self.get_query_creds() if query_creds.username is None: return cred_count = 0 cred = self._get_db_creds(query_creds) if cred is None: cred = db_models.Credential( campaign_id=campaign.id, message_id=self.message_id, visit_id=visit_id, **query_creds._asdict() ) cred.regex_validated = db_validation.validate_credential(cred, campaign) self._session.add(cred) self._session.commit() self.logger.debug("credential id: {0} created for message id: {1}".format(cred.id, cred.message_id)) campaign = db_manager.get_row_by_id(self._session, db_models.Campaign, self.campaign_id) cred_count = len(campaign.credentials) if cred_count > 0 and ((cred_count in [1, 5, 10]) or ((cred_count % 25) == 0)): self.server.job_manager.job_run(self.issue_alert, (self.campaign_id, 'credentials', cred_count)) signals.send_safe('credentials-received', self.logger, self, username=query_creds.username, password=query_creds.password)
def rpc_database_set_row_value(self, session, table_name, row_id, keys, values): """ Set values for a row in the specified table with an id of *row_id*. :param str table_name: The name of the database table to set the values of the specified row. :param tuple keys: The column names of *values*. :param tuple values: The values to be updated in the row. """ if not isinstance(keys, (list, tuple)): keys = (keys,) if not isinstance(values, (list, tuple)): values = (values,) assert len(keys) == len(values) for key, value in zip(keys, values): assert key in database_tables[table_name] table = database_table_objects.get(table_name) assert table row = db_manager.get_row_by_id(session, table, row_id) assert row for key, value in zip(keys, values): setattr(row, key, value) row.assert_session_has_permissions('u', self.rpc_session) session.commit()
def _handle_page_visit_creds(self, session, visit_id): username = None for pname in ['username', 'user', 'u']: username = (self.get_query(pname) or self.get_query(pname.title()) or self.get_query(pname.upper())) if username: break if not username: return password = None for pname in ['password', 'pass', 'p']: password = (self.get_query(pname) or self.get_query(pname.title()) or self.get_query(pname.upper())) if password: break password = (password or '') cred_count = 0 query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id, username=username, password=password) if query.count() == 0: cred = db_models.Credential(campaign_id=self.campaign_id, message_id=self.message_id, visit_id=visit_id) cred.username = username cred.password = password session.add(cred) campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) cred_count = len(campaign.credentials) if cred_count > 0 and ((cred_count in [1, 5, 10]) or ((cred_count % 25) == 0)): alert_text = "{0} credentials submitted for campaign: {{campaign_name}}".format( cred_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, self.campaign_id))
def rpc_login(handler, session, username, password, otp=None): logger = logging.getLogger('KingPhisher.Server.Authentication') if not ipaddress.ip_address(handler.client_address[0]).is_loopback: logger.warning("failed login request from {0} for user {1}, (invalid source address)".format(handler.client_address[0], username)) raise ValueError('invalid source address for login') fail_default = (False, ConnectionErrorReason.ERROR_INVALID_CREDENTIALS, None) fail_otp = (False, ConnectionErrorReason.ERROR_INVALID_OTP, None) if not (username and password): logger.warning("failed login request from {0} for user {1}, (missing username or password)".format(handler.client_address[0], username)) return fail_default if not handler.server.forked_authenticator.authenticate(username, password): logger.warning("failed login request from {0} for user {1}, (authentication failed)".format(handler.client_address[0], username)) return fail_default user = db_manager.get_row_by_id(session, db_models.User, username) if not user: logger.info('creating new user object with id: ' + username) user = db_models.User(id=username) session.add(user) session.commit() elif user.otp_secret: if otp is None: logger.debug("failed login request from {0} for user {1}, (missing otp)".format(handler.client_address[0], username)) return fail_otp if not (isinstance(otp, str) and len(otp) == 6 and otp.isdigit()): logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(handler.client_address[0], username)) return fail_otp totp = pyotp.TOTP(user.otp_secret) now = datetime.datetime.now() if not otp in (totp.at(now + datetime.timedelta(seconds=offset)) for offset in (0, -30, 30)): logger.warning("failed login request from {0} for user {1}, (invalid otp)".format(handler.client_address[0], username)) return fail_otp session_id = handler.server.session_manager.put(username) logger.info("successful login request from {0} for user {1}".format(handler.client_address[0], username)) signals.rpc_user_logged_in.send(handler, session=session_id, name=username) return True, ConnectionErrorReason.SUCCESS, session_id
def issue_alert(self, alert_text, campaign_id): """ Send an SMS alert. If no *campaign_id* is specified all users with registered SMS information will receive the alert otherwise only users subscribed to the campaign specified. :param str alert_text: The message to send to subscribers. :param int campaign_id: The campaign subscribers to send the alert to. """ session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) if '{campaign_name}' in alert_text: alert_text = alert_text.format(campaign_name=campaign.name) for subscription in campaign.alert_subscriptions: user = subscription.user carrier = user.phone_carrier number = user.phone_number if carrier is None or number is None: self.server.logger.warning("skipping alert because user {0} has missing information".format(user.id)) continue self.server.logger.debug("sending alert SMS message to {0} ({1})".format(number, carrier)) sms.send_sms(alert_text, number, carrier, '*****@*****.**') session.close()
def rpc_database_delete_rows_by_id(self, session, table_name, row_ids): """ Delete multiple rows from a table with the specified values in the id column. If a row id specified in *row_ids* does not exist, then it will be skipped and no error will be thrown. :param str table_name: The name of the database table to delete rows from. :param list row_ids: The row ids to delete. :return: The row ids that were deleted. :rtype: list """ table = database_table_objects.get(table_name) assert table deleted_rows = [] for row_id in row_ids: row = db_manager.get_row_by_id(session, table, row_id) if not row: continue if not row.session_has_permissions('d', self.rpc_session): continue session.delete(row) deleted_rows.append(row_id) session.commit() return deleted_rows
def handle_page_visit(self): if not self.message_id: return if self.message_id == self.config.get('server.secret_id'): return if not self.campaign_id: return self.logger.info( "handling a page visit for campaign id: {0} from IP address: {1}". format(self.campaign_id, self.client_address[0])) message_id = self.message_id campaign_id = self.campaign_id session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message.opened == None and self.config.get_if_exists( 'server.set_message_opened_on_visit', True): message.opened = db_models.current_timestamp() set_new_visit = True if self.visit_id: set_new_visit = False visit_id = self.visit_id query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost, page=self.request_path[1:]) if query.count(): visit = db_manager.get_row_by_id(session, db_models.Visit, visit_id) if visit.message_id == message_id: visit.visit_count += 1 else: set_new_visit = True if set_new_visit: visit_id = make_uid() kp_cookie_name = self.config.get('server.cookie_name') cookie = "{0}={1}; Path=/; HttpOnly".format( kp_cookie_name, visit_id) self.send_header('Set-Cookie', cookie) visit = db_models.Visit(id=visit_id, campaign_id=campaign_id, message_id=message_id) visit.visitor_ip = self.client_address[0] visit.visitor_details = self.headers.get('user-agent', '') session.add(visit) visit_count = len(campaign.visits) if visit_count > 0 and ((visit_count in [1, 10, 25]) or ((visit_count % 50) == 0)): alert_text = "{0} vists reached for campaign: {{campaign_name}}".format( visit_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, campaign_id)) username = None for pname in ['username', 'user', 'u']: username = (self.get_query_parameter(pname) or self.get_query_parameter(pname.title()) or self.get_query_parameter(pname.upper())) if username: break if username: password = None for pname in ['password', 'pass', 'p']: password = (self.get_query_parameter(pname) or self.get_query_parameter(pname.title()) or self.get_query_parameter(pname.upper())) if password: break password = (password or '') cred_count = 0 query = session.query(db_models.Credential) query = query.filter_by(message_id=message_id, username=username, password=password) if query.count() == 0: cred = db_models.Credential(campaign_id=campaign_id, message_id=message_id, visit_id=visit_id) cred.username = username cred.password = password session.add(cred) cred_count = len(campaign.credentials) if cred_count > 0 and ((cred_count in [1, 5, 10]) or ((cred_count % 25) == 0)): alert_text = "{0} credentials submitted for campaign: {{campaign_name}}".format( cred_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, campaign_id)) trained = self.get_query_parameter('trained') if isinstance(trained, str) and trained.lower() in ['1', 'true', 'yes']: message.trained = True session.commit() session.close()
def on_kp_db_new_visit(self, sender, targets, session): for visit in targets: message = db_manager.get_row_by_id(session, db_models.Message, visit.message_id) self.send_notification("new visit received from {0} for campaign '{1}'".format(message.target_email, message.campaign.name))
def handle_deaddrop_visit(self, query): self.send_response(200) self.end_headers() data = self.get_query('token') if not data: self.logger.warning('dead drop request received with no \'token\' parameter') return try: data = base64.b64decode(data) except (binascii.Error, TypeError): self.logger.error('dead drop request received with invalid \'token\' data') return data = xor.xor_decode(data) try: data = json.loads(data) except ValueError: self.logger.error('dead drop request received with invalid \'token\' data') return deaddrop_id = data.get('deaddrop_id') if deaddrop_id is None: self.logger.error('dead drop request received with no \'deaddrop_id\' key') return elif deaddrop_id == self.config.get('server.secret_id'): # this allows us to test the logic to this point at least self.logger.debug('dead drop request received with the test id') return self.semaphore_acquire() deployment = db_manager.get_row_by_id(self._session, db_models.DeaddropDeployment, deaddrop_id) if not deployment: self.semaphore_release() self.logger.error('dead drop request received for an unknown campaign') return if deployment.campaign.has_expired: self.semaphore_release() self.logger.info('dead drop request received for an expired campaign') return local_username = data.get('local_username') local_hostname = data.get('local_hostname') if local_username is None or local_hostname is None: self.semaphore_release() self.logger.error('dead drop request received with missing data') return local_ip_addresses = data.get('local_ip_addresses') if isinstance(local_ip_addresses, (list, tuple)): local_ip_addresses = ' '.join(local_ip_addresses) query = self._session.query(db_models.DeaddropConnection) query = query.filter_by(deployment_id=deployment.id, local_username=local_username, local_hostname=local_hostname) connection = query.first() if connection: connection.count += 1 connection.last_seen = db_models.current_timestamp() new_connection = False else: connection = db_models.DeaddropConnection(campaign_id=deployment.campaign_id, deployment_id=deployment.id) connection.ip = self.get_client_ip() connection.local_username = local_username connection.local_hostname = local_hostname connection.local_ip_addresses = local_ip_addresses self._session.add(connection) new_connection = True self._session.commit() query = self._session.query(db_models.DeaddropConnection) query = query.filter_by(campaign_id=deployment.campaign_id) visit_count = query.count() self.semaphore_release() if new_connection and visit_count > 0 and ((visit_count in [1, 3, 5]) or ((visit_count % 10) == 0)): self.server.job_manager.job_run(self.issue_alert, (deployment.campaign_id, 'deaddrop_connections', visit_count)) return
def handle_page_visit(self): if not self.message_id: return if self.message_id == self.config.get('server.secret_id'): return if not self.campaign_id: return client_ip = self.get_client_ip() headers = [] campaign = db_manager.get_row_by_id(self._session, db_models.Campaign, self.campaign_id) if campaign.has_expired: self.logger.info("ignoring page visit for expired campaign id: {0} from IP address: {1}".format(self.campaign_id, client_ip)) return self.logger.info("handling a page visit for campaign id: {0} from IP address: {1}".format(self.campaign_id, client_ip)) message = db_manager.get_row_by_id(self._session, db_models.Message, self.message_id) if message.opened is None and self.config.get('server.set_message_opened_on_visit'): message.opened = db_models.current_timestamp() message.opener_ip = self.get_client_ip() message.opener_user_agent = self.headers.get('user-agent', None) query = self._session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost, page=self.request_path[1:]) landing_page = query.first() set_new_visit = True visit_id = None if self.visit_id: visit_id = self.visit_id set_new_visit = False if landing_page: visit = db_manager.get_row_by_id(self._session, db_models.Visit, self.visit_id) if visit.message_id == self.message_id: visit.count += 1 visit.last_seen = db_models.current_timestamp() self._session.commit() else: set_new_visit = True visit_id = None if visit_id is None: visit_id = utilities.make_visit_uid() if landing_page and set_new_visit: kp_cookie_name = self.config.get('server.cookie_name') cookie = "{0}={1}; Path=/; HttpOnly".format(kp_cookie_name, visit_id) headers.append(('Set-Cookie', cookie)) visit = db_models.Visit(id=visit_id, campaign_id=self.campaign_id, message_id=self.message_id) visit.ip = client_ip visit.first_landing_page_id = landing_page.id visit.user_agent = self.headers.get('user-agent', '') self._session.add(visit) self._session.commit() self.logger.debug("visit id: {0} created for message id: {1}".format(visit_id, self.message_id)) visit_count = len(campaign.visits) if visit_count > 0 and ((visit_count in (1, 10, 25)) or ((visit_count % 50) == 0)): self.server.job_manager.job_run(self.issue_alert, (self.campaign_id, 'visits', visit_count)) signals.send_safe('visit-received', self.logger, self) self._handle_page_visit_creds(campaign, visit_id) trained = self.get_query('trained') if isinstance(trained, str) and trained.lower() in ['1', 'true', 'yes']: message.trained = True self._session.commit() return headers
def get_template_vars_client(self): """ Build a dictionary of variables for a client with an associated campaign. :return: The client specific template variables. :rtype: dict """ client_vars = {'address': self.get_client_ip()} if not self.message_id: return client_vars credential_count = 0 expired_campaign = True visit_count = 0 result = None session = db_manager.Session() if self.message_id == self.config.get('server.secret_id'): client_vars['company_name'] = 'Wonderland Inc.' client_vars['company'] = {'name': 'Wonderland Inc.'} result = ('*****@*****.**', 'Alice', 'Liddle', 0) elif self.message_id: message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message: campaign = message.campaign client_vars['campaign'] = { 'name': campaign.name, 'created': campaign.created, 'expiration': campaign.expiration, 'has_expired': campaign.has_expired, 'message_count': session.query(db_models.Message).filter_by( campaign_id=campaign.id).count(), 'visit_count': session.query(db_models.Visit).filter_by( campaign_id=campaign.id).count(), 'credential_count': session.query(db_models.Credential).filter_by( campaign_id=campaign.id).count(), } if message.campaign.company: client_vars['company_name'] = message.campaign.company.name client_vars['company'] = { 'name': campaign.company.name, 'url_email': campaign.company.url_email, 'url_main': campaign.company.url_main, 'url_remote_access': campaign.company.url_remote_access } result = (message.target_email, message.first_name, message.last_name, message.trained) query = session.query(db_models.Credential) query = query.filter_by(message_id=self.message_id) credential_count = query.count() expired_campaign = message.campaign.has_expired if not result: session.close() return client_vars client_vars['email_address'] = result[0] client_vars['first_name'] = result[1] client_vars['last_name'] = result[2] client_vars['is_trained'] = result[3] client_vars['message_id'] = self.message_id if self.visit_id: visit = db_manager.get_row_by_id(session, db_models.Visit, self.visit_id) client_vars['visit_id'] = visit.id visit_count = visit.visit_count # increment some counters preemptively if not expired_campaign and self.get_query_creds()[0] is not None: credential_count += 1 client_vars['credential_count'] = credential_count client_vars['visit_count'] = visit_count + (0 if expired_campaign else 1) session.close() return client_vars
def handle_deaddrop_visit(self, query): self.send_response(200) self.end_headers() data = self.get_query('token') if not data: self.logger.warning( 'dead drop request received with no \'token\' parameter') return try: data = base64.b64decode(data) except (binascii.Error, TypeError): self.logger.error( 'dead drop request received with invalid \'token\' data') return data = xor.xor_decode(data) try: data = json.loads(data) except ValueError: self.logger.error( 'dead drop request received with invalid \'token\' data') return session = db_manager.Session() deployment = db_manager.get_row_by_id(session, db_models.DeaddropDeployment, data.get('deaddrop_id')) if not deployment: session.close() self.logger.error( 'dead drop request received for an unknown campaign') return if deployment.campaign.has_expired: session.close() self.logger.info( 'dead drop request received for an expired campaign') return local_username = data.get('local_username') local_hostname = data.get('local_hostname') if local_username is None or local_hostname is None: session.close() self.logger.error('dead drop request received with missing data') return local_ip_addresses = data.get('local_ip_addresses') if isinstance(local_ip_addresses, (list, tuple)): local_ip_addresses = ' '.join(local_ip_addresses) query = session.query(db_models.DeaddropConnection) query = query.filter_by(deployment_id=deployment.id, local_username=local_username, local_hostname=local_hostname) connection = query.first() if connection: connection.visit_count += 1 new_connection = False else: connection = db_models.DeaddropConnection( campaign_id=deployment.campaign_id, deployment_id=deployment.id) connection.visitor_ip = self.get_client_ip() connection.local_username = local_username connection.local_hostname = local_hostname connection.local_ip_addresses = local_ip_addresses session.add(connection) new_connection = True session.commit() query = session.query(db_models.DeaddropConnection) query = query.filter_by(campaign_id=deployment.campaign_id) visit_count = query.count() session.close() if new_connection and visit_count > 0 and ( (visit_count in [1, 3, 5]) or ((visit_count % 10) == 0)): alert_text = "{0} deaddrop connections reached for campaign: {{campaign_name}}".format( visit_count) self.server.job_manager.job_run( self.issue_alert, (alert_text, deployment.campaign_id)) return
def handle_page_visit(self): if not self.message_id: return if self.message_id == self.config.get('server.secret_id'): return if not self.campaign_id: return client_ip = self.get_client_ip() session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, self.campaign_id) if campaign.has_expired: self.logger.info( "ignoring page visit for expired campaign id: {0} from IP address: {1}" .format(self.campaign_id, client_ip)) session.close() return self.logger.info( "handling a page visit for campaign id: {0} from IP address: {1}". format(self.campaign_id, client_ip)) message = db_manager.get_row_by_id(session, db_models.Message, self.message_id) if message.opened is None and self.config.get_if_exists( 'server.set_message_opened_on_visit', True): message.opened = db_models.current_timestamp() message.opener_ip = self.get_client_ip() message.opener_user_agent = self.headers.get('user-agent', None) set_new_visit = True visit_id = None if self.visit_id: visit_id = self.visit_id set_new_visit = False query = session.query(db_models.LandingPage) query = query.filter_by(campaign_id=self.campaign_id, hostname=self.vhost, page=self.request_path[1:]) if query.count(): visit = db_manager.get_row_by_id(session, db_models.Visit, self.visit_id) if visit.message_id == self.message_id: visit.visit_count += 1 visit.last_visit = db_models.current_timestamp() else: set_new_visit = True visit_id = None if visit_id is None: visit_id = make_uid() if set_new_visit: kp_cookie_name = self.config.get('server.cookie_name') cookie = "{0}={1}; Path=/; HttpOnly".format( kp_cookie_name, visit_id) self.send_header('Set-Cookie', cookie) visit = db_models.Visit(id=visit_id, campaign_id=self.campaign_id, message_id=self.message_id) visit.visitor_ip = client_ip visit.visitor_details = self.headers.get('user-agent', '') session.add(visit) visit_count = len(campaign.visits) if visit_count > 0 and ((visit_count in (1, 10, 25)) or ((visit_count % 50) == 0)): alert_text = "{0} visits reached for campaign: {{campaign_name}}".format( visit_count) self.server.job_manager.job_run(self.issue_alert, (alert_text, self.campaign_id)) signals.safe_send('visit-received', self.logger, self) if visit_id is None: self.logger.error('the visit id has not been set') raise RuntimeError('the visit id has not been set') self._handle_page_visit_creds(session, visit_id) trained = self.get_query('trained') if isinstance(trained, str) and trained.lower() in ['1', 'true', 'yes']: message.trained = True session.commit() session.close()