def post(self, *args, **kwargs): ''' Clears cookies and session data ''' if self.session is not None: user = self.get_current_user() EventManager.instance().deauth(user) self.session.delete() self.clear_all_cookies() self.redirect("/")
def post(self, *args, **kwargs): """ Clears cookies and session data """ if self.session is not None: user = self.get_current_user() EventManager.instance().deauth(user) self.session.delete() self.clear_all_cookies() self.redirect("/")
def del_user(self): """ Delete user objects in the database, you cannot delete yourself. """ user = User.by_uuid(self.get_argument("uuid", "")) if user is not None and user != self.get_current_user(): logging.info("Deleted User: '******'" % str(user.handle)) EventManager.instance().deauth(user) self.dbsession.delete(user) self.dbsession.commit() self.event_manager.push_score_update() self.redirect("/admin/users") else: self.render("admin/view/users.html", errors=["User does not exist"])
def __init__(self): self.config = options self.dbsession = dbsession self.cache = MemcachedConnect() self.epoch = None # Date/time of first snapshot self._load() self.event_manager = EventManager.instance()
def score_bots(): ''' Award money for botnets ''' logging.info("Scoring botnets, please wait ...") bot_manager = BotManager.instance() event_manager = EventManager.instance() for team in Team.all(): if len(team.members) > 0: bots = bot_manager.by_team(team.name) if 0 < len(bots): reward = 0 for bot in bots: try: reward += options.bot_reward bot.write_message({ 'opcode': 'status', 'message': 'Collected $%d reward' % options.bot_reward }) except: logging.info( "Bot at %s failed to respond to score ping" % bot.remote_ip ) message = "%s was awarded $%d for controlling %s bot(s)" % ( team.name, reward, len(bots), ) bot_manager.add_rewards(team.name, options.bot_reward) bot_manager.notify_monitors(team.name) team.money += reward dbsession.add(team) dbsession.flush() event_manager.bot_scored(team, message) dbsession.commit()
def __init__(self): self.config = options self.dbsession = dbsession self.cache = memcache.Client([self.config.memcached], debug=0) self.epoch = None # Date/time of first snapshot self._load() self.event_manager = EventManager.instance()
def __init__(self): self.config = ConfigManager.instance() self.dbsession = dbsession self.cache = pylibmc.Client([self.config.memcached], binary=True) self.epoch = None # Date/time of first snapshot self._load() self.event_manager = EventManager.instance()
def initialize(self): """ Setup sessions, etc """ self.session = None self.manager = EventManager.instance() self.config = ConfigManager.instance() session_id = self.get_secure_cookie("session_id") if session_id is not None: self.conn = pylibmc.Client([self.config.memcached], binary=True) self.conn.behaviors["no_block"] = 1 # async I/O self.session = self._create_session(session_id) self.session.refresh()
def del_team(self): """ Delete team objects in the database. """ team = Team.by_uuid(self.get_argument("uuid", "")) for user in team.members: if user == self.get_current_user(): self.render( "admin/view/users.html", errors=["Unable to delete user %s" % user.handle], ) return EventManager.instance().deauth(user) if team is not None: logging.info("Deleted Team: '%s'" % str(team.name)) self.dbsession.delete(team) self.dbsession.commit() self.redirect("/admin/users") else: self.render("admin/view/users.html", errors=["Team does not exist"])
class AdminMessageHandler(BaseHandler): event_manager = EventManager.instance() """ Send a global notification message """ @restrict_ip_address @authenticated @authorized(ADMIN_PERMISSION) def post(self, *args, **kwargs): message = self.get_argument("message", "") if len(message) > 0: self.event_manager.admin_message(message) if self.chatsession: self.chatsession.post_message(message) self.redirect("/user")
def initialize(self): self.bot_manager = BotManager.instance() self.event_manager = EventManager.instance() self.config = ConfigManager.instance() self.team_name = None self.team_uuid = None self.box_uuid = None self.remote_ip = None self.xid = os.urandom(16).encode("hex") if not self.config.use_bots: self.close() else: self.uuid = unicode(uuid4()) self.opcodes = {"interrogation_response": self.interrogation_response}
class NotifySocketHandler(BaseWebSocketHandler): ''' Handles websocket connections ''' event_manager = EventManager.instance() def open(self): ''' When we receive a new websocket connect ''' self.event_manager.add_connection(self) if self.session is not None and 'team_id' in self.session: logging.debug("Opened new websocket with user id: %s" % ( self.session['user_id'], )) self.io_loop.add_callback(self.event_manager.push_user, self.team_id, self.user_id ) else: logging.debug("[Web Socket] Opened public notification socket.") def on_close(self): ''' Lost connection to client ''' self.event_manager.remove_connection(self) @property def team_id(self): if self.session is not None and 'team_id' in self.session: return self.session['team_id'] @team_id.setter def team_id(self, value): raise ValueError('Cannot set team_id') @property def user_id(self): if self.session is not None and 'user_id' in self.session: return self.session['user_id'] @user_id.setter def user_id(self, value): raise ValueError('Cannot set user_id')
class BaseHandler(RequestHandler): ''' User handlers extend this class ''' csp = { "default-src": set(["'self'"]), "script-src": set(["'self'"]), "connect-src": set(["'self'"]), "frame-src": set(["'self'"]), "img-src": set(["'self'"]), "media-src": set(["'none'"]), "font-src": set(["'self'"]), "object-src": set(["'none'"]), "style-src": set(["'self'"]), } _session = None dbsession = dbsession _memcached = None new_events = [] io_loop = IOLoop.instance() event_manager = EventManager.instance() config = options # backward compatability def initialize(self): ''' Setup sessions, etc ''' self.add_content_policy('connect-src', self.config.origin) # We need this for a few things, and so far as I know it doesn't # present too much of a security risk - TODO: no longer require # inline styles self.add_content_policy('style-src', "'unsafe-inline'") def get_current_user(self): ''' Get current user object from database ''' if self.session is not None: try: return User.by_uuid(self.session['user_uuid']) except KeyError: logging.exception("Malformed session: %r" % self.session) except: logging.exception("Failed call to get_current_user()") return None def start_session(self): ''' Starts a new session ''' self.session = self._create_session() flags = { 'expires': self.session.expires, 'path': '/', 'HttpOnly': True } if self.config.ssl: flags['Secure'] = True self.set_secure_cookie('session_id', self.session.session_id, **flags) def add_content_policy(self, src, policy): ''' Add to the existing CSP header ''' if not src.endswith('-src'): src += '-src' if src in self.csp: self.csp[src].add(policy) self._refresh_csp() else: raise ValueError("Invalid content source") def clear_content_policy(self, src): ''' Clear a content source in the existing CSP header ''' if not src.endswith('-src'): src += '-src' if src in self.csp: self.csp[src] = set() self._refresh_csp() else: raise ValueError("Invalid content source") def _refresh_csp(self): ''' Rebuild the Content-Security-Policy header ''' _csp = [] for src, policies in self.csp.iteritems(): if len(policies): _csp.append("%s %s; " % (src, " ".join(policies))) csp = ''.join(_csp) # Disabled until i can figure out the bug # self.set_header("Content-Security-Policy", csp) @property def memcached(self): ''' Connects to Memcached instance ''' if self._memcached is None: self._memcached = memcache.Client([self.config.memcached], debug=0) return self._memcached def _create_session(self): ''' Creates a new session ''' kwargs = { 'connection': self.memcached, 'ip_address': self.request.remote_ip, } new_session = MemcachedSession(**kwargs) new_session.save() return new_session @property def session(self): if self._session is None: session_id = self.get_secure_cookie('session_id') if session_id is not None: self._session = self._get_session(session_id) return self._session @session.setter def session(self, new_session): self._session = new_session def _get_session(self, session_id): kwargs = { 'connection': self.memcached, 'session_id': session_id, 'ip_address': self.request.remote_ip, } old_session = MemcachedSession.load(**kwargs) if old_session and not old_session.is_expired(): old_session.refresh() return old_session else: return None def set_default_headers(self): ''' Set security HTTP headers, and add some troll-y version headers ''' self.set_header("Server", "Microsoft-IIS/7.5") self.add_header("X-Powered-By", "ASP.NET") self.add_header("X-Frame-Options", "DENY") self.add_header("X-XSS-Protection", "1; mode=block") self.add_header("X-Content-Type-Options", "nosniff") self._refresh_csp() if self.config.ssl: self.add_header("Strict-Transport-Security", 'max-age=31536000; includeSubDomains;') def write_error(self, status_code, **kwargs): ''' Write our custom error pages ''' if not self.config.debug: trace = "".join(traceback.format_exception(*kwargs["exc_info"])) logging.error("Request from %s resulted in an error code %d:\n%s" % ( self.request.remote_ip, status_code, trace )) if status_code in [403]: # This should only get called when the _xsrf check fails, # all other '403' cases we just send a redirect to /403 self.render('public/403.html', locked=False, xsrf=True) else: # Never tell the user we got a 500 self.render('public/404.html') else: # If debug mode is enabled, just call Tornado's write_error() super(BaseHandler, self).write_error(status_code, **kwargs) def get(self, *args, **kwargs): ''' Placeholder, incase child class does not impl this method ''' self.render("public/404.html") def post(self, *args, **kwargs): ''' Placeholder, incase child class does not impl this method ''' self.render("public/404.html") def put(self, *args, **kwargs): ''' Log odd behavior, this should never get legitimately called ''' logging.warn( "%s attempted to use PUT method" % self.request.remote_ip ) def delete(self, *args, **kwargs): ''' Log odd behavior, this should never get legitimately called ''' logging.warn( "%s attempted to use DELETE method" % self.request.remote_ip ) def head(self, *args, **kwargs): ''' Ignore it ''' logging.warn( "%s attempted to use HEAD method" % self.request.remote_ip ) def options(self, *args, **kwargs): ''' Log odd behavior, this should never get legitimately called ''' logging.warn( "%s attempted to use OPTIONS method" % self.request.remote_ip ) def on_finish(self, *args, **kwargs): ''' Called after a response is sent to the client ''' self.dbsession.close()
def initialize(self): ''' Setup sessions ''' self.manager = EventManager.instance()
class BaseHandler(RequestHandler): """ User handlers extend this class """ csp = { "default-src": set(["'self'"]), "script-src": set(["'self'"]), "connect-src": set(["'self'"]), "frame-src": set(["'self'"]), "img-src": set(["'self'"]), "media-src": set(["'none'"]), "font-src": set(["'self'"]), "object-src": set(["'none'"]), "style-src": set(["'self'"]), } _session = None dbsession = dbsession chatsession = chatsession _memcached = None new_events = [] io_loop = IOLoop.instance() event_manager = EventManager.instance() config = options # backward compatability def initialize(self): """ Setup sessions, etc """ self.add_content_policy("connect-src", self.config.origin) # We need this for a few things, and so far as I know it doesn't # present too much of a security risk - TODO: no longer require # inline styles self.add_content_policy("style-src", "'unsafe-inline'") def get_current_user(self): """ Get current user object from database """ if self.session is not None: try: return User.by_uuid(self.session["user_uuid"]) except KeyError: logging.exception("Malformed session: %r" % self.session) except: logging.exception("Failed call to get_current_user()") return None def start_session(self): """ Starts a new session """ self.session = self._create_session() flags = { "expires": self.session.expires, "path": "/", "HttpOnly": True } if self.config.ssl: flags["Secure"] = True self.set_secure_cookie("session_id", self.session.session_id, **flags) def add_content_policy(self, src, policy): """ Add to the existing CSP header """ if not src.endswith("-src"): src += "-src" if src in self.csp: self.csp[src].add(policy) self._refresh_csp() else: raise ValueError("Invalid content source") def clear_content_policy(self, src): """ Clear a content source in the existing CSP header """ if not src.endswith("-src"): src += "-src" if src in self.csp: self.csp[src] = set() self._refresh_csp() else: raise ValueError("Invalid content source") def _refresh_csp(self): """ Rebuild the Content-Security-Policy header """ _csp = [] for src, policies in list(self.csp.items()): if len(policies): _csp.append("%s %s; " % (src, " ".join(policies))) csp = "".join(_csp) # Disabled until i can figure out the bug # self.set_header("Content-Security-Policy", csp) @property def memcached(self): """ Connects to Memcached instance """ if self._memcached is None: self._memcached = MemcachedConnect() return self._memcached def _create_session(self): """ Creates a new session """ kwargs = { "connection": self.memcached, "ip_address": self.request.remote_ip } new_session = MemcachedSession(**kwargs) new_session.save() return new_session def flush_memcached(self): if self._memcached is not None: self._memcached.flush_all() @property def session(self): if self._session is None: session_id = self.get_secure_cookie("session_id") if session_id is not None: self._session = self._get_session(session_id) return self._session @session.setter def session(self, new_session): self._session = new_session def _get_session(self, session_id): kwargs = { "connection": self.memcached, "session_id": session_id, "ip_address": self.request.remote_ip, } old_session = MemcachedSession.load(**kwargs) if old_session and not old_session.is_expired(): old_session.refresh() return old_session else: return None def set_default_headers(self): """ Set security HTTP headers, and add some troll-y version headers """ self.set_header("Server", "Microsoft-IIS/7.5") self.add_header("X-Powered-By", "ASP.NET") self.add_header("X-Frame-Options", "DENY") self.add_header("X-XSS-Protection", "1; mode=block") self.add_header("X-Content-Type-Options", "nosniff") self._refresh_csp() if self.config.ssl: self.add_header("Strict-Transport-Security", "max-age=31536000; includeSubDomains;") def write_error(self, status_code, **kwargs): """ Write our custom error pages """ trace = "".join(traceback.format_exception(*kwargs["exc_info"])) logging.error("Request from %s resulted in an error code %d:\n%s" % (self.request.remote_ip, status_code, trace)) if status_code in [403]: # This should only get called when the _xsrf check fails, # all other '403' cases we just send a redirect to /403 # self.render('public/403.html', locked=False, xsrf=True) self.redirect("/logout") # just log them out else: if not self.config.debug: # Never tell the user we got a 500 self.render("public/404.html") else: # If debug mode is enabled, just call Tornado's write_error() super(BaseHandler, self).write_error(status_code, **kwargs) def get(self, *args, **kwargs): """ Placeholder, incase child class does not impl this method """ self.render("public/404.html") def post(self, *args, **kwargs): """ Placeholder, incase child class does not impl this method """ self.render("public/404.html") def put(self, *args, **kwargs): """ Log odd behavior, this should never get legitimately called """ logging.warn("%s attempted to use PUT method" % self.request.remote_ip) def delete(self, *args, **kwargs): """ Log odd behavior, this should never get legitimately called """ logging.warn("%s attempted to use DELETE method" % self.request.remote_ip) def head(self, *args, **kwargs): """ Ignore it """ logging.warn("%s attempted to use HEAD method" % self.request.remote_ip) def options(self, *args, **kwargs): """ Log odd behavior, this should never get legitimately called """ logging.warn("%s attempted to use OPTIONS method" % self.request.remote_ip) def on_finish(self, *args, **kwargs): """ Called after a response is sent to the client """ self.dbsession.close() def timer(self): timer = None if self.application.settings["freeze_scoreboard"]: timerdiff = self.application.settings[ "freeze_scoreboard"] - time.time() if timerdiff <= 0: timerdiff = 0 if self.application.settings["stop_timer"]: self.application.settings["stop_timer"] = False self.stop_game() timer = str(timerdiff) return timer def start_game(self): """ Start the game and any related callbacks """ if not self.application.settings["game_started"]: logging.info("The game is about to begin, good hunting!") self.application.settings["game_started"] = True self.application.settings["history_callback"].start() if self.config.use_bots: self.application.settings["score_bots_callback"].start() # Fire game start webhook send_game_start_webhook() def stop_game(self): """ Stop the game and all callbacks """ if self.application.settings["game_started"]: logging.info("The game is stopping ...") self.application.settings["game_started"] = False if self.application.settings["history_callback"]._running: self.application.settings["history_callback"].stop() if self.application.settings["score_bots_callback"]._running: self.application.settings["score_bots_callback"].stop() # Fire game stop webhook send_game_stop_webhook() def get_user_locale(self): """ Get user lang value from config. If None is returned, Tornado fall back to get_browser_locale() """ if len(self.config.force_locale) > 0: return locale.get(self.config.force_locale) else: """ This is a work around as Tornado get_browser_locale() is not returning the closest match. https://github.com/tornadoweb/tornado/issues/1858 https://github.com/moloch--/RootTheBox/issues/367 """ codes = self.request.headers.get("Accept-Language") if codes: for code in codes.split(","): code = code.split(";")[0] for l in locale.get_supported_locales(): if code.lower() == l.split("_")[0]: return locale.get(l) return None
class BaseWebSocketHandler(WebSocketHandler): """ Handles websocket connections """ _session = None _memcached = None io_loop = IOLoop.instance() manager = EventManager.instance() config = options # backward compatability def check_origin(self, origin): """ Parses the request's origin header """ try: request_origin = urlparse(origin) origin = urlparse(self.config.origin) logging.debug("Checking request origin '%s' ends with '%s'" % (request_origin, origin)) return request_origin.netloc.endswith(origin) except: logging.exception("Failed to parse request origin: %r" % origin) return False @property def memcached(self): """ Connects to Memcached instance """ if self._memcached is None: self._memcached = MemcachedConnect() return self._memcached @property def session(self): if self._session is None: session_id = self.get_secure_cookie("session_id") if session_id is not None: self._session = self._get_session(session_id) return self._session @session.setter def session(self, new_session): self._session = new_session def _get_session(self, session_id): kwargs = { "connection": self.memcached, "session_id": session_id, "ip_address": self.request.remote_ip, } old_session = MemcachedSession.load(**kwargs) if old_session and not old_session.is_expired(): old_session.refresh() return old_session else: return None def get_current_user(self): """ Get current user object from database """ if self.session is not None: try: return User.by_handle(self.session["handle"]) except KeyError: logging.exception("Malformed session: %r" % self.session) except: logging.exception("Failed call to get_current_user()") return None def open(self): pass def on_message(self, message): pass def on_close(self): pass
def initialize(self): ''' Setup sessions ''' self.manager = EventManager.instance() self.game_history = GameHistory.instance()
class BaseWebSocketHandler(WebSocketHandler): ''' Handles websocket connections ''' _session = None _memcached = None io_loop = IOLoop.instance() manager = EventManager.instance() config = ConfigManager.instance() @property def memcached(self): ''' Connects to Memcached instance ''' if self._memcached is None: self._memcached = pylibmc.Client([self.config.memcached], binary=True) self._memcached.behaviors['no_block'] = 1 # async I/O return self._memcached @property def session(self): if self._session is None: session_id = self.get_secure_cookie('session_id') if session_id is not None: self._session = self._get_session(session_id) return self._session @session.setter def session(self, new_session): self._session = new_session def _get_session(self, session_id): kwargs = { 'connection': self.memcached, 'session_id': session_id, 'ip_address': self.request.remote_ip, } old_session = MemcachedSession.load(**kwargs) if old_session and not old_session.is_expired(): old_session.refresh() return old_session else: return None def get_current_user(self): ''' Get current user object from database ''' if self.session is not None: try: return User.by_handle(self.session['handle']) except KeyError: logging.exception( "Malformed session: %r" % self.session ) except: logging.exception("Failed call to get_current_user()") return None def open(self): pass def on_message(self, message): pass def on_close(self): pass
class BotSocketHandler(tornado.websocket.WebSocketHandler): ''' *** Rough bot protocol layout *** ================================= 1) Bot connects to server a) If IP config.whitelist_box_ips is enabled, check the datbase for boxes with matching IPs 2) Server responds with "Interrogation" request a) This request includes a random string 'xid' 3) Bot responds with a "InterrogationResponse", includes a) The value of SHA512(SHA512(xid + garbage)) b) Asserted user handle (reward goes to user.team) c) Asserted box name 4) Server looks up asserted box and user in database, ensures they do exist, and the user is not an admin. 5) Server then computes it's own SHA512(SHA512(xid + box garbage)) a) Check if the server's value matches the bot's 6) Check for duplicate bots (one bot per box per team) 7) Add new bot to botnet ''' bot_manager = BotManager.instance() event_manager = EventManager.instance() config = options team_name = None team_uuid = None box_uuid = None remote_ip = None def initialize(self): self.xid = os.urandom(16).encode('hex') if not self.config.use_bots: self.close() else: self.uuid = unicode(uuid4()) self.opcodes = { 'interrogation_response': self.interrogation_response, } def open(self, *args): ''' Steps 1 and 2; called when a new bot connects ''' box = Box.by_ip_address(self.request.remote_ip) self.remote_ip = self.request.remote_ip if box is None and self.config.whitelist_box_ips: logging.debug("Rejected bot from '%s' (not a box)" % self.request.remote_ip) self.write_message({ 'opcode': 'error', 'message': 'Invalid IP address.' }) self.close() else: logging.debug("Interrogating bot on %s" % self.request.remote_ip) self.write_message({'opcode': 'interrogate', 'xid': self.xid}) def on_message(self, message): ''' Routes the request to the correct function based on opcode ''' try: req = json.loads(message) if 'opcode' not in req: raise ValueError('Missing opcode') elif req['opcode'] not in self.opcodes: raise ValueError('Invalid opcode in request: %s' % req['opcode']) else: self.opcodes[req['opcode']](req) except ValueError as error: logging.warn("Invalid json request from bot: %s" % str(error)) self.close() def on_close(self): ''' Close connection to remote host ''' if self.uuid in self.bot_manager.botnet: self.bot_manager.remove_bot(self) logging.debug("Closing connection to bot at %s" % self.request.remote_ip) def interrogation_response(self, msg): ''' Steps 3 and 4; validate repsonses ''' logging.debug("Recieved interrogate response, validating ...") response_xid = msg['rxid'] user = User.by_handle(msg['handle']) box = Box.by_name(msg['box_name']) if self.config.whitelist_box_ips and self.remote_ip not in box.ips: self.send_error("Invalid remote IP for this box") elif user is None or user.has_permission(ADMIN_PERMISSION): self.send_error("User does not exist") elif box is None: self.send_error("Box does not exist") elif not self.is_valid_xid(box, response_xid): self.send_error("Invalid xid response") else: self.team_name = user.team.name self.team_uuid = user.team.uuid self.box_uuid = box.uuid self.box_name = box.name self.add_to_botnet() def add_to_botnet(self): ''' Step 6 and 7; Add current web socket to botnet ''' if self.bot_manager.add_bot(self): logging.debug("Auth okay, adding '%s' to botnet" % self.uuid) count = self.bot_manager.count_by_team(self.team_name) self.write_message({ 'opcode': 'status', 'message': 'Added new bot; total number of bots is now %d' % count }) else: logging.debug("Duplicate bot on %s" % self.remote_ip) self.send_error("Duplicate bot") def is_valid_xid(self, box, response_xid): return response_xid == sha1(self.xid + box.garbage).hexdigest() def ping(self): ''' Just make sure we can write data to the socket ''' try: self.write_message({'opcode': 'ping'}) except: logging.exception("Error: while sending ping to bot.") self.close() def send_error(self, msg): ''' Send the errors, and close socket ''' self.write_message({ 'opcode': 'error', 'message': msg, }) self.close()
port=config.listen_port, # Anti-bruteforce automatic_ban=False, blacklist_threshold=10, blacklisted_ips=[], failed_logins={}, # Special file directories source_code_market_dir=path.abspath('files/source_code_market/'), # Notifier WebSocket ws_connect=config.ws_connect, # Event manager event_manager=EventManager.instance(), # Debug mode debug=config.debug, # Flag used to start the game game_started=False, # Callback functions score_bots_callback = PeriodicCallback( score_bots, config.bot_reward_interval, io_loop=io_loop ), history_callback = PeriodicCallback(
class BotSocketHandler(tornado.websocket.WebSocketHandler): """ *** Rough bot protocol layout *** ================================= 1) Bot connects to server a) If IP config.whitelist_box_ips is enabled, check the datbase for boxes with matching IPs 2) Server responds with "Interrogation" request a) This request includes a random string 'xid' 3) Bot responds with a "InterrogationResponse", includes a) The value of SHA512(SHA512(xid + garbage)) b) Asserted user handle (reward goes to user.team) c) Asserted box name 4) Server looks up asserted box and user in database, ensures they do exist, and the user is not an admin. 5) Server then computes it's own SHA512(SHA512(xid + box garbage)) a) Check if the server's value matches the bot's 6) Check for duplicate bots (one bot per box per team) 7) Add new bot to botnet """ bot_manager = BotManager.instance() event_manager = EventManager.instance() config = options team_name = None team_uuid = None box_uuid = None remote_ip = None def initialize(self): try: hex_random = os.urandom(16).hex() except AttributeError: hex_random = encode(os.urandom(16), "hex") self.xid = hex_random if not self.config.use_bots: self.close() else: self.uuid = str(uuid4()) self.opcodes = { "interrogation_response": self.interrogation_response } def open(self, *args): """ Steps 1 and 2; called when a new bot connects """ box = Box.by_ip_address(self.request.remote_ip) self.remote_ip = self.request.remote_ip if box is None and self.config.whitelist_box_ips: logging.debug("Rejected bot from '%s' (not a box)" % self.request.remote_ip) self.write_message({ "opcode": "error", "message": "Invalid IP address." }) self.close() else: logging.debug("Interrogating bot on %s" % self.request.remote_ip) self.write_message({"opcode": "interrogate", "xid": str(self.xid)}) def on_message(self, message): """ Routes the request to the correct function based on opcode """ try: req = json.loads(message) if "opcode" not in req: raise ValueError("Missing opcode") elif req["opcode"] not in self.opcodes: raise ValueError("Invalid opcode in request: %s" % req["opcode"]) else: self.opcodes[req["opcode"]](req) except ValueError as error: logging.warning("Invalid json request from bot: %s" % str(error)) self.close() def on_close(self): """ Close connection to remote host """ if self.uuid in self.bot_manager.botnet: self.bot_manager.remove_bot(self) logging.debug("Closing connection to bot at %s" % self.request.remote_ip) def interrogation_response(self, msg): """ Steps 3 and 4; validate repsonses """ logging.debug("Received interrogate response, validating ...") response_xid = msg["response_xid"] user = User.by_handle(msg["handle"]) box = Box.by_name(msg["box_name"]) if self.config.whitelist_box_ips and self.remote_ip not in box.ips: self.send_error("Invalid remote IP for this box") elif user is None or user.is_admin(): self.send_error("User does not exist") elif box is None: self.send_error("Box does not exist") elif not self.is_valid_xid(box, response_xid): self.send_error("Invalid xid response") else: self.team_name = user.team.name self.team_uuid = user.team.uuid self.box_uuid = box.uuid self.box_name = box.name self.add_to_botnet(user) def add_to_botnet(self, user): """ Step 6 and 7; Add current web socket to botnet """ if self.bot_manager.add_bot(self): logging.debug("Auth okay, adding '%s' to botnet" % self.uuid) count = self.bot_manager.count_by_team(self.team_name) self.write_message({ "opcode": "status", "message": "Added new bot; total number of bots is now %d" % count, }) self.event_manager.bot_added(user, count) else: logging.debug("Duplicate bot on %s" % self.remote_ip) self.send_error("Duplicate bot") def is_valid_xid(self, box, response_xid): round1 = encode(sha512(encode(self.xid + box.garbage)).hexdigest()) return response_xid == sha512(round1).hexdigest() def ping(self): """ Just make sure we can write data to the socket """ try: self.write_message({"opcode": "ping"}) except: logging.exception("Error: while sending ping to bot.") self.close() def send_error(self, msg): """ Send the errors, and close socket """ self.write_message({"opcode": "error", "message": msg}) self.close()