Beispiel #1
0
	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
Beispiel #2
0
	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
Beispiel #3
0
	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()
Beispiel #4
0
	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
Beispiel #5
0
	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
Beispiel #6
0
    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
Beispiel #7
0
	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
Beispiel #8
0
	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
Beispiel #9
0
	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))
Beispiel #10
0
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
Beispiel #13
0
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
Beispiel #16
0
	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
Beispiel #17
0
	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
Beispiel #18
0
	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
Beispiel #19
0
	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
Beispiel #20
0
	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
Beispiel #24
0
	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)
Beispiel #26
0
	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))
Beispiel #27
0
    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
Beispiel #28
0
    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
Beispiel #29
0
    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
Beispiel #30
0
    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)
Beispiel #31
0
	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))
Beispiel #32
0
	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
Beispiel #33
0
	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
Beispiel #34
0
    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
Beispiel #35
0
	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
Beispiel #36
0
	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
Beispiel #37
0
    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
Beispiel #38
0
	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 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()
Beispiel #40
0
    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
Beispiel #41
0
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]
Beispiel #42
0
    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
Beispiel #43
0
    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
Beispiel #44
0
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
Beispiel #45
0
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
Beispiel #46
0
	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)
Beispiel #47
0
	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()
Beispiel #48
0
 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))
Beispiel #49
0
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
Beispiel #50
0
	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()
Beispiel #51
0
    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
Beispiel #52
0
    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()
Beispiel #53
0
	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))
Beispiel #54
0
	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
Beispiel #55
0
	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
Beispiel #56
0
    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
Beispiel #57
0
    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
Beispiel #58
0
    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()