def setUp(self): app.app.config['WTF_CSRF_ENABLED'] = False self.work_dir = tempfile.mkdtemp() self.config_actor = ConfigActor( os.path.join(os.path.dirname(__file__), 'beeswarmcfg.json.test'), self.work_dir) self.config_actor.start() self.app = app.app.test_client() self.authenticator = Authenticator() database.setup_db('sqlite://') session = database.get_session() # dummy entities self.authenticator.add_user('test', 'test', 0) self.client_id = str(uuid.uuid4()) self.client_password = str(uuid.uuid4()) self.authenticator.add_user(self.client_id, self.client_password, 2) self.honeypot_id = str(uuid.uuid4()) self.honeypot_password = str(uuid.uuid4()) self.authenticator.add_user(self.honeypot_id, self.honeypot_password, 1) session.add_all([ Client(id=self.client_id, configuration='test_client_config'), Honeypot(id=self.honeypot_id, configuration='test_honeypot_config') ]) session.commit()
def __init__(self, work_dir, config, **kwargs): """ Main class for the Web-Interface. It takes care of setting up the database, managing the users, etc. :param work_dir: The working directory (usually the current working directory). :param config_arg: Beeswarm configuration dictionary, None if not configuration was supplied. """ customize = kwargs['customize'] reset_password = kwargs['reset_password'] if 'clear_db' in kwargs: clear_sessions = kwargs['clear_db'] else: clear_sessions = True self.work_dir = work_dir self.config_file = 'beeswarmcfg.json' if config is None: Server.prepare_environment(work_dir, customize) with open(os.path.join(work_dir, self.config_file), 'r') as config_file: self.config = json.load(config_file, object_hook=asciify) else: self.config = config # list of all self-running (actor) objects that receive or send # messages on one or more zmq queues self.actors = [] gevent.spawn(self.message_proxy, work_dir) config_actor = ConfigActor(self.config_file, work_dir) config_actor.start() self.actors.append(config_actor) database_setup.setup_db( os.path.join(self.config['sql']['connection_string'])) persistanceActor = SessionPersister(clear_sessions) persistanceActor.start() self.actors.append(persistanceActor) gevent.sleep() self.workers = {} self.greenlets = [] self.started = False from beeswarm.server.webapp import app self.app = app.app self.app.config['CERT_PATH'] = self.config['ssl']['certpath'] self.authenticator = Authenticator() self.authenticator.ensure_default_user(reset_password)
def setUp(self): app.app.config["WTF_CSRF_ENABLED"] = False self.work_dir = tempfile.mkdtemp() self.config_actor = ConfigActor(os.path.join(os.path.dirname(__file__), "beeswarmcfg.json.test"), self.work_dir) self.config_actor.start() self.app = app.app.test_client() self.authenticator = Authenticator() database.setup_db("sqlite://") session = database.get_session() # dummy entities self.authenticator.add_user("test", "test", 0) self.client_id = str(uuid.uuid4()) self.client_password = str(uuid.uuid4()) self.authenticator.add_user(self.client_id, self.client_password, 2) self.honeypot_id = str(uuid.uuid4()) self.honeypot_password = str(uuid.uuid4()) self.authenticator.add_user(self.honeypot_id, self.honeypot_password, 1) session.add_all( [ Client(id=self.client_id, configuration="test_client_config"), Honeypot(id=self.honeypot_id, configuration="test_honeypot_config"), ] ) session.commit()
def setUp(self): app.app.config['WTF_CSRF_ENABLED'] = False app.app.config['CERT_PATH'] = os.path.join(os.path.dirname(__file__), 'beeswarmcfg.json.test') app.app.config['SERVER_CONFIG'] = os.path.join(os.path.dirname(__file__), 'beeswarmcfg.json.test') self.app = app.app.test_client() self.authenticator = Authenticator() database.setup_db('sqlite://') session = database.get_session() #dummy entities self.authenticator.add_user('test', 'test', 0) self.client_id = str(uuid.uuid4()) self.client_password = str(uuid.uuid4()) self.authenticator.add_user(self.client_id, self.client_password, 2) self.honeypot_id = str(uuid.uuid4()) self.honeypot_password = str(uuid.uuid4()) self.authenticator.add_user(self.honeypot_id, self.honeypot_password, 1) session.add_all([Client(id=self.client_id, configuration='test_client_config'), Honeypot(id=self.honeypot_id, configuration='test_honeypot_config') ]) session.commit()
def __init__(self, work_dir, config, **kwargs): """ Main class for the Web-Interface. It takes care of setting up the database, managing the users, etc. :param work_dir: The working directory (usually the current working directory). :param config_arg: Beeswarm configuration dictionary, None if not configuration was supplied. """ customize = kwargs['customize'] reset_password = kwargs['reset_password'] if 'clear_db' in kwargs: clear_sessions = kwargs['clear_db'] else: clear_sessions = True self.work_dir = work_dir self.config_file = 'beeswarmcfg.json' if config is None: Server.prepare_environment(work_dir, customize) with open(os.path.join(work_dir, self.config_file), 'r') as config_file: self.config = json.load(config_file, object_hook=asciify) else: self.config = config # list of all self-running (actor) objects that receive or send # messages on one or more zmq queues self.actors = [] gevent.spawn(self.message_proxy, work_dir) config_actor = ConfigActor(self.config_file, work_dir) config_actor.start() self.actors.append(config_actor) database_setup.setup_db(os.path.join(self.config['sql']['connection_string'])) persistanceActor = SessionPersister(clear_sessions) persistanceActor.start() self.actors.append(persistanceActor) gevent.sleep() self.workers = {} self.greenlets = [] self.started = False from beeswarm.server.webapp import app self.app = app.app self.app.config['CERT_PATH'] = self.config['ssl']['certpath'] self.authenticator = Authenticator() self.authenticator.ensure_default_user(reset_password)
def __init__(self, work_dir, config, curses_screen=None, **kwargs): """ Main class for the Web-Interface. It takes care of setting up the database, managing the users, etc. :param work_dir: The working directory (usually the current working directory). :param config_arg: Beeswarm configuration dictionary, None if not configuration was supplied. :param curses_screen: This parameter is to maintain a similar interface for all the modes. It is ignored for the Server. """ customize = kwargs['customize'] if config is None: Server.prepare_environment(work_dir, customize) with open(os.path.join(work_dir, 'beeswarmcfg.json'), 'r') as config_file: config = json.load(config_file, object_hook=asciify) self.work_dir = work_dir self.config = config self.config_file = 'beeswarmcfg.json' self.actors = [] config_actor = ConfigActor('beeswarmcfg.json', work_dir) config_actor.start() self.actors.append(config_actor) self.workers = {} self.greenlets = [] self.started = False database_setup.setup_db(os.path.join(self.config['sql']['connection_string'])) self.app = app.app self.app.config['CERT_PATH'] = self.config['ssl']['certpath'] self.app.config['SERVER_CONFIG'] = 'beeswarmcfg.json' self.authenticator = Authenticator() self.authenticator.ensure_default_user() gevent.spawn(self.message_proxy, work_dir) persistanceWorker = PersistanceWorker() gevent.spawn(persistanceWorker.start) gevent.sleep()
class WebappTests(unittest.TestCase): def setUp(self): app.app.config["WTF_CSRF_ENABLED"] = False self.work_dir = tempfile.mkdtemp() self.config_actor = ConfigActor(os.path.join(os.path.dirname(__file__), "beeswarmcfg.json.test"), self.work_dir) self.config_actor.start() self.app = app.app.test_client() self.authenticator = Authenticator() database.setup_db("sqlite://") session = database.get_session() # dummy entities self.authenticator.add_user("test", "test", 0) self.client_id = str(uuid.uuid4()) self.client_password = str(uuid.uuid4()) self.authenticator.add_user(self.client_id, self.client_password, 2) self.honeypot_id = str(uuid.uuid4()) self.honeypot_password = str(uuid.uuid4()) self.authenticator.add_user(self.honeypot_id, self.honeypot_password, 1) session.add_all( [ Client(id=self.client_id, configuration="test_client_config"), Honeypot(id=self.honeypot_id, configuration="test_honeypot_config"), ] ) session.commit() def tearDown(self): database.clear_db() self.config_actor.close() shutil.rmtree(self.work_dir) # TODO: All these posts should be moved to ZMQ tests # def test_basic_client_post(self): # """ # Tests if a bait_session dict can be posted without exceptions. # """ # self.login(self.client_id, self.client_password) # data_dict = { # 'id': str(uuid.uuid4()), # 'client_id': self.client_id, # 'honeypot_id': self.honeypot_id, # 'protocol': 'pop3', # 'destination_ip': '127.0.0.1', # 'destination_port': '110', # 'source_ip': '123.123.123.123', # 'source_port': 12345, # 'timestamp': datetime.utcnow().isoformat(), # 'did_connect': True, # 'did_login': True, # 'did_complete': True, # 'protocol_data': None, # 'login_attempts': [{'id': str(uuid.uuid4()), 'username': '******', 'password': '******', 'successful': True, # 'timestamp': datetime.utcnow().isoformat()}] # } # r = self.app.post('/ws/client_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '200 OK') # # def test_basic_unsuccessful_client_post(self): # """ # Tests if an error is returned when data is posted without ID values. # """ # # self.login(self.client_id, self.client_password) # # #missing id's # data_dict = { # 'protocol': 'pop3', # 'username': '******', # 'password': '******', # 'server_host': '127.0.0.1', # 'server_port': '110', # 'source_ip': '123.123.123.123', # 'source_port': 12345, # 'timestamp': datetime.utcnow().isoformat(), # 'did_connect': True, # 'did_login': True, # 'did_complete': True, # 'protocol_data': None # } # # r = self.app.post('/ws/client_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '500 INTERNAL SERVER ERROR') # # def test_basic_honeypot_post(self): # """ # Tests if a session dict can be posted without exceptions. # """ # # self.login(self.honeypot_id, self.honeypot_password) # # data_dict = { # 'id': 'ba9fdb3d-0efb-4764-9a6b-d9b86eccda96', # 'honeypot_id': self.honeypot_id, # 'destination_ip': '192.168.1.1', # 'destination_port': 8023, # 'protocol': 'telnet', # 'source_ip': '127.0.0.1', # 'timestamp': '2013-05-07T22:21:19.453828', # 'source_port': 61175, # 'login_attempts': [ # {'username': '******', 'timestamp': '2013-05-07T22:21:20.846805', 'password': '******', # 'id': '027bd968-f8ea-4a69-8d4c-6cf21476ca10', 'successful': False}, # {'username': '******', 'timestamp': '2013-05-07T22:21:21.150571', 'password': '******', # 'id': '603f40a4-e7eb-442d-9fde-0cd3ba707af7', 'successful': False}, ], # 'transcript': [ # {'timestamp': '2013-05-07T22:21:20.846805', 'direction': 'in', 'data': 'whoami\r\n'}, # {'timestamp': '2013-05-07T22:21:21.136800', 'direction': 'out', 'data': 'james_brown\r\n$:~'}] # } # # r = self.app.post('/ws/honeypot_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '200 OK') # # def test_basic_unsuccessful_honeypot_post(self): # """ # Tests if an error is returned when data is posted without ID values. # """ # # self.login(self.honeypot_id, self.honeypot_password) # # #missing id # data_dict = { # 'honeypot_id': self.honeypot_id, # 'destination_ip': '192.168.1.1', # 'destination_port': 8023, # 'protocol': 'telnet', # 'source_ip': '127.0.0.1', # 'timestamp': '2013-05-07T22:21:19.453828', # 'source_port': 61175, # 'login_attempts': [ # {'username': '******', 'timestamp': '2013-05-07T22:21:20.846805', 'password': '******', # 'id': '027bd968-f8ea-4a69-8d4c-6cf21476ca10', 'successful': False}, # {'username': '******', 'timestamp': '2013-05-07T22:21:21.150571', 'password': '******', # 'id': '603f40a4-e7eb-442d-9fde-0cd3ba707af7', 'successful': False}, ], # 'transcript': [ # {'timestamp': '2013-05-07T22:21:20.846805', 'direction': 'in', 'data': 'whoami\r\n'}, # {'timestamp': '2013-05-07T22:21:21.136800', 'direction': 'out', 'data': 'james_brown\r\n$:~'} # ] # } # r = self.app.post('/ws/honeypot_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '500 INTERNAL SERVER ERROR') # # def test_new_client(self): # """ # Tests if a new Client configuration can be posted successfully # """ # # post_data = { # 'http_enabled': True, # 'http_server': '127.0.0.1', # 'http_port': 80, # 'http_active_range': '13:40 - 16:30', # 'http_sleep_interval': 0, # 'http_activation_probability': 1, # 'http_login': '******', # 'http_password': '******', # # 'https_enabled': True, # 'https_server': '127.0.0.1', # 'https_port': 80, # 'https_active_range': '13:40 - 16:30', # 'https_sleep_interval': 0, # 'https_activation_probability': 1, # 'https_login': '******', # 'https_password': '******', # # 'pop3s_enabled': True, # 'pop3s_server': '127.0.0.1', # 'pop3s_port': 80, # 'pop3s_active_range': '13:40 - 16:30', # 'pop3s_sleep_interval': 0, # 'pop3s_activation_probability': 1, # 'pop3s_login': '******', # 'pop3s_password': '******', # # 'ssh_enabled': True, # 'ssh_server': '127.0.0.1', # 'ssh_port': 80, # 'ssh_active_range': '13:40 - 16:30', # 'ssh_sleep_interval': 0, # 'ssh_activation_probability': 1, # 'ssh_login': '******', # 'ssh_password': '******', # # 'ftp_enabled': True, # 'ftp_server': '127.0.0.1', # 'ftp_port': 80, # 'ftp_active_range': '13:40 - 16:30', # 'ftp_sleep_interval': 0, # 'ftp_activation_probability': 1, # 'ftp_login': '******', # 'ftp_password': '******', # # 'pop3_enabled': True, # 'pop3_server': '127.0.0.1', # 'pop3_port': 110, # 'pop3_active_range': '13:40 - 16:30', # 'pop3_sleep_interval': 0, # 'pop3_activation_probability': 1, # 'pop3_login': '******', # 'pop3_password': '******', # # 'smtp_enabled': True, # 'smtp_server': '127.0.0.1', # 'smtp_port': 25, # 'smtp_active_range': '13:40 - 16:30', # 'smtp_sleep_interval': 0, # 'smtp_activation_probability': 1, # 'smtp_login': '******', # 'smtp_password': '******', # # 'vnc_enabled': True, # 'vnc_server': '127.0.0.1', # 'vnc_port': 5900, # 'vnc_active_range': '13:40 - 16:30', # 'vnc_sleep_interval': 0, # 'vnc_activation_probability': 1, # 'vnc_login': '******', # 'vnc_password': '******', # # 'telnet_enabled': True, # 'telnet_server': '127.0.0.1', # 'telnet_port': 23, # 'telnet_active_range': '13:40 - 16:30', # 'telnet_sleep_interval': 0, # 'telnet_activation_probability': 1, # 'telnet_login': '******', # 'telnet_password': '******', # } # self.login('test', 'test') # resp = self.app.post('/ws/client', data=post_data) # self.assertTrue(200, resp.status_code) # self.logout() # # def test_new_honeypot(self): # """ # Tests whether new Honeypot configuration can be posted successfully. # """ # post_data = { # 'http_enabled': True, # 'http_port': 80, # 'http_banner': 'Microsoft-IIS/5.0', # # 'https_enabled': False, # 'https_port': 443, # 'https_banner': 'Microsoft-IIS/5.0', # # 'ftp_enabled': False, # 'ftp_port': 21, # 'ftp_max_attempts': 3, # 'ftp_banner': 'Microsoft FTP Server', # # 'smtp_enabled': False, # 'smtp_port': 25, # 'smtp_banner': 'Microsoft ESMTP MAIL service ready', # # 'vnc_enabled': False, # 'vnc_port': 5900, # # 'telnet_enabled': False, # 'telnet_port': 23, # 'telnet_max_attempts': 3, # # 'pop3_enabled': False, # 'pop3_port': 110, # 'pop3_max_attempts': 3, # # 'pop3s_enabled': False, # 'pop3s_port': 110, # 'pop3s_max_attempts': 3, # # 'ssh_enabled': False, # 'ssh_port': 22, # 'ssh_key': 'server.key' # } # self.login('test', 'test') # resp = self.app.post('/ws/honeypot', data=post_data) # self.assertTrue(200, resp.status_code) # self.logout() # # def test_new_honeypot_config(self): # """ Tests if a Honeypot config is being returned correctly """ # # resp = self.app.get('/ws/honeypot/config/' + self.honeypot_id) # self.assertEquals(resp.data, 'test_honeypot_config') # # def test_new_client_config(self): # """ Tests if a Client config is being returned correctly """ # # resp = self.app.get('/ws/client/config/' + self.client_id) # self.assertEquals(resp.data, 'test_client_config') def test_data_sessions_all(self): """ Tests if all sessions are returned properly""" self.login("test", "test") self.populate_sessions() resp = self.app.get("/data/sessions/all") table_data = json.loads(resp.data) self.assertEquals(len(table_data), 4) self.logout() def test_data_sessions_honeybees(self): """ Tests if bait_sessions are returned properly """ self.login("test", "test") self.populate_honeybees() resp = self.app.get("/data/sessions/bait_sessions") table_data = json.loads(resp.data) self.assertEquals(len(table_data), 3) self.logout() def test_data_sessions_attacks(self): """ Tests if attacks are returned properly """ self.login("test", "test") self.populate_sessions() resp = self.app.get("/data/sessions/attacks") table_data = json.loads(resp.data) self.assertEquals(len(table_data), 4) self.logout() def test_data_honeypot(self): """ Tests if Honeypot information is returned properly """ self.login("test", "test") self.populate_honeypots() resp = self.app.get("/data/honeypots") table_data = json.loads(resp.data) self.assertEquals(len(table_data), 5) # One is added in the setup method, and 4 in populate def test_data_client(self): """ Tests if Client information is returned properly """ self.login("test", "test") self.populate_clients() resp = self.app.get("/data/clients") table_data = json.loads(resp.data) self.assertEquals(len(table_data), 5) # One is added in the setup method, and 4 in populate def test_data_transcripts(self): """ Tests that if given a session ID we can extract the relevant transcripts""" db_session = database.get_session() self.login("test", "test") session_id = str(uuid.uuid4()) timestamp = datetime.utcnow() db_session.add(Transcript(timestamp=timestamp, direction="outgoing", data="whoami", session_id=session_id)) db_session.add(Transcript(timestamp=timestamp, direction="outgoing", data="root\r\n", session_id=session_id)) db_session.commit() resp = self.app.get("/data/session/{0}/transcript".format(session_id)) data = json.loads(resp.data) string_timestamp = timestamp.strftime("%Y-%m-%d %H:%M:%S") expected_result = [ {u"direction": u"outgoing", u"data": u"whoami", u"time": u"{0}".format(string_timestamp)}, {u"direction": u"outgoing", u"data": u"root\r\n", u"time": u"{0}".format(string_timestamp)}, ] self.assertDictEqual(sorted(data)[0], sorted(expected_result)[0]) def test_login_logout(self): """ Tests basic login/logout """ self.login("test", "test") self.logout() # def test_honeypot_delete(self): # """ Tests the '/ws/honeypot/delete' route.""" # # self.login('test', 'test') # self.populate_honeypots() # data = [self.honeypots[0], self.honeypots[1]] # self.app.post('/ws/drone/delete', data=json.dumps(data)) # db_session = database.get_session() # honeypot_count = db_session.query(Honeypot).count() # self.assertEquals(3, honeypot_count) # gevent.sleep() # # def test_client_delete(self): # """ Tests the '/ws/client/delete' route.""" # # self.login('test', 'test') # self.populate_clients() # data = [self.clients[0], self.clients[1]] # print data # self.app.post('/ws/drone/delete', data=json.dumps(data)) # gevent.sleep(1) # db_session = database.get_session() # nclients = db_session.query(Client).count() # self.assertEquals(3, nclients) def test_get_baitusers(self): """ Tests GET on the '/ws/bait_users' route.""" self.login("test", "test") self.populate_bait_users() resp = self.app.get("/ws/bait_users") table_data = json.loads(resp.data) self.assertEquals(len(table_data), 2) self.logout() def test_post_baitusers(self): """ Tests POST on the '/ws/bait_users' route.""" self.login("test", "test") data = [ {"username": "******", "password": "******"}, {"username": "******", "password": "******"}, {"username": "******", "password": "******"}, ] self.app.post("/ws/bait_users", data=json.dumps(data), follow_redirects=True) db_session = database.get_session() bait_user_count = db_session.query(BaitUser).count() self.assertEquals(bait_user_count, 3) self.logout() def populate_clients(self): """ Populates the database with 4 clients """ db_session = database.get_session() self.clients = [] for i in xrange(4): # We add 4 here, but one is added in the setup method curr_id = str(uuid.uuid4()) curr_id = curr_id.encode("utf-8") self.clients.append(curr_id) f = Client(id=curr_id) db_session.add(f) db_session.commit() def populate_honeypots(self): """ Populates the database with 4 honeypots """ db_session = database.get_session() self.honeypots = [] for i in xrange(4): # We add 4 here, but one is added in the setup method curr_id = str(uuid.uuid4()) curr_id = curr_id.encode("utf-8") self.honeypots.append(curr_id) h = Honeypot(id=curr_id) db_session.add(h) db_session.commit() def populate_bait_users(self): """ Populates the database with 2 bait users """ db_session = database.get_session() db_session.query(BaitUser).delete() self.clients = [] for c in [("userA", "passA"), ("userB", "passB")]: # We add 4 here, but one is added in the setup method bait_user = BaitUser(username=c[0], password=c[1]) db_session.add(bait_user) db_session.commit() def login(self, username, password): """ Logs into the web-app """ data = {"username": username, "password": password} return self.app.post("/login", data=data, follow_redirects=True) def populate_honeybees(self): """ Populates the database with 3 Honeybees """ db_session = database.get_session() for i in xrange(3): h = BaitSession( id=str(uuid.uuid4()), timestamp=datetime.utcnow(), received=datetime.utcnow(), protocol="ssh", destination_ip="1.2.3.4", destination_port=1234, source_ip="4.3.2.1", source_port=4321, did_connect=True, did_login=False, did_complete=True, ) a = Authentication( id=str(uuid.uuid4()), username="******", password="******", successful=False, timestamp=datetime.utcnow() ) h.authentication.append(a) db_session.add(h) db_session.commit() def populate_sessions(self): """ Populates the database with 3 Sessions """ db_session = database.get_session() for i in xrange(4): s = Session( id=str(uuid.uuid4()), timestamp=datetime.utcnow(), received=datetime.utcnow(), protocol="telnet", destination_ip="123.123.123.123", destination_port=1234, source_ip="12.12.12.12", source_port=12345, classification_id="asd", ) a = Authentication( id=str(uuid.uuid4()), username="******", password="******", successful=False, timestamp=datetime.utcnow() ) s.authentication.append(a) db_session.add(s) db_session.commit() def logout(self): return self.app.get("/logout", follow_redirects=True)
class WebappTests(unittest.TestCase): def setUp(self): app.app.config['WTF_CSRF_ENABLED'] = False app.app.config['CERT_PATH'] = os.path.join(os.path.dirname(__file__), 'beeswarmcfg.json.test') app.app.config['SERVER_CONFIG'] = os.path.join(os.path.dirname(__file__), 'beeswarmcfg.json.test') self.app = app.app.test_client() self.authenticator = Authenticator() database.setup_db('sqlite://') session = database.get_session() #dummy entities self.authenticator.add_user('test', 'test', 0) self.client_id = str(uuid.uuid4()) self.client_password = str(uuid.uuid4()) self.authenticator.add_user(self.client_id, self.client_password, 2) self.honeypot_id = str(uuid.uuid4()) self.honeypot_password = str(uuid.uuid4()) self.authenticator.add_user(self.honeypot_id, self.honeypot_password, 1) session.add_all([Client(id=self.client_id, configuration='test_client_config'), Honeypot(id=self.honeypot_id, configuration='test_honeypot_config') ]) session.commit() def tearDown(self): database.clear_db() def test_basic_client_post(self): """ Tests if a bait_session dict can be posted without exceptions. """ self.login(self.client_id, self.client_password) data_dict = { 'id': str(uuid.uuid4()), 'client_id': self.client_id, 'honeypot_id': self.honeypot_id, 'protocol': 'pop3', 'destination_ip': '127.0.0.1', 'destination_port': '110', 'source_ip': '123.123.123.123', 'source_port': 12345, 'timestamp': datetime.utcnow().isoformat(), 'did_connect': True, 'did_login': True, 'did_complete': True, 'protocol_data': None, 'login_attempts': [{'id': str(uuid.uuid4()), 'username': '******', 'password': '******', 'successful': True, 'timestamp': datetime.utcnow().isoformat()}] } r = self.app.post('/ws/client_data', data=json.dumps(data_dict), content_type='application/json') self.assertEquals(r.status, '200 OK') def test_basic_unsuccessful_client_post(self): """ Tests if an error is returned when data is posted without ID values. """ self.login(self.client_id, self.client_password) #missing id's data_dict = { 'protocol': 'pop3', 'username': '******', 'password': '******', 'server_host': '127.0.0.1', 'server_port': '110', 'source_ip': '123.123.123.123', 'source_port': 12345, 'timestamp': datetime.utcnow().isoformat(), 'did_connect': True, 'did_login': True, 'did_complete': True, 'protocol_data': None } r = self.app.post('/ws/client_data', data=json.dumps(data_dict), content_type='application/json') self.assertEquals(r.status, '500 INTERNAL SERVER ERROR') def test_basic_honeypot_post(self): """ Tests if a session dict can be posted without exceptions. """ self.login(self.honeypot_id, self.honeypot_password) data_dict = { 'id': 'ba9fdb3d-0efb-4764-9a6b-d9b86eccda96', 'honeypot_id': self.honeypot_id, 'destination_ip': '192.168.1.1', 'destination_port': 8023, 'protocol': 'telnet', 'source_ip': '127.0.0.1', 'timestamp': '2013-05-07T22:21:19.453828', 'source_port': 61175, 'login_attempts': [ {'username': '******', 'timestamp': '2013-05-07T22:21:20.846805', 'password': '******', 'id': '027bd968-f8ea-4a69-8d4c-6cf21476ca10', 'successful': False}, {'username': '******', 'timestamp': '2013-05-07T22:21:21.150571', 'password': '******', 'id': '603f40a4-e7eb-442d-9fde-0cd3ba707af7', 'successful': False}, ], 'transcript': [ {'timestamp': '2013-05-07T22:21:20.846805', 'direction': 'in', 'data': 'whoami\r\n'}, {'timestamp': '2013-05-07T22:21:21.136800', 'direction': 'out', 'data': 'james_brown\r\n$:~'}] } r = self.app.post('/ws/honeypot_data', data=json.dumps(data_dict), content_type='application/json') self.assertEquals(r.status, '200 OK') def test_basic_unsuccessful_honeypot_post(self): """ Tests if an error is returned when data is posted without ID values. """ self.login(self.honeypot_id, self.honeypot_password) #missing id data_dict = { 'honeypot_id': self.honeypot_id, 'destination_ip': '192.168.1.1', 'destination_port': 8023, 'protocol': 'telnet', 'source_ip': '127.0.0.1', 'timestamp': '2013-05-07T22:21:19.453828', 'source_port': 61175, 'login_attempts': [ {'username': '******', 'timestamp': '2013-05-07T22:21:20.846805', 'password': '******', 'id': '027bd968-f8ea-4a69-8d4c-6cf21476ca10', 'successful': False}, {'username': '******', 'timestamp': '2013-05-07T22:21:21.150571', 'password': '******', 'id': '603f40a4-e7eb-442d-9fde-0cd3ba707af7', 'successful': False}, ], 'transcript': [ {'timestamp': '2013-05-07T22:21:20.846805', 'direction': 'in', 'data': 'whoami\r\n'}, {'timestamp': '2013-05-07T22:21:21.136800', 'direction': 'out', 'data': 'james_brown\r\n$:~'} ] } r = self.app.post('/ws/honeypot_data', data=json.dumps(data_dict), content_type='application/json') self.assertEquals(r.status, '500 INTERNAL SERVER ERROR') def test_new_client(self): """ Tests if a new Client configuration can be posted successfully """ post_data = { 'http_enabled': True, 'http_server': '127.0.0.1', 'http_port': 80, 'http_active_range': '13:40 - 16:30', 'http_sleep_interval': 0, 'http_activation_probability': 1, 'http_login': '******', 'http_password': '******', 'https_enabled': True, 'https_server': '127.0.0.1', 'https_port': 80, 'https_active_range': '13:40 - 16:30', 'https_sleep_interval': 0, 'https_activation_probability': 1, 'https_login': '******', 'https_password': '******', 'pop3s_enabled': True, 'pop3s_server': '127.0.0.1', 'pop3s_port': 80, 'pop3s_active_range': '13:40 - 16:30', 'pop3s_sleep_interval': 0, 'pop3s_activation_probability': 1, 'pop3s_login': '******', 'pop3s_password': '******', 'ssh_enabled': True, 'ssh_server': '127.0.0.1', 'ssh_port': 80, 'ssh_active_range': '13:40 - 16:30', 'ssh_sleep_interval': 0, 'ssh_activation_probability': 1, 'ssh_login': '******', 'ssh_password': '******', 'ftp_enabled': True, 'ftp_server': '127.0.0.1', 'ftp_port': 80, 'ftp_active_range': '13:40 - 16:30', 'ftp_sleep_interval': 0, 'ftp_activation_probability': 1, 'ftp_login': '******', 'ftp_password': '******', 'pop3_enabled': True, 'pop3_server': '127.0.0.1', 'pop3_port': 110, 'pop3_active_range': '13:40 - 16:30', 'pop3_sleep_interval': 0, 'pop3_activation_probability': 1, 'pop3_login': '******', 'pop3_password': '******', 'smtp_enabled': True, 'smtp_server': '127.0.0.1', 'smtp_port': 25, 'smtp_active_range': '13:40 - 16:30', 'smtp_sleep_interval': 0, 'smtp_activation_probability': 1, 'smtp_login': '******', 'smtp_password': '******', 'vnc_enabled': True, 'vnc_server': '127.0.0.1', 'vnc_port': 5900, 'vnc_active_range': '13:40 - 16:30', 'vnc_sleep_interval': 0, 'vnc_activation_probability': 1, 'vnc_login': '******', 'vnc_password': '******', 'telnet_enabled': True, 'telnet_server': '127.0.0.1', 'telnet_port': 23, 'telnet_active_range': '13:40 - 16:30', 'telnet_sleep_interval': 0, 'telnet_activation_probability': 1, 'telnet_login': '******', 'telnet_password': '******', } self.login('test', 'test') resp = self.app.post('/ws/client', data=post_data) self.assertTrue(200, resp.status_code) self.logout() def test_new_honeypot(self): """ Tests whether new Honeypot configuration can be posted successfully. """ post_data = { 'http_enabled': True, 'http_port': 80, 'http_banner': 'Microsoft-IIS/5.0', 'https_enabled': False, 'https_port': 443, 'https_banner': 'Microsoft-IIS/5.0', 'ftp_enabled': False, 'ftp_port': 21, 'ftp_max_attempts': 3, 'ftp_banner': 'Microsoft FTP Server', 'smtp_enabled': False, 'smtp_port': 25, 'smtp_banner': 'Microsoft ESMTP MAIL service ready', 'vnc_enabled': False, 'vnc_port': 5900, 'telnet_enabled': False, 'telnet_port': 23, 'telnet_max_attempts': 3, 'pop3_enabled': False, 'pop3_port': 110, 'pop3_max_attempts': 3, 'pop3s_enabled': False, 'pop3s_port': 110, 'pop3s_max_attempts': 3, 'ssh_enabled': False, 'ssh_port': 22, 'ssh_key': 'server.key' } self.login('test', 'test') resp = self.app.post('/ws/honeypot', data=post_data) self.assertTrue(200, resp.status_code) self.logout() def test_new_honeypot_config(self): """ Tests if a Honeypot config is being returned correctly """ resp = self.app.get('/ws/honeypot/config/' + self.honeypot_id) self.assertEquals(resp.data, 'test_honeypot_config') def test_new_client_config(self): """ Tests if a Client config is being returned correctly """ resp = self.app.get('/ws/client/config/' + self.client_id) self.assertEquals(resp.data, 'test_client_config') def test_data_sessions_all(self): """ Tests if all sessions are returned properly""" self.login('test', 'test') self.populate_sessions() resp = self.app.get('/data/sessions/all') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 4) self.logout() def test_data_sessions_honeybees(self): """ Tests if bait_sessions are returned properly """ self.login('test', 'test') self.populate_honeybees() resp = self.app.get('/data/sessions/bait_sessions') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 3) self.logout() def test_data_sessions_attacks(self): """ Tests if attacks are returned properly """ self.login('test', 'test') self.populate_sessions() resp = self.app.get('/data/sessions/attacks') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 4) self.logout() def test_data_honeypot(self): """ Tests if Honeypot information is returned properly """ self.login('test', 'test') self.populate_honeypots() resp = self.app.get('/data/honeypots') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 5) # One is added in the setup method, and 4 in populate def test_data_client(self): """ Tests if Client information is returned properly """ self.login('test', 'test') self.populate_clients() resp = self.app.get('/data/clients') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 5) # One is added in the setup method, and 4 in populate def test_data_transcripts(self): """ Tests that if given a session ID we can extract the relevant transcripts""" db_session = database.get_session() self.login('test', 'test') session_id = str(uuid.uuid4()) timestamp = datetime.utcnow() db_session.add(Transcript(timestamp=timestamp, direction='outgoing', data='whoami', session_id=session_id)) db_session.add(Transcript(timestamp=timestamp, direction='outgoing', data='root\r\n', session_id=session_id)) db_session.commit() resp = self.app.get('/data/session/{0}/transcript'.format(session_id)) data = json.loads(resp.data) string_timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S') expected_result = [{u'direction': u'outgoing', u'data': u'whoami', u'time': u'{0}'.format(string_timestamp)}, {u'direction': u'outgoing', u'data': u'root\r\n', u'time': u'{0}'.format(string_timestamp)}] self.assertDictEqual(sorted(data)[0], sorted(expected_result)[0]) def test_settings(self): """ Tests if new settings are successfully written to the config file """ self.login('test', 'test') with open(app.app.config['SERVER_CONFIG'], 'r') as conf: original_config = conf.read() config_modified = os.stat(app.app.config['SERVER_CONFIG']).st_mtime data = { 'bait_session_retain': 3, 'malicious_session_retain': 50, 'ignore_failed_bait_session': False } self.app.post('/settings', data=data, follow_redirects=True) config_next_modified = os.stat(app.app.config['SERVER_CONFIG']).st_mtime self.assertTrue(config_next_modified > config_modified) # Restore original configuration with open(app.app.config['SERVER_CONFIG'], 'w') as conf: conf.write(original_config) def test_login_logout(self): """ Tests basic login/logout """ self.login('test', 'test') self.logout() def test_honeypot_delete(self): """ Tests the '/ws/honeypot/delete' route.""" self.login('test', 'test') self.populate_honeypots() data = [ self.honeypots[0], self.honeypots[1]] self.app.post('/ws/honeypot/delete', data=json.dumps(data)) db_session = database.get_session() honeypot_count = db_session.query(Honeypot).count() self.assertEquals(3, honeypot_count) def test_client_delete(self): """ Tests the '/ws/client/delete' route.""" self.login('test', 'test') self.populate_clients() data = [self.clients[0], self.clients[1]] self.app.post('/ws/client/delete', data=json.dumps(data)) db_session = database.get_session() nclients = db_session.query(Client).count() self.assertEquals(3, nclients) def populate_clients(self): """ Populates the database with 4 clients """ db_session = database.get_session() self.clients = [] for i in xrange(4): # We add 4 here, but one is added in the setup method curr_id = str(uuid.uuid4()) curr_id = curr_id.encode('utf-8') self.clients.append(curr_id) u = User(id=curr_id, password=str(uuid.uuid4()), utype=2) f = Client(id=curr_id) db_session.add(f) db_session.add(u) db_session.commit() def populate_honeypots(self): """ Populates the database with 4 honeypots """ db_session = database.get_session() self.honeypots = [] for i in xrange(4): # We add 4 here, but one is added in the setup method curr_id = str(uuid.uuid4()) curr_id = curr_id.encode('utf-8') self.honeypots.append(curr_id) h = Honeypot(id=curr_id) u = User(id=curr_id, password=str(uuid.uuid4()), utype=1) db_session.add(h) db_session.add(u) db_session.commit() def login(self, username, password): """ Logs into the web-app """ data = { 'username': username, 'password': password } return self.app.post('/login', data=data, follow_redirects=True) def populate_honeybees(self): """ Populates the database with 3 Honeybees """ db_session = database.get_session() for i in xrange(3): h = BaitSession( id=str(uuid.uuid4()), timestamp=datetime.utcnow(), received=datetime.utcnow(), protocol='ssh', destination_ip='1.2.3.4', destination_port=1234, source_ip='4.3.2.1', source_port=4321, did_connect=True, did_login=False, did_complete=True ) a = Authentication(id=str(uuid.uuid4()), username='******', password='******', successful=False, timestamp=datetime.utcnow()) h.authentication.append(a) db_session.add(h) db_session.commit() def populate_sessions(self): """ Populates the database with 3 Sessions """ db_session = database.get_session() for i in xrange(4): s = Session( id=str(uuid.uuid4()), timestamp=datetime.utcnow(), received=datetime.utcnow(), protocol='telnet', destination_ip='123.123.123.123', destination_port=1234, source_ip='12.12.12.12', source_port=12345, classification_id='asd' ) a = Authentication(id=str(uuid.uuid4()), username='******', password='******', successful=False, timestamp=datetime.utcnow()) s.authentication.append(a) db_session.add(s) db_session.commit() def logout(self): return self.app.get('/logout', follow_redirects=True)
class Server(object): def __init__(self, work_dir, config, curses_screen=None, **kwargs): """ Main class for the Web-Interface. It takes care of setting up the database, managing the users, etc. :param work_dir: The working directory (usually the current working directory). :param config_arg: Beeswarm configuration dictionary, None if not configuration was supplied. :param curses_screen: This parameter is to maintain a similar interface for all the modes. It is ignored for the Server. """ customize = kwargs['customize'] if config is None: Server.prepare_environment(work_dir, customize) with open(os.path.join(work_dir, 'beeswarmcfg.json'), 'r') as config_file: config = json.load(config_file, object_hook=asciify) self.work_dir = work_dir self.config = config self.config_file = 'beeswarmcfg.json' self.actors = [] config_actor = ConfigActor('beeswarmcfg.json', work_dir) config_actor.start() self.actors.append(config_actor) self.workers = {} self.greenlets = [] self.started = False database_setup.setup_db(os.path.join(self.config['sql']['connection_string'])) self.app = app.app self.app.config['CERT_PATH'] = self.config['ssl']['certpath'] self.app.config['SERVER_CONFIG'] = 'beeswarmcfg.json' self.authenticator = Authenticator() self.authenticator.ensure_default_user() gevent.spawn(self.message_proxy, work_dir) persistanceWorker = PersistanceWorker() gevent.spawn(persistanceWorker.start) gevent.sleep() # distributes messages between external and internal receivers and senders def message_proxy(self, work_dir): """ drone_data_inboud is for data comming from drones drone_data_outbound is for commands to the drone, topic must either be a drone ID or all for sending a broadcast message to all drones """ ctx = zmq.Context() public_keys_dir = os.path.join(work_dir, 'certificates', 'public_keys') secret_keys_dir = os.path.join(work_dir, 'certificates', 'private_keys') # start and configure auth worker auth = IOLoopAuthenticator() auth.start() auth.allow('127.0.0.1') auth.configure_curve(domain='*', location=public_keys_dir) # external interfaces for communicating with drones server_secret_file = os.path.join(secret_keys_dir, 'beeswarm_server.pri') server_public, server_secret = load_certificate(server_secret_file) drone_data_inbound = ctx.socket(zmq.PULL) drone_data_inbound.curve_secretkey = server_secret drone_data_inbound.curve_publickey = server_public drone_data_inbound.curve_server = True drone_data_inbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_port'])) drone_data_outbound = ctx.socket(zmq.PUB) drone_data_outbound.curve_secretkey = server_secret drone_data_outbound.curve_publickey = server_public drone_data_outbound.curve_server = True drone_data_outbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_command_port'])) # internal interfaces # all inbound session data from drones will be replayed in this socket sessionPublisher = ctx.socket(zmq.PUB) sessionPublisher.bind('ipc://sessionPublisher') # all commands received on this will be published on the external interface drone_command_receiver = ctx.socket(zmq.PULL) drone_command_receiver.bind('ipc://droneCommandReceiver') poller = zmq.Poller() poller.register(drone_data_inbound, zmq.POLLIN) poller.register(drone_command_receiver, zmq.POLLIN) while True: # .recv() gives no context switch - why not? using poller with timeout instead socks = dict(poller.poll(1)) gevent.sleep() if drone_command_receiver in socks and socks[drone_command_receiver] == zmq.POLLIN: data = drone_command_receiver.recv() topic, _ = data.split(' ', 1) logger.debug("Sending drone command to: {0}".format(topic)) # pub socket takes care of filtering drone_data_outbound.send(data) elif drone_data_inbound in socks and socks[drone_data_inbound] == zmq.POLLIN: topic, data = drone_data_inbound.recv().split(' ', 1) logger.debug("Received {0} data.".format(topic)) if topic == Messages.SESSION_HONEYPOT or topic == Messages.SESSION_CLIENT: sessionPublisher.send('{0} {1}'.format(topic, data)) elif topic == Messages.KEY or topic == Messages.CERT: # for now we just store the fingerprint # in the future it might be relevant to store the entire public key and private key # for forensic purposes if topic == Messages.CERT: drone_id, cert = data.split(' ', 1) digest = generate_cert_digest(cert) logging.debug('Storing public key digest: {0} for drone {1}.'.format(digest, drone_id)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter(Drone.id == drone_id).one() drone.cert_digest = digest db_session.add(drone) db_session.commit() elif topic == Messages.PING: drone_id = data db_session = database_setup.get_session() drone = db_session.query(Drone).filter(Drone.id == drone_id).one() drone.last_activity = datetime.now() db_session.add(drone) db_session.commit() else: logger.warn('Message with unknown topic received: {0}'.format(topic)) def start(self, maintenance=True): """ Starts the BeeSwarm server. :param port: The port on which the web-app is to run. """ self.started = True web_port = self.config['network']['web_port'] logger.info('Starting server listening on port {0}'.format(web_port)) print web_port http_server = WSGIServer(('', web_port), self.app, keyfile='server.key', certfile='server.crt') http_server_greenlet = gevent.spawn(http_server.serve_forever) self.workers['http'] = http_server self.greenlets.append(http_server_greenlet) if maintenance: maintenance_greenlet = gevent.spawn(self.start_maintenance_tasks) self.workers['maintenance'] = maintenance_greenlet self.greenlets.append(maintenance_greenlet) drop_privileges() logger.info('Server started and priviliges dropped.') gevent.joinall(self.greenlets) def stop(self): self.started = False logging.info('Stopping server.') self.workers['http'].stop(5) def get_config(self, configfile): """ Loads the configuration from the JSON file, and returns it. :param configfile: Path to the configuration file """ with open(configfile) as config_file: config = json.load(config_file) return config def start_maintenance_tasks(self): # one-off task to ensure we have the correct offset logger.info('Hang on, calculating binary offset - this can take a while!') if os.path.isfile(self.config['iso']['path']): config_tar_offset = find_offset(self.config['iso']['path'], '\x07' * 30) if not config_tar_offset: logger.warning('Beeswarm client ISO was found but is invalid. Bootable clients can not be generated.') raise Exception('Expected binary pattern not found in ISO file.') else: logger.debug('Binary pattern found in ISO at: {0}'.format(config_tar_offset)) with open(self.config_file, 'r+') as config_file: self.config['iso']['offset'] = config_tar_offset #clear file config_file.seek(0) config_file.truncate(0) # and write again config_file.write(json.dumps(self.config, indent=4)) else: logger.warning('Beeswarm client ISO was NOT found. Bootable clients can not be generated.') maintenance_worker = Scheduler(self.config) maintenance_greenlet = gevent.spawn(maintenance_worker.start) config_last_modified = os.stat(self.config_file).st_mtime while self.started: poll_last_modified = os.stat(self.config_file).st_mtime if poll_last_modified > config_last_modified: logger.debug('Config file changed, restarting maintenance workers.') config_last_modified = poll_last_modified config = self.get_config(self.config_file) #kill and stop old greenlet maintenance_worker.stop() maintenance_greenlet.kill(timeout=2) #spawn new worker greenlet and pass the new config maintenance_worker = Scheduler(config) maintenance_greenlet = gevent.spawn(maintenance_worker.start) #check config file for changes every 5 second gevent.sleep(5) @staticmethod def prepare_environment(work_dir, customize): package_directory = os.path.dirname(os.path.abspath(beeswarm.__file__)) config_file = os.path.join(work_dir, 'beeswarmcfg.json') if not os.path.isfile(config_file): print '*** Please answer a few configuration options ***' if customize: logging.info('Copying configuration file to workdir.') print '' print '* Certificate Information *' print 'IMPORTANT: Please make sure that "Common Name" is the IP address or fully qualified host name ' \ ' that you want to use for the server API.' cert_cn = raw_input('Common Name: ') cert_country = raw_input('Country: ') cert_state = raw_input('State: ') cert_locality = raw_input('Locality/City: ') cert_org = raw_input('Organization: ') cert_org_unit = raw_input('Organizational unit: ') print '' print '* Network *' web_port = raw_input('Port for UI (default: 5000): ') if web_port: web_port = int(web_port) else: web_port = 5000 else: logging.warn('Beeswarm server will be configured using default ssl parameters and network ' 'configuration, this could be used to fingerprint the beeswarm server. If you want to ' 'customize these options please use the --customize options on first startup.') cert_cn = '*' cert_country = 'US' cert_state = 'None' cert_locality = 'None' cert_org = 'None' cert_org_unit = '' web_port = 5000 cert, priv_key = create_self_signed_cert(cert_country, cert_state, cert_org, cert_locality, cert_org_unit, cert_cn) cert_path = os.path.join(work_dir, 'server.crt') key_path = os.path.join(work_dir, 'server.key') with open(cert_path, 'w') as certfile: certfile.write(cert) with open(key_path, 'w') as keyfile: keyfile.write(priv_key) shutil.copyfile(os.path.join(package_directory, 'server/beeswarmcfg.json.dist'), config_file) print '' print '* Communication between drones (honeypots and clients) and server *' print '* Please make sure that drones can always contact the Beeswarm server using the information that' \ ' you are about to enter. *' zmq_port = 5712 zmq_command_port = 5713 zmq_host = raw_input('IP or hostname of server: ') if customize: zmq_port = raw_input('TCP port for session data (default: 5712) : ') if zmq_port != '': zmq_port = int(zmq_port) zmq_command_port = raw_input('TCP port for drone commands(default: 5713) : ') if zmq_command_port != '': zmq_command_port = int(zmq_port) #tmp actor while initializing config_actor = ConfigActor('beeswarmcfg.json', work_dir) config_actor.start() context = zmq.Context() socket = context.socket(zmq.REQ) socket.connect('ipc://configCommands') socket.send('{0} {1}'.format(Messages.GEN_ZMQ_KEYS, 'beeswarm_server')) result = socket.recv() if result.split(' ', 1)[0] == Messages.OK: result = json.loads(result.split(' ', 1)[1]) zmq_public, zmq_private = (result['public_key'], result['private_key']) else: assert False socket.send('{0} {1}'.format(Messages.SET, json.dumps({'network': {'zmq_server_public_key': zmq_public, 'web_port': web_port, 'zmq_port': zmq_port, 'zmq_command_port': zmq_command_port, 'zmq_host': zmq_host}}))) socket.recv() config_actor.close()
class Server(object): def __init__(self, work_dir, config, **kwargs): """ Main class for the Web-Interface. It takes care of setting up the database, managing the users, etc. :param work_dir: The working directory (usually the current working directory). :param config_arg: Beeswarm configuration dictionary, None if not configuration was supplied. """ customize = kwargs['customize'] reset_password = kwargs['reset_password'] if 'clear_db' in kwargs: clear_sessions = kwargs['clear_db'] else: clear_sessions = True self.work_dir = work_dir self.config_file = 'beeswarmcfg.json' if config is None: Server.prepare_environment(work_dir, customize) with open(os.path.join(work_dir, self.config_file), 'r') as config_file: self.config = json.load(config_file, object_hook=asciify) else: self.config = config # list of all self-running (actor) objects that receive or send # messages on one or more zmq queues self.actors = [] gevent.spawn(self.message_proxy, work_dir) config_actor = ConfigActor(self.config_file, work_dir) config_actor.start() self.actors.append(config_actor) database_setup.setup_db( os.path.join(self.config['sql']['connection_string'])) persistanceActor = SessionPersister(clear_sessions) persistanceActor.start() self.actors.append(persistanceActor) gevent.sleep() self.workers = {} self.greenlets = [] self.started = False from beeswarm.server.webapp import app self.app = app.app self.app.config['CERT_PATH'] = self.config['ssl']['certpath'] self.authenticator = Authenticator() self.authenticator.ensure_default_user(reset_password) # distributes messages between external and internal receivers and senders def message_proxy(self, work_dir): """ drone_data_inboud is for data comming from drones drone_data_outbound is for commands to the drones, topic must either be a drone ID or all for sending a broadcast message to all drones """ public_keys_dir = os.path.join(work_dir, 'certificates', 'public_keys') secret_keys_dir = os.path.join(work_dir, 'certificates', 'private_keys') # start and configure auth worker auth = IOLoopAuthenticator() auth.start() auth.allow('127.0.0.1') auth.configure_curve(domain='*', location=public_keys_dir) # external interfaces for communicating with drones server_secret_file = os.path.join(secret_keys_dir, 'beeswarm_server.pri') server_public, server_secret = load_certificate(server_secret_file) drone_data_inbound = beeswarm.shared.zmq_context.socket(zmq.PULL) drone_data_inbound.curve_secretkey = server_secret drone_data_inbound.curve_publickey = server_public drone_data_inbound.curve_server = True drone_data_inbound.bind('tcp://*:{0}'.format( self.config['network']['zmq_port'])) drone_data_outbound = beeswarm.shared.zmq_context.socket(zmq.PUB) drone_data_outbound.curve_secretkey = server_secret drone_data_outbound.curve_publickey = server_public drone_data_outbound.curve_server = True drone_data_outbound.bind('tcp://*:{0}'.format( self.config['network']['zmq_command_port'])) # internal interfaces # all inbound session data from drones will be replayed in this socket sessionPublisher = beeswarm.shared.zmq_context.socket(zmq.PUB) sessionPublisher.bind('inproc://sessionPublisher') # all commands received on this will be published on the external interface drone_command_receiver = beeswarm.shared.zmq_context.socket(zmq.PULL) drone_command_receiver.bind('inproc://droneCommandReceiver') poller = zmq.Poller() poller.register(drone_data_inbound, zmq.POLLIN) poller.register(drone_command_receiver, zmq.POLLIN) while True: # .recv() gives no context switch - why not? using poller with timeout instead socks = dict(poller.poll(100)) gevent.sleep() if drone_command_receiver in socks and socks[ drone_command_receiver] == zmq.POLLIN: data = drone_command_receiver.recv() drone_id, _ = data.split(' ', 1) logger.debug("Sending drone command to: {0}".format(drone_id)) # pub socket takes care of filtering drone_data_outbound.send(data) elif drone_data_inbound in socks and socks[ drone_data_inbound] == zmq.POLLIN: split_data = drone_data_inbound.recv().split(' ', 2) if len(split_data) == 3: topic, drone_id, data = split_data else: data = None topic, drone_id, = split_data logger.debug("Received {0} message from {1}.".format( topic, drone_id)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter( Drone.id == drone_id).one() drone.last_activity = datetime.now() db_session.add(drone) db_session.commit() if topic == Messages.SESSION_HONEYPOT or topic == Messages.SESSION_CLIENT: sessionPublisher.send('{0} {1}'.format(topic, data)) elif topic == Messages.KEY or topic == Messages.CERT: # for now we just store the fingerprint # in the future it might be relevant to store the entire public key and private key # for forensic purposes if topic == Messages.CERT: cert = data.split(' ', 1)[1] digest = generate_cert_digest(cert) logging.debug( 'Storing public key digest: {0} for drone {1}.'. format(digest, drone_id)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter( Drone.id == drone_id).one() drone.cert_digest = digest db_session.add(drone) db_session.commit() elif topic == Messages.PING: pass elif topic == Messages.IP: ip_address = data logging.debug('Drone {0} reported ip: {1}'.format( drone_id, ip_address)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter( Drone.id == drone_id).one() if drone.ip_address != ip_address: drone.ip_address = ip_address db_session.add(drone) db_session.commit() send_zmq_request( 'inproc://configCommands', '{0} {1}'.format(Messages.DRONE_CONFIG_CHANGED, drone_id)) # drone want it's config transmitted elif topic == Messages.DRONE_CONFIG: config_dict = send_zmq_request( 'inproc://configCommands', '{0} {1}'.format(Messages.DRONE_CONFIG, drone_id)) drone_data_outbound.send('{0} {1} {2}'.format( drone_id, Messages.CONFIG, json.dumps(config_dict))) else: logger.warn( 'Message with unknown topic received: {0}'.format( topic)) def start(self, maintenance=True): """ Starts the BeeSwarm server. :param port: The port on which the web-app is to run. """ self.started = True web_port = self.config['network']['web_port'] logger.info('Starting server listening on port {0}'.format(web_port)) http_server = WSGIServer(('', web_port), self.app, keyfile='server.key', certfile='server.crt') http_server_greenlet = gevent.spawn(http_server.serve_forever) self.workers['http'] = http_server self.greenlets.append(http_server_greenlet) if maintenance: maintenance_greenlet = gevent.spawn(self.start_maintenance_tasks) self.workers['maintenance'] = maintenance_greenlet self.greenlets.append(maintenance_greenlet) stop_if_not_write_workdir(self.work_dir) logger.info('Server started.') gevent.joinall(self.greenlets) def stop(self): self.started = False logging.info('Stopping server.') self.workers['http'].stop(5) def get_config(self, configfile): """ Loads the configuration from the JSON file, and returns it. :param configfile: Path to the configuration file """ with open(configfile) as config_file: config = json.load(config_file) return config def start_maintenance_tasks(self): maintenance_worker = Scheduler(self.config) maintenance_greenlet = gevent.spawn(maintenance_worker.start) config_last_modified = os.stat(self.config_file).st_mtime while self.started: poll_last_modified = os.stat(self.config_file).st_mtime if poll_last_modified > config_last_modified: logger.debug( 'Config file changed, restarting maintenance workers.') config_last_modified = poll_last_modified config = self.get_config(self.config_file) # kill and stop old greenlet maintenance_worker.stop() maintenance_greenlet.kill(timeout=2) # spawn new worker greenlet and pass the new config maintenance_worker = Scheduler(config) maintenance_greenlet = gevent.spawn(maintenance_worker.start) # check config file for changes every 5 second gevent.sleep(5) @staticmethod def prepare_environment(work_dir, customize): package_directory = os.path.dirname(os.path.abspath(beeswarm.__file__)) config_file = os.path.join(work_dir, 'beeswarmcfg.json') if not os.path.isfile(config_file): print '*** Please answer a few configuration options ***' if customize: logging.info('Copying configuration file to workdir.') print '' print '* Certificate Information *' print 'IMPORTANT: Please make sure that "Common Name" is the IP address or fully qualified host name ' \ ' that you want to use for the server API.' cert_cn = raw_input('Common Name: ') cert_country = raw_input('Country: ') cert_state = raw_input('State: ') cert_locality = raw_input('Locality/City: ') cert_org = raw_input('Organization: ') cert_org_unit = raw_input('Organizational unit: ') print '' print '* Network *' web_port = raw_input('Port for UI (default: 5000): ') if web_port: web_port = int(web_port) else: web_port = 5000 else: logging.warn( 'Beeswarm server will be configured using default ssl parameters and network ' 'configuration, this could be used to fingerprint the beeswarm server. If you want to ' 'customize these options please use the --customize options on first startup.' ) cert_cn = '*' cert_country = 'US' cert_state = 'None' cert_locality = 'None' cert_org = 'None' cert_org_unit = '' web_port = 5000 cert, priv_key = create_self_signed_cert(cert_country, cert_state, cert_org, cert_locality, cert_org_unit, cert_cn) cert_path = os.path.join(work_dir, 'server.crt') key_path = os.path.join(work_dir, 'server.key') with open(cert_path, 'w') as certfile: certfile.write(cert) with open(key_path, 'w') as keyfile: keyfile.write(priv_key) print '' print '* Communication between drones (honeypots and clients) and server *' print '* Please make sure that drones can always contact the Beeswarm server using the information that' \ ' you are about to enter. *' zmq_port = 5712 zmq_command_port = 5713 server_hostname = raw_input('IP or hostname of server: ') if customize: zmq_port_input = raw_input( 'TCP port for session data (default: 5712) : ') if zmq_port_input != '': zmq_port = int(zmq_port) zmq_command_port_input = raw_input( 'TCP port for drone commands(default: 5713) : ') if zmq_command_port_input != '': zmq_command_port = int(zmq_port) # tmp actor while initializing config_actor = ConfigActor('beeswarmcfg.json', work_dir, True) config_actor.start() context = beeswarm.shared.zmq_context socket = context.socket(zmq.REQ) gevent.sleep() socket.connect('inproc://configCommands') socket.send('{0} {1}'.format(Messages.GEN_ZMQ_KEYS, 'beeswarm_server')) result = socket.recv() if result.split(' ', 1)[0] == Messages.OK: result = json.loads(result.split(' ', 1)[1]) zmq_public, zmq_private = (result['public_key'], result['private_key']) else: assert False socket.send('{0} {1}'.format( Messages.SET_CONFIG_ITEM, json.dumps({ 'network': { 'zmq_server_public_key': zmq_public, 'web_port': web_port, 'zmq_port': zmq_port, 'zmq_command_port': zmq_command_port, 'server_host': server_hostname }, 'sql': { 'connection_string': 'sqlite:///beeswarm_sqlite.db' }, 'ssl': { 'certpath': 'server.crt', 'keypath': 'server.key' }, 'general': { 'mode': 'server' }, 'bait_session_retain': 2, 'malicious_session_retain': 100, 'ignore_failed_bait_session': False }))) socket.recv() config_actor.close()
app = Flask(__name__) app.config['DEBUG'] = False app.config['WTF_CSRF_ENABLED'] = True app.config['SECRET_KEY'] = ''.join( random.choice(string.lowercase) for x in range(random.randint(16, 32))) app.jinja_env.filters['bootstrap_is_hidden_field'] = is_hidden_field_filter login_manager = LoginManager() login_manager.init_app(app) login_manager.login_view = 'login' logger = logging.getLogger(__name__) authenticator = Authenticator() first_cfg_received = gevent.event.Event() # keys used for adding new drones to the system drone_keys = [] context = beeswarm.shared.zmq_context config_actor_socket = context.socket(zmq.REQ) config_actor_socket.connect('inproc://configCommands') request_lock = gevent.lock.RLock() def send_config_request(request): global config_actor_socket request_lock.acquire() try:
class Server(object): def __init__(self, work_dir, config, **kwargs): """ Main class for the Web-Interface. It takes care of setting up the database, managing the users, etc. :param work_dir: The working directory (usually the current working directory). :param config_arg: Beeswarm configuration dictionary, None if not configuration was supplied. """ customize = kwargs['customize'] reset_password = kwargs['reset_password'] if 'clear_db' in kwargs: clear_sessions = kwargs['clear_db'] else: clear_sessions = True self.work_dir = work_dir self.config_file = 'beeswarmcfg.json' if config is None: Server.prepare_environment(work_dir, customize) with open(os.path.join(work_dir, self.config_file), 'r') as config_file: self.config = json.load(config_file, object_hook=asciify) else: self.config = config # list of all self-running (actor) objects that receive or send # messages on one or more zmq queues self.actors = [] gevent.spawn(self.message_proxy, work_dir) config_actor = ConfigActor(self.config_file, work_dir) config_actor.start() self.actors.append(config_actor) database_setup.setup_db(os.path.join(self.config['sql']['connection_string'])) persistanceActor = SessionPersister(clear_sessions) persistanceActor.start() self.actors.append(persistanceActor) gevent.sleep() self.workers = {} self.greenlets = [] self.started = False from beeswarm.server.webapp import app self.app = app.app self.app.config['CERT_PATH'] = self.config['ssl']['certpath'] self.authenticator = Authenticator() self.authenticator.ensure_default_user(reset_password) # distributes messages between external and internal receivers and senders def message_proxy(self, work_dir): """ drone_data_inboud is for data comming from drones drone_data_outbound is for commands to the drones, topic must either be a drone ID or all for sending a broadcast message to all drones """ public_keys_dir = os.path.join(work_dir, 'certificates', 'public_keys') secret_keys_dir = os.path.join(work_dir, 'certificates', 'private_keys') # start and configure auth worker auth = IOLoopAuthenticator() auth.start() auth.allow('127.0.0.1') auth.configure_curve(domain='*', location=public_keys_dir) # external interfaces for communicating with drones server_secret_file = os.path.join(secret_keys_dir, 'beeswarm_server.pri') server_public, server_secret = load_certificate(server_secret_file) drone_data_inbound = beeswarm.shared.zmq_context.socket(zmq.PULL) drone_data_inbound.curve_secretkey = server_secret drone_data_inbound.curve_publickey = server_public drone_data_inbound.curve_server = True drone_data_inbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_port'])) drone_data_outbound = beeswarm.shared.zmq_context.socket(zmq.PUB) drone_data_outbound.curve_secretkey = server_secret drone_data_outbound.curve_publickey = server_public drone_data_outbound.curve_server = True drone_data_outbound.bind('tcp://*:{0}'.format(self.config['network']['zmq_command_port'])) # internal interfaces # all inbound session data from drones will be replayed in this socket sessionPublisher = beeswarm.shared.zmq_context.socket(zmq.PUB) sessionPublisher.bind('inproc://sessionPublisher') # all commands received on this will be published on the external interface drone_command_receiver = beeswarm.shared.zmq_context.socket(zmq.PULL) drone_command_receiver.bind('inproc://droneCommandReceiver') poller = zmq.Poller() poller.register(drone_data_inbound, zmq.POLLIN) poller.register(drone_command_receiver, zmq.POLLIN) while True: # .recv() gives no context switch - why not? using poller with timeout instead socks = dict(poller.poll(100)) gevent.sleep() if drone_command_receiver in socks and socks[drone_command_receiver] == zmq.POLLIN: data = drone_command_receiver.recv() drone_id, _ = data.split(' ', 1) logger.debug("Sending drone command to: {0}".format(drone_id)) # pub socket takes care of filtering drone_data_outbound.send(data) elif drone_data_inbound in socks and socks[drone_data_inbound] == zmq.POLLIN: split_data = drone_data_inbound.recv().split(' ', 2) if len(split_data) == 3: topic, drone_id, data = split_data else: data = None topic, drone_id, = split_data logger.debug("Received {0} message from {1}.".format(topic, drone_id)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter(Drone.id == drone_id).one() drone.last_activity = datetime.now() db_session.add(drone) db_session.commit() if topic == Messages.SESSION_HONEYPOT or topic == Messages.SESSION_CLIENT: sessionPublisher.send('{0} {1}'.format(topic, data)) elif topic == Messages.KEY or topic == Messages.CERT: # for now we just store the fingerprint # in the future it might be relevant to store the entire public key and private key # for forensic purposes if topic == Messages.CERT: cert = data.split(' ', 1)[1] digest = generate_cert_digest(cert) logging.debug('Storing public key digest: {0} for drone {1}.'.format(digest, drone_id)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter(Drone.id == drone_id).one() drone.cert_digest = digest db_session.add(drone) db_session.commit() elif topic == Messages.PING: pass elif topic == Messages.IP: ip_address = data logging.debug('Drone {0} reported ip: {1}'.format(drone_id, ip_address)) db_session = database_setup.get_session() drone = db_session.query(Drone).filter(Drone.id == drone_id).one() if drone.ip_address != ip_address: drone.ip_address = ip_address db_session.add(drone) db_session.commit() send_zmq_request('inproc://configCommands', '{0} {1}'.format(Messages.DRONE_CONFIG_CHANGED, drone_id)) # drone want it's config transmitted elif topic == Messages.DRONE_CONFIG: config_dict = send_zmq_request('inproc://configCommands', '{0} {1}'.format(Messages.DRONE_CONFIG, drone_id)) drone_data_outbound.send('{0} {1} {2}'.format(drone_id, Messages.CONFIG, json.dumps(config_dict))) else: logger.warn('Message with unknown topic received: {0}'.format(topic)) def start(self, maintenance=True): """ Starts the BeeSwarm server. :param port: The port on which the web-app is to run. """ self.started = True web_port = self.config['network']['web_port'] logger.info('Starting server listening on port {0}'.format(web_port)) http_server = WSGIServer(('', web_port), self.app, keyfile='server.key', certfile='server.crt') http_server_greenlet = gevent.spawn(http_server.serve_forever) self.workers['http'] = http_server self.greenlets.append(http_server_greenlet) if maintenance: maintenance_greenlet = gevent.spawn(self.start_maintenance_tasks) self.workers['maintenance'] = maintenance_greenlet self.greenlets.append(maintenance_greenlet) stop_if_not_write_workdir(self.work_dir) logger.info('Server started.') gevent.joinall(self.greenlets) def stop(self): self.started = False logging.info('Stopping server.') self.workers['http'].stop(5) def get_config(self, configfile): """ Loads the configuration from the JSON file, and returns it. :param configfile: Path to the configuration file """ with open(configfile) as config_file: config = json.load(config_file) return config def start_maintenance_tasks(self): maintenance_worker = Scheduler(self.config) maintenance_greenlet = gevent.spawn(maintenance_worker.start) config_last_modified = os.stat(self.config_file).st_mtime while self.started: poll_last_modified = os.stat(self.config_file).st_mtime if poll_last_modified > config_last_modified: logger.debug('Config file changed, restarting maintenance workers.') config_last_modified = poll_last_modified config = self.get_config(self.config_file) # kill and stop old greenlet maintenance_worker.stop() maintenance_greenlet.kill(timeout=2) # spawn new worker greenlet and pass the new config maintenance_worker = Scheduler(config) maintenance_greenlet = gevent.spawn(maintenance_worker.start) # check config file for changes every 5 second gevent.sleep(5) @staticmethod def prepare_environment(work_dir, customize): package_directory = os.path.dirname(os.path.abspath(beeswarm.__file__)) config_file = os.path.join(work_dir, 'beeswarmcfg.json') if not os.path.isfile(config_file): print '*** Please answer a few configuration options ***' if customize: logging.info('Copying configuration file to workdir.') print '' print '* Certificate Information *' print 'IMPORTANT: Please make sure that "Common Name" is the IP address or fully qualified host name ' \ ' that you want to use for the server API.' cert_cn = raw_input('Common Name: ') cert_country = raw_input('Country: ') cert_state = raw_input('State: ') cert_locality = raw_input('Locality/City: ') cert_org = raw_input('Organization: ') cert_org_unit = raw_input('Organizational unit: ') print '' print '* Network *' web_port = raw_input('Port for UI (default: 5000): ') if web_port: web_port = int(web_port) else: web_port = 5000 else: logging.warn('Beeswarm server will be configured using default ssl parameters and network ' 'configuration, this could be used to fingerprint the beeswarm server. If you want to ' 'customize these options please use the --customize options on first startup.') cert_cn = '*' cert_country = 'US' cert_state = 'None' cert_locality = 'None' cert_org = 'None' cert_org_unit = '' web_port = 5000 cert, priv_key = create_self_signed_cert(cert_country, cert_state, cert_org, cert_locality, cert_org_unit, cert_cn) cert_path = os.path.join(work_dir, 'server.crt') key_path = os.path.join(work_dir, 'server.key') with open(cert_path, 'w') as certfile: certfile.write(cert) with open(key_path, 'w') as keyfile: keyfile.write(priv_key) print '' print '* Communication between drones (honeypots and clients) and server *' print '* Please make sure that drones can always contact the Beeswarm server using the information that' \ ' you are about to enter. *' zmq_port = 5712 zmq_command_port = 5713 server_hostname = raw_input('IP or hostname of server: ') if customize: zmq_port_input = raw_input('TCP port for session data (default: 5712) : ') if zmq_port_input != '': zmq_port = int(zmq_port) zmq_command_port_input = raw_input('TCP port for drone commands(default: 5713) : ') if zmq_command_port_input != '': zmq_command_port = int(zmq_port) # tmp actor while initializing config_actor = ConfigActor('beeswarmcfg.json', work_dir, True) config_actor.start() context = beeswarm.shared.zmq_context socket = context.socket(zmq.REQ) gevent.sleep() socket.connect('inproc://configCommands') socket.send('{0} {1}'.format(Messages.GEN_ZMQ_KEYS, 'beeswarm_server')) result = socket.recv() if result.split(' ', 1)[0] == Messages.OK: result = json.loads(result.split(' ', 1)[1]) zmq_public, zmq_private = (result['public_key'], result['private_key']) else: assert False socket.send('{0} {1}'.format(Messages.SET_CONFIG_ITEM, json.dumps({'network': {'zmq_server_public_key': zmq_public, 'web_port': web_port, 'zmq_port': zmq_port, 'zmq_command_port': zmq_command_port, 'server_host': server_hostname}, 'sql': { 'connection_string': 'sqlite:///beeswarm_sqlite.db'}, 'ssl': { 'certpath': 'server.crt', 'keypath': 'server.key' }, 'general': { 'mode': 'server' }, 'bait_session_retain': 2, 'malicious_session_retain': 100, 'ignore_failed_bait_session': False } ))) socket.recv() config_actor.close()
class WebappTests(unittest.TestCase): def setUp(self): app.app.config['WTF_CSRF_ENABLED'] = False self.work_dir = tempfile.mkdtemp() self.config_actor = ConfigActor( os.path.join(os.path.dirname(__file__), 'beeswarmcfg.json.test'), self.work_dir) self.config_actor.start() self.app = app.app.test_client() self.authenticator = Authenticator() database.setup_db('sqlite://') session = database.get_session() # dummy entities self.authenticator.add_user('test', 'test', 0) self.client_id = str(uuid.uuid4()) self.client_password = str(uuid.uuid4()) self.authenticator.add_user(self.client_id, self.client_password, 2) self.honeypot_id = str(uuid.uuid4()) self.honeypot_password = str(uuid.uuid4()) self.authenticator.add_user(self.honeypot_id, self.honeypot_password, 1) session.add_all([ Client(id=self.client_id, configuration='test_client_config'), Honeypot(id=self.honeypot_id, configuration='test_honeypot_config') ]) session.commit() def tearDown(self): database.clear_db() self.config_actor.close() shutil.rmtree(self.work_dir) # TODO: All these posts should be moved to ZMQ tests # def test_basic_client_post(self): # """ # Tests if a bait_session dict can be posted without exceptions. # """ # self.login(self.client_id, self.client_password) # data_dict = { # 'id': str(uuid.uuid4()), # 'client_id': self.client_id, # 'honeypot_id': self.honeypot_id, # 'protocol': 'pop3', # 'destination_ip': '127.0.0.1', # 'destination_port': '110', # 'source_ip': '123.123.123.123', # 'source_port': 12345, # 'timestamp': datetime.utcnow().isoformat(), # 'did_connect': True, # 'did_login': True, # 'did_complete': True, # 'protocol_data': None, # 'login_attempts': [{'id': str(uuid.uuid4()), 'username': '******', 'password': '******', 'successful': True, # 'timestamp': datetime.utcnow().isoformat()}] # } # r = self.app.post('/ws/client_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '200 OK') # # def test_basic_unsuccessful_client_post(self): # """ # Tests if an error is returned when data is posted without ID values. # """ # # self.login(self.client_id, self.client_password) # # #missing id's # data_dict = { # 'protocol': 'pop3', # 'username': '******', # 'password': '******', # 'server_host': '127.0.0.1', # 'server_port': '110', # 'source_ip': '123.123.123.123', # 'source_port': 12345, # 'timestamp': datetime.utcnow().isoformat(), # 'did_connect': True, # 'did_login': True, # 'did_complete': True, # 'protocol_data': None # } # # r = self.app.post('/ws/client_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '500 INTERNAL SERVER ERROR') # # def test_basic_honeypot_post(self): # """ # Tests if a session dict can be posted without exceptions. # """ # # self.login(self.honeypot_id, self.honeypot_password) # # data_dict = { # 'id': 'ba9fdb3d-0efb-4764-9a6b-d9b86eccda96', # 'honeypot_id': self.honeypot_id, # 'destination_ip': '192.168.1.1', # 'destination_port': 8023, # 'protocol': 'telnet', # 'source_ip': '127.0.0.1', # 'timestamp': '2013-05-07T22:21:19.453828', # 'source_port': 61175, # 'login_attempts': [ # {'username': '******', 'timestamp': '2013-05-07T22:21:20.846805', 'password': '******', # 'id': '027bd968-f8ea-4a69-8d4c-6cf21476ca10', 'successful': False}, # {'username': '******', 'timestamp': '2013-05-07T22:21:21.150571', 'password': '******', # 'id': '603f40a4-e7eb-442d-9fde-0cd3ba707af7', 'successful': False}, ], # 'transcript': [ # {'timestamp': '2013-05-07T22:21:20.846805', 'direction': 'in', 'data': 'whoami\r\n'}, # {'timestamp': '2013-05-07T22:21:21.136800', 'direction': 'out', 'data': 'james_brown\r\n$:~'}] # } # # r = self.app.post('/ws/honeypot_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '200 OK') # # def test_basic_unsuccessful_honeypot_post(self): # """ # Tests if an error is returned when data is posted without ID values. # """ # # self.login(self.honeypot_id, self.honeypot_password) # # #missing id # data_dict = { # 'honeypot_id': self.honeypot_id, # 'destination_ip': '192.168.1.1', # 'destination_port': 8023, # 'protocol': 'telnet', # 'source_ip': '127.0.0.1', # 'timestamp': '2013-05-07T22:21:19.453828', # 'source_port': 61175, # 'login_attempts': [ # {'username': '******', 'timestamp': '2013-05-07T22:21:20.846805', 'password': '******', # 'id': '027bd968-f8ea-4a69-8d4c-6cf21476ca10', 'successful': False}, # {'username': '******', 'timestamp': '2013-05-07T22:21:21.150571', 'password': '******', # 'id': '603f40a4-e7eb-442d-9fde-0cd3ba707af7', 'successful': False}, ], # 'transcript': [ # {'timestamp': '2013-05-07T22:21:20.846805', 'direction': 'in', 'data': 'whoami\r\n'}, # {'timestamp': '2013-05-07T22:21:21.136800', 'direction': 'out', 'data': 'james_brown\r\n$:~'} # ] # } # r = self.app.post('/ws/honeypot_data', data=json.dumps(data_dict), content_type='application/json') # self.assertEquals(r.status, '500 INTERNAL SERVER ERROR') # # def test_new_client(self): # """ # Tests if a new Client configuration can be posted successfully # """ # # post_data = { # 'http_enabled': True, # 'http_server': '127.0.0.1', # 'http_port': 80, # 'http_active_range': '13:40 - 16:30', # 'http_sleep_interval': 0, # 'http_activation_probability': 1, # 'http_login': '******', # 'http_password': '******', # # 'https_enabled': True, # 'https_server': '127.0.0.1', # 'https_port': 80, # 'https_active_range': '13:40 - 16:30', # 'https_sleep_interval': 0, # 'https_activation_probability': 1, # 'https_login': '******', # 'https_password': '******', # # 'pop3s_enabled': True, # 'pop3s_server': '127.0.0.1', # 'pop3s_port': 80, # 'pop3s_active_range': '13:40 - 16:30', # 'pop3s_sleep_interval': 0, # 'pop3s_activation_probability': 1, # 'pop3s_login': '******', # 'pop3s_password': '******', # # 'ssh_enabled': True, # 'ssh_server': '127.0.0.1', # 'ssh_port': 80, # 'ssh_active_range': '13:40 - 16:30', # 'ssh_sleep_interval': 0, # 'ssh_activation_probability': 1, # 'ssh_login': '******', # 'ssh_password': '******', # # 'ftp_enabled': True, # 'ftp_server': '127.0.0.1', # 'ftp_port': 80, # 'ftp_active_range': '13:40 - 16:30', # 'ftp_sleep_interval': 0, # 'ftp_activation_probability': 1, # 'ftp_login': '******', # 'ftp_password': '******', # # 'pop3_enabled': True, # 'pop3_server': '127.0.0.1', # 'pop3_port': 110, # 'pop3_active_range': '13:40 - 16:30', # 'pop3_sleep_interval': 0, # 'pop3_activation_probability': 1, # 'pop3_login': '******', # 'pop3_password': '******', # # 'smtp_enabled': True, # 'smtp_server': '127.0.0.1', # 'smtp_port': 25, # 'smtp_active_range': '13:40 - 16:30', # 'smtp_sleep_interval': 0, # 'smtp_activation_probability': 1, # 'smtp_login': '******', # 'smtp_password': '******', # # 'vnc_enabled': True, # 'vnc_server': '127.0.0.1', # 'vnc_port': 5900, # 'vnc_active_range': '13:40 - 16:30', # 'vnc_sleep_interval': 0, # 'vnc_activation_probability': 1, # 'vnc_login': '******', # 'vnc_password': '******', # # 'telnet_enabled': True, # 'telnet_server': '127.0.0.1', # 'telnet_port': 23, # 'telnet_active_range': '13:40 - 16:30', # 'telnet_sleep_interval': 0, # 'telnet_activation_probability': 1, # 'telnet_login': '******', # 'telnet_password': '******', # } # self.login('test', 'test') # resp = self.app.post('/ws/client', data=post_data) # self.assertTrue(200, resp.status_code) # self.logout() # # def test_new_honeypot(self): # """ # Tests whether new Honeypot configuration can be posted successfully. # """ # post_data = { # 'http_enabled': True, # 'http_port': 80, # 'http_banner': 'Microsoft-IIS/5.0', # # 'https_enabled': False, # 'https_port': 443, # 'https_banner': 'Microsoft-IIS/5.0', # # 'ftp_enabled': False, # 'ftp_port': 21, # 'ftp_max_attempts': 3, # 'ftp_banner': 'Microsoft FTP Server', # # 'smtp_enabled': False, # 'smtp_port': 25, # 'smtp_banner': 'Microsoft ESMTP MAIL service ready', # # 'vnc_enabled': False, # 'vnc_port': 5900, # # 'telnet_enabled': False, # 'telnet_port': 23, # 'telnet_max_attempts': 3, # # 'pop3_enabled': False, # 'pop3_port': 110, # 'pop3_max_attempts': 3, # # 'pop3s_enabled': False, # 'pop3s_port': 110, # 'pop3s_max_attempts': 3, # # 'ssh_enabled': False, # 'ssh_port': 22, # 'ssh_key': 'server.key' # } # self.login('test', 'test') # resp = self.app.post('/ws/honeypot', data=post_data) # self.assertTrue(200, resp.status_code) # self.logout() # # def test_new_honeypot_config(self): # """ Tests if a Honeypot config is being returned correctly """ # # resp = self.app.get('/ws/honeypot/config/' + self.honeypot_id) # self.assertEquals(resp.data, 'test_honeypot_config') # # def test_new_client_config(self): # """ Tests if a Client config is being returned correctly """ # # resp = self.app.get('/ws/client/config/' + self.client_id) # self.assertEquals(resp.data, 'test_client_config') def test_data_sessions_all(self): """ Tests if all sessions are returned properly""" self.login('test', 'test') self.populate_sessions() resp = self.app.get('/data/sessions/all') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 4) self.logout() def test_data_sessions_honeybees(self): """ Tests if bait_sessions are returned properly """ self.login('test', 'test') self.populate_honeybees() resp = self.app.get('/data/sessions/bait_sessions') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 3) self.logout() def test_data_sessions_attacks(self): """ Tests if attacks are returned properly """ self.login('test', 'test') self.populate_sessions() resp = self.app.get('/data/sessions/attacks') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 4) self.logout() def test_data_honeypot(self): """ Tests if Honeypot information is returned properly """ self.login('test', 'test') self.populate_honeypots() resp = self.app.get('/data/honeypots') table_data = json.loads(resp.data) self.assertEquals( len(table_data), 5) # One is added in the setup method, and 4 in populate def test_data_client(self): """ Tests if Client information is returned properly """ self.login('test', 'test') self.populate_clients() resp = self.app.get('/data/clients') table_data = json.loads(resp.data) self.assertEquals( len(table_data), 5) # One is added in the setup method, and 4 in populate def test_data_transcripts(self): """ Tests that if given a session ID we can extract the relevant transcripts""" db_session = database.get_session() self.login('test', 'test') session_id = str(uuid.uuid4()) timestamp = datetime.utcnow() db_session.add( Transcript(timestamp=timestamp, direction='outgoing', data='whoami', session_id=session_id)) db_session.add( Transcript(timestamp=timestamp, direction='outgoing', data='root\r\n', session_id=session_id)) db_session.commit() resp = self.app.get('/data/session/{0}/transcript'.format(session_id)) data = json.loads(resp.data) string_timestamp = timestamp.strftime('%Y-%m-%d %H:%M:%S') expected_result = [{ u'direction': u'outgoing', u'data': u'whoami', u'time': u'{0}'.format(string_timestamp) }, { u'direction': u'outgoing', u'data': u'root\r\n', u'time': u'{0}'.format(string_timestamp) }] self.assertDictEqual(sorted(data)[0], sorted(expected_result)[0]) def test_login_logout(self): """ Tests basic login/logout """ self.login('test', 'test') self.logout() # def test_honeypot_delete(self): # """ Tests the '/ws/honeypot/delete' route.""" # # self.login('test', 'test') # self.populate_honeypots() # data = [self.honeypots[0], self.honeypots[1]] # self.app.post('/ws/drone/delete', data=json.dumps(data)) # db_session = database.get_session() # honeypot_count = db_session.query(Honeypot).count() # self.assertEquals(3, honeypot_count) # gevent.sleep() # # def test_client_delete(self): # """ Tests the '/ws/client/delete' route.""" # # self.login('test', 'test') # self.populate_clients() # data = [self.clients[0], self.clients[1]] # print data # self.app.post('/ws/drone/delete', data=json.dumps(data)) # gevent.sleep(1) # db_session = database.get_session() # nclients = db_session.query(Client).count() # self.assertEquals(3, nclients) def test_get_baitusers(self): """ Tests GET on the '/ws/bait_users' route.""" self.login('test', 'test') self.populate_bait_users() resp = self.app.get('/ws/bait_users') table_data = json.loads(resp.data) self.assertEquals(len(table_data), 2) self.logout() def test_post_baitusers(self): """ Tests POST on the '/ws/bait_users' route.""" self.login('test', 'test') data = [{ 'username': '******', 'password': '******' }, { 'username': '******', 'password': '******' }, { 'username': '******', 'password': '******' }] self.app.post('/ws/bait_users', data=json.dumps(data), follow_redirects=True) db_session = database.get_session() bait_user_count = db_session.query(BaitUser).count() self.assertEquals(bait_user_count, 3) self.logout() def populate_clients(self): """ Populates the database with 4 clients """ db_session = database.get_session() self.clients = [] for i in xrange( 4): # We add 4 here, but one is added in the setup method curr_id = str(uuid.uuid4()) curr_id = curr_id.encode('utf-8') self.clients.append(curr_id) f = Client(id=curr_id) db_session.add(f) db_session.commit() def populate_honeypots(self): """ Populates the database with 4 honeypots """ db_session = database.get_session() self.honeypots = [] for i in xrange( 4): # We add 4 here, but one is added in the setup method curr_id = str(uuid.uuid4()) curr_id = curr_id.encode('utf-8') self.honeypots.append(curr_id) h = Honeypot(id=curr_id) db_session.add(h) db_session.commit() def populate_bait_users(self): """ Populates the database with 2 bait users """ db_session = database.get_session() db_session.query(BaitUser).delete() self.clients = [] for c in [('userA', 'passA'), ('userB', 'passB') ]: # We add 4 here, but one is added in the setup method bait_user = BaitUser(username=c[0], password=c[1]) db_session.add(bait_user) db_session.commit() def login(self, username, password): """ Logs into the web-app """ data = {'username': username, 'password': password} return self.app.post('/login', data=data, follow_redirects=True) def populate_honeybees(self): """ Populates the database with 3 Honeybees """ db_session = database.get_session() for i in xrange(3): h = BaitSession(id=str(uuid.uuid4()), timestamp=datetime.utcnow(), received=datetime.utcnow(), protocol='ssh', destination_ip='1.2.3.4', destination_port=1234, source_ip='4.3.2.1', source_port=4321, did_connect=True, did_login=False, did_complete=True) a = Authentication(id=str(uuid.uuid4()), username='******', password='******', successful=False, timestamp=datetime.utcnow()) h.authentication.append(a) db_session.add(h) db_session.commit() def populate_sessions(self): """ Populates the database with 3 Sessions """ db_session = database.get_session() for i in xrange(4): s = Session(id=str(uuid.uuid4()), timestamp=datetime.utcnow(), received=datetime.utcnow(), protocol='telnet', destination_ip='123.123.123.123', destination_port=1234, source_ip='12.12.12.12', source_port=12345, classification_id='asd') a = Authentication(id=str(uuid.uuid4()), username='******', password='******', successful=False, timestamp=datetime.utcnow()) s.authentication.append(a) db_session.add(s) db_session.commit() def logout(self): return self.app.get('/logout', follow_redirects=True)