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 upgrade(): op.add_column( 'campaigns', sqlalchemy.Column('credential_regex_username', sqlalchemy.String)) op.add_column( 'campaigns', sqlalchemy.Column('credential_regex_password', sqlalchemy.String)) op.add_column( 'campaigns', sqlalchemy.Column('credential_regex_mfa_token', sqlalchemy.String)) op.add_column('credentials', sqlalchemy.Column('mfa_token', sqlalchemy.String)) op.add_column('credentials', sqlalchemy.Column('regex_validated', sqlalchemy.Boolean)) op.add_column('users', sqlalchemy.Column('access_level', sqlalchemy.Integer)) op.execute('UPDATE users SET access_level = 1000') op.alter_column('users', 'access_level', nullable=False) # adjust the schema version metadata db_manager.Session.remove() db_manager.Session.configure(bind=op.get_bind()) session = db_manager.Session() db_manager.set_metadata('schema_version', 9, session=session) session.commit()
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 test_query_get_total(self): session = db_manager.Session() result = graphql_schema.execute( "{ db { users { total } } }", context_value={'session': session} ) self.assertEquals(result.data['db']['users']['total'], 2)
def rpc_database_get_rows(self, *args): """ Retrieve the rows from the specified table where the search criteria matches. :return: A dictionary with columns and rows keys. :rtype: dict """ args = list(args) offset = 0 fields = self.path.split('/')[1:-2] if len(args) == (len(fields) + 1): offset = (args.pop() * VIEW_ROW_COUNT) assert len(fields) == len(args) table_name = self.path.split('/')[-2] table = DATABASE_TABLE_OBJECTS.get(table_name) assert table # it's critical that the columns are in the order that the client is expecting columns = DATABASE_TABLES[table_name] rows = [] session = db_manager.Session() query = session.query(table) query = query.filter_by(**dict(zip((f + '_id' for f in fields), args))) for row in query[offset:offset + VIEW_ROW_COUNT]: rows.append([getattr(row, c) for c in columns]) session.close() if not len(rows): return None return {'columns': columns, 'rows': rows}
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 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 handle_email_opened(self, query): # image size: 43 Bytes img_data = '47494638396101000100800100000000ffffff21f90401000001002c00000000' img_data += '010001000002024c01003b' img_data = binascii.a2b_hex(img_data) self.send_response(200) self.send_header('Content-Type', 'image/gif') self.send_header('Content-Length', str(len(img_data))) self.end_headers() self.wfile.write(img_data) msg_id = self.get_query('id') if not msg_id: return session = db_manager.Session() query = session.query(db_models.Message) query = query.filter_by(id=msg_id, opened=None) message = query.first() if message and not message.campaign.has_expired: message.opened = db_models.current_timestamp() message.opener_ip = self.get_client_ip() message.opener_user_agent = self.headers.get('user-agent', None) session.commit() session.close() signals.safe_send('email-opened', self.logger, self)
def __init__(self, timeout='30m'): """ :param timeout: The length of time in seconds for which sessions are valid. :type timeout: int, str """ self.logger = logging.getLogger('KingPhisher.Server.SessionManager') timeout = smoke_zephyr.utilities.parse_timespan(timeout) self.session_timeout = timeout self._sessions = {} self._lock = threading.Lock() # get valid sessions from the database expired = 0 session = db_manager.Session() for stored_session in session.query(db_models.AuthenticatedSession): if stored_session.last_seen < time.time() - self.session_timeout: expired += 1 continue auth_session = AuthenticatedSession.from_db_authenticated_session( stored_session) self._sessions[stored_session.id] = auth_session session.query(db_models.AuthenticatedSession).delete() session.commit() self.logger.info( "restored {0:,} valid sessions and skipped {1:,} expired sessions from the database" .format(len(self._sessions), expired))
def main(): parser = argparse.ArgumentParser(description='King Phisher Interactive Database Console', conflict_handler='resolve') utilities.argp_add_args(parser) config_group = parser.add_mutually_exclusive_group(required=True) config_group.add_argument('-c', '--config', dest='server_config', type=argparse.FileType('r'), help='the server configuration file') config_group.add_argument('-u', '--url', dest='database_url', help='the database connection url') arguments = parser.parse_args() if arguments.database_url: database_connection_url = arguments.database_url elif arguments.server_config: server_config = yaml.load(arguments.server_config) database_connection_url = server_config['server']['database'] else: raise RuntimeError('no database connection was specified') engine = manager.init_database(database_connection_url) session = manager.Session() rpc_session = aaa.AuthenticatedSession(user=getpass.getuser()) console = code.InteractiveConsole(dict( engine=engine, graphql=graphql, graphql_query=graphql_query, manager=manager, models=models, pprint=pprint.pprint, rpc_session=rpc_session, session=session )) console.interact('starting interactive database console') if os.path.isdir(os.path.dirname(history_file)): readline.write_history_file(history_file)
def wrapper(handler_instance, *args, **kwargs): if log_call and rpc_logger.isEnabledFor(logging.DEBUG): args_repr = ', '.join(map(repr, args)) if kwargs: for key, value in sorted(kwargs.items()): args_repr += ", {0}={1!r}".format(key, value) msg = "calling RPC method {0}({1})".format( function.__name__, args_repr) if getattr(handler_instance, 'rpc_session', False): msg = handler_instance.rpc_session.user + ' is ' + msg rpc_logger.debug(msg) signals.rpc_method_call.send(path[1:-1], request_handler=handler_instance, args=args, kwargs=kwargs) if database_access: session = db_manager.Session() try: result = function(handler_instance, session, *args, **kwargs) finally: session.close() else: result = function(handler_instance, *args, **kwargs) signals.rpc_method_called.send(path[1:-1], request_handler=handler_instance, args=args, kwargs=kwargs, retval=result) return result
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 wrapper(handler_instance, *args, **kwargs): session = db_manager.Session() try: result = function(handler_instance, session, *args, **kwargs) finally: session.close() return result
def _do_http_method(self, *args, **kwargs): # This method wraps all of the default do_* HTTP verb handlers to # provide error handling and (for non-RPC requests) path adjustments. # This also is also a high level location where the throttle semaphore # is managed which is acquired for all RPC requests. Non-RPC requests # can acquire it as necessary and *should* release it when they are # finished with it, however if they fail to do so or encounter an error # the semaphore will be released here as a fail safe. self.connection.settimeout(smoke_zephyr.utilities.parse_timespan('20s')) # set a timeout as a fail safe self.rpc_session_id = self.headers.get(server_rpc.RPC_AUTH_HEADER, None) # delete cached properties so they are handled per request instead of connection. for cache_prop in ('_campaign_id', '_message_id', '_visit_id'): if hasattr(self, cache_prop): delattr(self, cache_prop) self.adjust_path() self._session = db_manager.Session() http_method_handler = None try: signals.request_handle.send(self) http_method_handler = getattr(super(KingPhisherRequestHandler, self), 'do_' + self.command) http_method_handler(*args, **kwargs) except errors.KingPhisherAbortRequestError as error: if http_method_handler is None: self.logger.debug('http request aborted by a signal handler') else: self.logger.info('http request aborted') if not error.response_sent: self.respond_not_found() finally: if self.semaphore_acquired: self.logger.warning('http request failed to cleanly release resources') self.semaphore_release() self._session.close() self.connection.settimeout(None)
def main(): parser = argparse.ArgumentParser(description='King Phisher Interactive Database Console', conflict_handler='resolve') parser.add_argument('-v', '--version', action='version', version=parser.prog + ' Version: ' + version.version) parser.add_argument('-L', '--log', dest='loglvl', action='store', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], default='CRITICAL', help='set the logging level') config_group = parser.add_mutually_exclusive_group(required=True) config_group.add_argument('-c', '--config', dest='server_config', type=argparse.FileType('r'), help='the server configuration file') config_group.add_argument('-u', '--url', dest='database_url', help='the database connection url') arguments = parser.parse_args() logging.getLogger('').setLevel(logging.DEBUG) console_log_handler = logging.StreamHandler() console_log_handler.setLevel(getattr(logging, arguments.loglvl)) console_log_handler.setFormatter(logging.Formatter("%(levelname)-8s %(message)s")) logging.getLogger('').addHandler(console_log_handler) if arguments.database_url: database_connection_url = arguments.database_url elif arguments.server_config: server_config = yaml.load(arguments.server_config) database_connection_url = server_config['server']['database'] else: raise RuntimeError('no database connection was specified') engine = manager.init_database(database_connection_url) session = manager.Session() console = code.InteractiveConsole(dict(engine=engine, manager=manager, models=models, session=session)) console.interact('starting interactive database console')
def rpc_campaign_message_new(self, campaign_id, email_id, target_email, company_name, first_name, last_name): """ Record a message that has been sent as part of a campaign. These details can be retrieved later for value substitution in template pages. :param int campaign_id: The ID of the campaign. :param str email_id: The message id of the sent email. :param str target_email: The email address that the message was sent to. :param str company_name: The company name value for the message. :param str first_name: The first name of the message's recipient. :param str last_name: The last name of the message's recipient. """ session = db_manager.Session() message = db_models.Message() message.id = email_id message.campaign_id = campaign_id message.target_email = target_email message.company_name = company_name message.first_name = first_name message.last_name = last_name session.add(message) session.commit() session.close() return
def downgrade(): op.drop_table('storage_data') db_manager.Session.remove() db_manager.Session.configure(bind=op.get_bind()) session = db_manager.Session() db_manager.set_meta_data('schema_version', 6, session=session) session.commit()
def downgrade(): op.drop_table('authenticated_sessions') db_manager.Session.remove() db_manager.Session.configure(bind=op.get_bind()) session = db_manager.Session() db_manager.set_meta_data('schema_version', 5, session=session) session.commit()
def setUp(self): username = '******' self._session = db_manager.Session() self.user = self._session.query( db_models.User).filter_by(name=username).first() if self.user is None: self.user = db_models.User(name=username) self._session.add(self.user) self._session.commit()
def test_query_auth_middleware_no_session(self): self._init_db() session = db_manager.Session() result = graphql_schema.execute( "{ db { users { edges { node { id otpSecret } } } } }", context_value={'session': session}) users = result.data['db']['users']['edges'] self.assertEquals(len(users), 2) self.assertEquals(users[0]['node']['otpSecret'], 'secret') self.assertEquals(users[1]['node']['otpSecret'], 'secret')
def test_query_get_pageinfo(self): self._init_db() session = db_manager.Session() result = graphql_schema.execute( "{ db { users(first: 1) { total pageInfo { hasNextPage } } } }", context_value={'session': session}) users = result.data['db']['users'] self.assertEquals(users['total'], 2) self.assertIn('pageInfo', users) self.assertTrue(users['pageInfo'].get('hasNextPage', False))
def downgrade(): op.drop_column('campaigns', 'description') op.drop_column('messages', 'opener_ip') op.drop_column('messages', 'opener_user_agent') db_manager.Session.remove() db_manager.Session.configure(bind=op.get_bind()) session = db_manager.Session() db_manager.set_meta_data('schema_version', 3, session=session) 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 """ 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 main(): parser = argparse.ArgumentParser(description='King Phisher TOTP Enrollment Utility', conflict_handler='resolve') utilities.argp_add_args(parser) config_group = parser.add_mutually_exclusive_group(required=True) config_group.add_argument('-c', '--config', dest='server_config', type=argparse.FileType('r'), help='the server configuration file') config_group.add_argument('-u', '--url', dest='database_url', help='the database connection url') parser.add_argument('--otp', dest='otp_secret', help='a specific otp secret') parser.add_argument('user', help='the user to mange') parser.add_argument('action', choices=('remove', 'set', 'show'), help='the action to preform') parser.epilog = PARSER_EPILOG arguments = parser.parse_args() utilities.configure_stream_logger(arguments.loglvl, arguments.logger) if arguments.database_url: database_connection_url = arguments.database_url elif arguments.server_config: server_config = yaml.load(arguments.server_config) database_connection_url = server_config['server']['database'] else: raise RuntimeError('no database connection was specified') manager.init_database(database_connection_url) session = manager.Session() user = session.query(models.User).filter_by(id=arguments.user).first() if not user: color.print_error("invalid user id: {0}".format(arguments.user)) return for case in utilities.switch(arguments.action): if case('remove'): user.otp_secret = None break if case('set'): if user.otp_secret: color.print_error("the specified user already has an otp secret set") return if arguments.otp_secret: new_otp = arguments.otp_secret else: new_otp = pyotp.random_base32() if len(new_otp) != 16: color.print_error("invalid otp secret length, must be 16") return user.otp_secret = new_otp break if user.otp_secret: color.print_status("user: {0} otp: {1}".format(user.id, user.otp_secret)) totp = pyotp.TOTP(user.otp_secret) uri = totp.provisioning_uri(user.id + '@king-phisher') + '&issuer=King%20Phisher' color.print_status("provisioning uri: {0}".format(uri)) else: color.print_status("user: {0} otp: N/A".format(user.id)) session.commit()
def rpc_test_login(self, username, password, otp=None): session = db_manager.Session() user = session.query(db_models.User).filter_by(name=username).first() if not user: user = db_models.User(name=username) user.last_login = db_models.current_timestamp() session.add(user) session.commit() session_id = self.server.session_manager.put(user) session.close() return True, constants.ConnectionErrorReason.SUCCESS, session_id
def _init_db(self): try: db_manager.init_database('sqlite://') except Exception as error: self.fail("failed to initialize the database (error: {0})".format(error.__class__.__name__)) alice = db_models.User(id='alice', otp_secret='secret') calie = db_models.User(id='calie', otp_secret='secret') session = db_manager.Session() session.add(alice) session.add(calie) session.commit() session.close()
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 self.semaphore_acquire() if self.message_id == self.config.get('server.secret_id'): self.semaphore_release() 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.semaphore_release() 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: session.close() self.semaphore_release() self.server.logger.warning( 'denying request with not found due to invalid hostname') raise errors.KingPhisherAbortRequestError() if campaign.has_expired: session.close() self.semaphore_release() self.server.logger.warning( 'denying request because the campaign has expired') 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(): session.close() self.semaphore_release() self.server.logger.warning( 'denying request because credentials were already harvested' ) raise errors.KingPhisherAbortRequestError() session.close() self.semaphore_release() return
def upgrade(): op.add_column('campaigns', sqlalchemy.Column('description', sqlalchemy.String)) op.add_column('messages', sqlalchemy.Column('opener_ip', sqlalchemy.String)) op.add_column('messages', sqlalchemy.Column('opener_user_agent', sqlalchemy.String)) db_manager.Session.remove() db_manager.Session.configure(bind=op.get_bind()) session = db_manager.Session() db_manager.set_meta_data('schema_version', 4, session=session) session.commit()
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() session.delete(db_manager.get_row_by_id(session, table, row_id)) session.commit() session.close() return
def test_query_auth_middleware_session(self): self._init_db() session = db_manager.Session() rpc_session = aaa.AuthenticatedSession('alice') result = graphql.schema.execute( "{ db { users { edges { node { id, otpSecret } } } } }", context_value={'rpc_session': rpc_session, 'session': session} ) users = result.data['db']['users']['edges'] self.assertEquals(len(users), 2) self.assertEquals(users[0]['node']['id'], 'alice') self.assertEquals(users[0]['node']['otpSecret'], 'secret') self.assertIsNone(users[1]['node']['otpSecret'])