Example #1
0
    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()
Example #2
0
    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)
Example #3
0
    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()
Example #4
0
    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()
Example #5
0
    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)
Example #6
0
    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()
Example #7
0
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)
Example #8
0
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)
Example #9
0
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()
Example #10
0
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()
Example #11
0

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:
Example #12
0
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()
Example #13
0
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)