def get_scripts_to_run(tick_id=None): """ The ``/scripts/get/torun`` endpoint requires authentication. By default, it returns all scripts that needs to be run for current tick. It can be reached at ``/scripts/get/torun?secret=<API_SECRET>``. The JSON response looks like:: { "scripts_to_run" : [List of { "id" : int, "script_id" : int, "against_team_id": int, "tick_id" : int}] } :return: a JSON dictionary that contains all script that needs to be run. """ cursor = mysql.cursor() # Get basic information about the game, like tick info and services if tick_id is None: current_tick, tick_start, seconds_to_next_tick, _ = get_current_tick(cursor) tick_id = current_tick cursor.execute("""SELECT id, script_id, against_team_id, tick_id FROM script_runs WHERE error is NULL AND tick_id = %s""", (tick_id,)) return json.dumps({"scripts_to_run": cursor.fetchall()})
def get_services_state(): if request.args.get('secret') != API_SECRET: abort(401) current_tick, time_left = get_current_tick() return jsonify(get_service_state_by_tick(current_tick))
def services_get_status(team_id=0): """The ``/services/status/team`` endpoint requires authentication and takes an ``team_id`` argument. It fetches the services' status for the specified team for the current tick . The JSON response looks like:: { team_id: { service_id: { "service_name": string, "service_state": ("up", "notfunctional", "down", "untested") } } } :param int team_id: optional tick_id :return: a JSON dictionary that maps teams to service states. """ cursor = mysql.cursor() tick_id, _, _, _ = get_current_tick(cursor) if team_id == 0: team_id = "ts.team_id" # now get the real results cursor.execute( """SELECT ts.service_id, ts.tick_id, ts.team_id, ts.state, svcs.port, svcs.name FROM curr_team_service_state ts, services svcs WHERE (ts.tick_id = %s OR ts.tick_id = %s) AND ts.team_id=%s AND svcs.id = ts.service_id ORDER BY ts.tick_id, svcs.port""", (tick_id, (tick_id - 1), team_id)) # build the result dict out = dict() for result in cursor.fetchall(): nm = result["name"][slice(-10, None)] name_val = "{:10}".format(nm) + " status:{:4}".format( result["state"]) + " Port:" + str(result["port"]) if result["tick_id"] not in out: out[result["tick_id"]] = list() out[result["tick_id"]].append(name_val) return json.dumps(out)
def service_enable_disable(service_id): """The ``/service/enable`` and ``/service/disable`` endpoints requires authentication and expect no additional arguments. It enables or disables a service starting at the next tick. Note that this endpoint requires a POST request. It can be reached at ``/service/enable/<service_id>?secret=<API_SECRET>`` or ``/service/disable/<service_id>?secret=<API_SECRET>``. It expects the following inputs: - reason, the reason why the service was enabled (optional) The optional ``reason`` argument is any string and justifies why the the service was enabled or disabled. The JSON response is:: { "id": int, "result": ("success", "failure") } :param int service_id: the id of the service to be enabled or disabled. :return: a JSON dictionary with a result status, to verify if setting the state was successful. """ state = "enabled" if "enable" in request.path else "disabled" reason = request.form.get("reason", None) cursor = mysql.cursor() tick_id, _, _, _ = get_current_tick(cursor) cursor.execute( """UPDATE services set current_state = %s WHERE id = %s""", (state, service_id)) # update the service_state log cursor.execute( """INSERT INTO service_state (service_id, state, reason, tick_id) VALUES (%s, %s, %s, %s)""", (service_id, state, reason, tick_id)) mysql.database.commit() return json.dumps({"id": cursor.lastrowid, "result": "success"})
def services_get_history_status(history_cnt=10): """The ``/services/history/statius`` endpoint requires authentication. It fetches the services' status for the last 10 ticks and summarizes them in a json object. The JSON response looks like:: { service_info: { team_id: [ status1, status2, status3 ... ] } } :return: a JSON dictionary that maps teams to service states. """ cursor = mysql.cursor() tick_id, _, _, _ = get_current_tick(cursor) lower_tick_id = tick_id - history_cnt print("LOWER TICK ID {}".format(lower_tick_id)) sql = """ SELECT ts.service_id, ts.tick_id, ts.team_id, ts.state, svcs.port, svcs.name FROM curr_team_service_state ts, services svcs WHERE (ts.tick_id >= %s ) AND svcs.id = ts.service_id ORDER BY svcs.port, ts.tick_id""" print(sql % (lower_tick_id)) # now get the real results cursor.execute(sql, (lower_tick_id, )) # build the result dict out = dict() for result in cursor.fetchall(): nm = "{}:{}".format(result["name"], result["port"]) nm = nm[slice(-15, None)] if nm not in out: out[nm] = dict() team_id = "team{:5}".format(result["team_id"]) if team_id not in out[nm]: out[nm][team_id] = "" if result["state"] == "up": out[nm][team_id] += "____ " else: status = result["state"][slice(-4, None)] out[nm][team_id] += "{:4} ".format(status) return json.dumps(out)
def game_get_info(): """The ``/game/info`` endpoint requires authentication and expects no other arguments. It can be reached at ``/game/info?secret=<API_SECRET>``. It is used to retrieve the information about the game, like team and service information. The JSON response looks like:: { "services": [List of {"service_id": int, "service_name": string, "port": int, "flag_id_description": string, "description": string, "state": ("enabled", "disabled")}], "teams": [List of {"team_id": int, "team_name": string, "url": string, "country": 2-digit country code according to ISO-3166-1, ZZ for unknown}] } :return: a JSON dictionary with a list of all teams and a list of all services, including auxiliary information. """ cursor = mysql.cursor() cursor.execute("""SELECT id as team_id, name as team_name, url, country FROM teams""") teams = cursor.fetchall() _, tick_start, _, _ = get_current_tick(cursor) cursor.execute("""SELECT id as service_id, name as service_name, port, flag_id_description, description, current_state as state FROM services""") services = cursor.fetchall() return json.dumps({"teams": teams, "services": services})
def current_tick_num(): """The ``/game/tick/`` endpoint requires authentication and expects no other arguments. It can be reached at ``/game/tick?secret=<API_SECRET>`` or at ``/game/tick?secret=<API_SECRET>``. It is used to retrieve the information about the current tick. The JSON response looks like:: {"created_on": "2015-11-30 17:01:42", "approximate_seconds_left": 0, "tick_id": 47} :return: a JSON dictionary with information about the current tick. """ cursor = mysql.cursor() tick_id, created_on, seconds_left, ends_on = get_current_tick(cursor) return json.dumps({ "tick_id": tick_id, "created_on": str(created_on), "approximate_seconds_left": seconds_left, "ends_on": str(ends_on) })
def current_state(): """ state_id is :return: """ if request.args.get('secret') != API_SECRET: abort(401) # from sqlalchemy.ext.serializer import loads, dumps event = Event.query.order_by(Event.id.desc()).first() current_tick, seconds_to_next_tick = get_current_tick() # is 0,1337 when there is no tick for current event yet # is None, 1337 ?? if current_tick is None: print("Warning: There is no current tick. Game not started yet?") return jsonify({ 'state_id': 0, 'state_expire': 1337, 'game_id': 0, }) result = { 'state_id': current_tick.id, 'state_expire': seconds_to_next_tick, 'game_id': event.id, # 'services': [object_as_dict(u) for u in ], # 'scripts': list(Script.query.filter_by(event=event)), # 'run_scripts': [{'team_id': tsrs.attending_team.id, 'run_list': json.loads(tsrs.json_list_of_scripts_to_run) } for tsrs in TeamScriptsRunStatus.query.filter_by(tick=current_tick)] } services = [] for service in Challenge.query.filter_by(type="ad", event=event): services.append({ 'service_id': service.id, 'service_name': service.name, # ??? TODO # 'type': str(service.type), # NEW 'port': service.port, }) result['services'] = services scripts = [] for script in Script.query.filter_by(event=event): scripts.append({ 'script_id': script.id, 'is_bundle': 0, 'type': str(script.type).split(".")[-1], 'script_name': script.name, 'service_id': script.challenge.id, }) result['scripts'] = scripts run_scripts = [] for run_script in TeamScriptsRunStatus.query.filter_by(tick=current_tick): run_scripts.append({ 'team_id': run_script.attending_team.id, 'run_list': json.loads(run_script.json_list_of_scripts_to_run), }) result['run_scripts'] = run_scripts return jsonify(result)
def script_new(): """The ``/script/new`` endpoint requires authentication. It add a script to the database, and initializes its state. Note that this endpoint requires a POST request. It can be reached at ``/script/new?secret=<API_SECRET>``. It requires the following inputs: - name, an optional name of the script. - upload_id, upload which has the payload. - filename, the name of the file - type, the type of the script, currently exploit, benign, getflag, or setflag. - state, enabled, disabled. Defaults to "enabled" - service_id The JSON response looks like:: { "id" : int, "result": ("success", "failure") } :return: a JSON dictionary containing status information. """ upload_id = request.form.get("upload_id") filename = request.form.get("filename") type_ = request.form.get("type") state = request.form.get("state", "enabled") service_id = request.form.get("service_id") if state not in ("enabled", "disabled"): abort(400) cursor = mysql.cursor() # get the team_id from the uploads cursor.execute("""SELECT team_id FROM uploads WHERE id = %s LIMIT 1""", (upload_id,)) result = cursor.fetchone() team_id = result["team_id"] # add the script cursor.execute("""INSERT INTO scripts (type, team_id, service_id, upload_id, filename, current_state) VALUES (%s, %s, %s, %s, %s, %s)""", (type_, team_id, service_id, upload_id, filename, state)) script_id = cursor.lastrowid # set it in the script state log tick_id, _, _, _ = get_current_tick(cursor) cursor.execute("""INSERT INTO script_state (script_id, state, reason, tick_id) VALUES (%s, %s, %s, %s)""", (script_id, state, "initial state", tick_id)) mysql.database.commit() return json.dumps({"result": "success", "id": script_id})
def game_get_state(): """The ``/game/state`` endpoint requires authentication and expects no other arguments. It can be reached at ``/game/state?secret=<API_SECRET>``. It is used to retrieve the current state of the game. The JSON response looks like:: { "state_id": int, "game_id": int, "services": [List of {"service_id": int, "service_name": string, "port": int}], "scripts": [List of {"script_id": int, "upload_id": int, "type": ("exploit", "benign", "getflag", "setflag"), "script_name": string, "service_id": int}] "run_scripts": [{"team_id": int (team_id to run scripts against), "run_list": [Ordered list of int script_ids]}], "state_expire": int (approximate remaining seconds in this tick), } :return: a JSON dictionary providing information on the current state. """ cursor = mysql.cursor() # Get basic information about the game, like tick info and services to_return = {} current_tick, tick_start, seconds_to_next_tick, _ = get_current_tick( cursor) to_return["state_id"] = current_tick to_return["state_expire"] = seconds_to_next_tick cursor.execute("SELECT id FROM game LIMIT 1") game_cursor = cursor.fetchone() if game_cursor is None: to_return["num"] = "621" to_return["msg"] = "No game is currently running..." return json.dumps(to_return) to_return["game_id"] = game_cursor["id"] cursor.execute("""SELECT services.id AS service_id, services.name as service_name, services.port as port, current_state as state FROM services""") to_return["services"] = cursor.fetchall() # Determine which scripts exists and which should be run cursor.execute( """SELECT id AS script_id, upload_id, filename AS script_name, type, service_id, current_state as state FROM scripts""") to_return["scripts"] = cursor.fetchall() cursor.execute( """SELECT team_id, json_list_of_scripts_to_run AS json_list FROM team_scripts_run_status WHERE team_scripts_run_status.tick_id = %s""", (current_tick, )) run_scripts = [] for team_scripts_to_run in cursor.fetchall(): team_id = team_scripts_to_run["team_id"] run_list = json.loads(team_scripts_to_run["json_list"]) run_scripts.append({"team_id": team_id, "run_list": run_list}) to_return["run_scripts"] = run_scripts return json.dumps(to_return)
def main(): event = Event.query.order_by(Event.id.desc()).first() while not game_ad_running(): event = Event.query.order_by(Event.id.desc()).first() logger.info( "AD Game is not running. Gamebot will start at {} (UTC) (System time (UTC) now is {})" .format(str(event.attack_defense_start), str(datetime.now()))) sleep(10) current_tick, seconds_left = get_current_tick() if current_tick != None: # Gibt schon einen Tick, dann erstelle nicht direkt noch einen logger.info("We must be picking up from the last run. Sleep for", seconds_left, "until the next tick.") sleep(seconds_left) while True: event = Event.query.order_by(Event.id.desc()).first() if not game_ad_running(): logger.info("Game ended at {}".format(str(datetime.now()))) break # Create a new tick time_to_sleep = random.uniform(TICK_TIME_IN_SECONDS - 30, TICK_TIME_IN_SECONDS + 30) tick = Tick() tick.time_to_change = datetime.now() + timedelta(seconds=time_to_sleep) tick.event = event db_session.add(tick) db_session.commit() # db_session.remove() num_benign_scripts = random.randint( max(1, NUMBER_OF_BENIGN_SCRIPTS - 1), NUMBER_OF_BENIGN_SCRIPTS + 1) # Decide what scripts to run against each team for attending_team in AttendingTeam.query.filter_by(event=event): list_of_scripts_to_execute = get_list_of_scripts_to_run( Challenge.query.filter_by(type='ad', event=event), num_benign_scripts) tsrs = TeamScriptsRunStatus() tsrs.attending_team = attending_team tsrs.tick = tick tsrs.json_list_of_scripts_to_run = json.dumps( list_of_scripts_to_execute, ensure_ascii=False) db_session.add(tsrs) db_session.commit() # db_session.remove() # Sleep for the amount of time until the next tick time_diff_to_sleep = tick.time_to_change - datetime.now() seconds_to_sleep = time_diff_to_sleep.seconds + ( time_diff_to_sleep.microseconds / 1E6) if time_diff_to_sleep.total_seconds() < 0: logger.debug( "Time left is negative: {}".format(time_diff_to_sleep)) seconds_to_sleep = 0 logger.info("Sleeping for {}".format(seconds_to_sleep)) sleep(seconds_to_sleep) logger.info("Awake")
def service_set_state(service_id, team_id): """The ``/service/state`` endpoint requires authentication and expects the ``team_id`` and ``service_id`` as additional arguments. It sets the the state for a specific service for a specific team. Note that this endpoint requires a POST request. It can be reached at ``/service/state/set/<service_id>/team/<team_id>?secret=<API_SECRET>``. It expects the following inputs: - state, the state the service is in. - reason, the reason why the state was changed. The ``state`` argument must conform to the following notion: - up, which means the service is up - notfunctional, which means it is up but not functional - down, which means it is down (refusing connections) The ``reason`` argument is any string and can be a return value of a verification script. The JSON response is:: { "id": int, "result": ("success", "failure") } :param int team_id: the ID of the team to be updated. :param int service_id: the ID of the service to be updated. :return: a JSON dictionary with a result status, to verify if setting the state was successful. """ state = request.form.get("state") reason = request.form.get("reason") # Allow old numerical-style status and convert to enum if state not in ("up", "notfunctional", "down"): old_code = int(state) if old_code not in (0, 1, 2): abort(400) state = ["down", "notfunctional", "up"][int(state)] if state not in ("up", "notfunctional", "down"): abort(400) cursor = mysql.cursor() tick_id, _, _, _ = get_current_tick(cursor) # check that the service is enabled, otherwise state is untested cursor.execute( """SELECT current_state FROM services WHERE id = %s LIMIT 1""", (service_id, )) result = cursor.fetchone() if result["current_state"] != 'enabled': state = "untested" # get the new state for the current state table cursor.execute( """SELECT state FROM curr_team_service_state WHERE tick_id = %s and team_id = %s and service_id = %s LIMIT 1""", (tick_id, team_id, service_id)) result = cursor.fetchone() new_state = state if result: # the new state for the current table will be down if it was down at all that tick if result["state"] == "down" or state == "down": new_state = "down" elif result["state"] == "notfunctional" or state == "notfunctional": new_state = "notfunctional" elif result["state"] == "up" or state == "up": new_state = "up" # add to the log cursor.execute( """INSERT INTO team_service_state (team_id, service_id, state, reason, tick_id) VALUES (%s, %s, %s, %s, %s)""", (team_id, service_id, state, reason, tick_id)) # update the current state cursor.execute( """INSERT INTO curr_team_service_state (tick_id, team_id, service_id, state) VALUES(%s, %s, %s, %s) ON DUPLICATE KEY UPDATE state=%s""", (tick_id, team_id, service_id, new_state, new_state)) mysql.database.commit() return json.dumps({"id": cursor.lastrowid, "result": "success"})
def services_get_states(tick_id=None): """The ``/services/states`` endpoint requires authentication and takes an optional ``tick_id`` argument. It fetches the services' states for all teams for the current tick if no ``tick_id`` is provided, or for the specified ``tick_id`` otherwise. It can be reached at ``/services/states?secret=<API_SECRET>``. It can also be reached at ``/services/states/tick/<tick_id>?secret=<API_SECRET>``. The JSON response looks like:: { team_id: { service_id: { "service_name": string, "service_state": ("up", "notfunctional", "down", "untested") } } } :param int tick_id: optional tick_id :return: a JSON dictionary that maps teams to service states. """ cursor = mysql.cursor() if tick_id is None: tick_id, _, _, _ = get_current_tick(cursor) if tick_id > 0: tick_id -= 1 cursor.execute( """SELECT id, name FROM services WHERE current_state = 'enabled' """) services = dict() for result in cursor.fetchall(): services[result["id"]] = result["name"] # start building the dict # we do this here since the table of service states might not have results if things weren't going well # in which case services are untested teams = dict() cursor.execute("""SELECT id from teams""") for result in cursor.fetchall(): team_id = result["id"] teams[team_id] = dict() for service_id, name in services.items(): teams[team_id][service_id] = { "service_name": name, "service_state": "untested" } # now get the real results cursor.execute( """SELECT ts.team_id, ts.service_id, ts.state, s.name FROM curr_team_service_state as ts JOIN services as s ON s.id = ts.service_id WHERE tick_id = %s AND s.current_state = 'enabled' """, (tick_id, )) # build the result dict for result in cursor.fetchall(): team_id = result["team_id"] service_name = result["name"] service_id = result["service_id"] state = result["state"] service_dict = dict() service_dict["service_name"] = service_name service_dict["service_state"] = state teams[team_id][service_id] = service_dict return json.dumps({"service_states": teams})
def service_new(): """The ``/service/new`` endpoint requires authentication. It adds a service to the database and initializes its state. Note that this endpoint requires a POST request. It can be reached at ``/service/new?secret=<API_SECRET>``. It requires the following inputs: - name, the name of the service. - upload_id, upload which has the payload. - description, the description of the service. - authors, optional - flag_id_description, description of the flag_id - state, enabled, disabled. Defaults to "enabled" The JSON response looks like:: { "id" : int, "result": ("success", "failure") } :return: a JSON dictionary containing status information. """ name = request.form.get("name") upload_id = request.form.get("upload_id") description = request.form.get("description") authors = request.form.get("authors", None) flag_id_description = request.form.get("flag_id_description") state = request.form.get("state", "enabled") if state not in ("enabled", "disabled"): abort(400) cursor = mysql.cursor() # get the team_id from the uploads cursor.execute( """SELECT team_id FROM uploads WHERE id = %s LIMIT 1""", (upload_id, )) result = cursor.fetchone() team_id = result["team_id"] cursor.execute( """INSERT INTO services (name, upload_id, description, authors, flag_id_description, team_id, current_state) VALUES (%s, %s, %s, %s, %s, %s, %s)""", (name, upload_id, description, authors, flag_id_description, team_id, state)) service_id = cursor.lastrowid # set the state in the log tick_id, _, _, _ = get_current_tick(cursor) cursor.execute( """INSERT INTO service_state (service_id, state, reason, tick_id) VALUES (%s, %s, %s, %s)""", (service_id, state, "initial state", tick_id)) # set the port number to service_id + 10000 cursor.execute( """UPDATE services SET port = %s WHERE id = %s""", (service_id + 10000, service_id)) mysql.database.commit() return json.dumps({"result": "success", "id": service_id})
def get_wasexploited(tick_id=None): """The ``/flags/wasexploited/tick/`` endpoints requires authentication and expect no additional arguments. It fetches all the flags captured and lost by all the teams in the given tick. It can be reached at ``/flags/wasexploited/tick/<tick_id>?secret=<API_SECRET>`` ``/flags/wasexploited?secret=<API_SECRET>`` The JSON response is:: { team_id: { service_id: { "service_name": string, "service_state": ("exploited", "notexploited") } } } :param int tick_id: The target tick id for which the flags information is needed. :return: a JSON dictionary for each team with flag information. """ cursor = mysql.cursor() if tick_id is None: tick_id, _, _, _ = get_current_tick(cursor) cursor.execute("""SELECT id, name FROM services WHERE current_state = 'enabled' """) services = dict() for result in cursor.fetchall(): services[result["id"]] = result["name"] # start building the dict # we do this here since the table of service states might not have results if things weren't going well # in which case services are untested teams = dict() cursor.execute("""SELECT id from teams""") for result in cursor.fetchall(): team_id = result["id"] teams[team_id] = dict() for service_id, name in services.items(): teams[team_id][service_id] = {"service_name": name, "service_state": "notexploited"} # Perform the main query cursor = mysql.cursor() cursor.execute("""SELECT DISTINCT fl.team_id as against_team_id, fs.service_id as service_id FROM flag_submissions as fs, flags as fl WHERE fs.tick_id = %s AND fs.result = 'correct' AND fs.flag_id = fl.id""", (tick_id,)) for result in cursor.fetchall(): against_team_id = result["against_team_id"] service_id = result["service_id"] teams[against_team_id][service_id]["service_state"] = "exploited" return json.dumps(teams)
def get_tick_duration(): _, seconds_to_next_tick = get_current_tick() return json.dumps(seconds_to_next_tick)