def files(path): """ Route in charge of dealing with making sure that CTF challenges are only accessible during the competition. :param path: :return: """ f = Files.query.filter_by(location=path).first_or_404() if f.type == 'challenge': if challenges_visible(): if current_user.is_admin() is False: if not ctftime(): abort(403) else: if not ctftime(): abort(403) # Allow downloads if a valid token is provided token = request.args.get('token', '') try: data = unserialize(token, max_age=3600) user_id = data.get('user_id') team_id = data.get('team_id') file_id = data.get('file_id') user = Users.query.filter_by(id=user_id).first() team = Teams.query.filter_by(id=team_id).first() # Check user is admin if challenge_visibility is admins only if get_config('challenge_visibility' ) == 'admins' and user.type != 'admin': abort(403) # Check that the user exists and isn't banned if user: if user.banned: abort(403) else: abort(403) # Check that the team isn't banned if team: if team.banned: abort(403) else: pass # Check that the token properly refers to the file if file_id != f.id: abort(403) # The token isn't expired or broken except (BadTimeSignature, SignatureExpired, BadSignature): abort(403) uploader = get_uploader() try: return uploader.download(f.location) except IOError: abort(404)
def attempt_single(self, request, request_data, user, team, kpm, challenge): challenge_id = challenge.id if challenge.state == "hidden": return False, '' if challenge.state == "locked": return False, '' if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) solve_ids = set([solve_id for solve_id, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs: pass else: return False, '' chal_class = get_chal_class(challenge.type) solves = Solves.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).first() # Challenge not solved yet if not solves: status, message = chal_class.attempt(challenge, request) if status: # The challenge plugin says the input is right if ctftime() or current_user.is_admin(): chal_class.solve(user=user, team=team, challenge=challenge, request=request) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} with kpm {kpm} [CORRECT]", submission=request_data["submission"].encode("utf-8"), kpm=kpm, ) return True, message else: # The challenge plugin says the input is wrong if ctftime() or current_user.is_admin(): clear_standings() return False, message # Challenge already solved else: return False, ''
def get_challenges(): if not is_admin(): if not ctftime(): if view_after_ctf(): pass else: return [] if challenges_visible() and (ctf_started() or is_admin()): chals = db.session.query(Challenges.id, Challenges.name, Challenges.category).filter( or_(Challenges.state != 'hidden', Challenges.state is None)).all() jchals = [] for x in chals: jchals.append({ 'id': x.id, 'name': x.name, 'category': x.category }) # Sort into groups categories = set(map(lambda x: x['category'], jchals)) jchals = [ j for c in categories for j in jchals if j['category'] == c ] return jchals return []
def rand_unlock_callback(): with APP_REF.app_context(): if not ctftime(): log('lah', "[{date}] unlocking did not run because ctf has not started") return if datetime.datetime.utcnow().minute not in RAND_UNLOCK_MINUTES: log('lah', "[{date}] unlocking did not run because minute is not aligned") return for i in range(RAND_UNLOCK_QUESTIONS): # Unlock one random question, that is visible, not unlocked, and of the lowest available unlock_order min_order = db.session.query( func.min(LahChallenge.unlock_order).label("min_order"), func.count().label("count") ).filter( LahChallenge.state == "visible", LahChallenge.is_unlocked == False, LahChallenge.unlock_order > 0, ).one() count = min_order.count order = min_order.min_order if not min_order or count == 0: log('lah', "[{date}] unlocking finished early because no locked challenges were found.") return rand_offset = randrange(count) challenge = LahChallenge.query.filter_by(unlock_order=order, is_unlocked=False, state="visible").order_by(LahChallenge.id).offset(rand_offset).first() if not challenge: log('lah', "[{date}] encountered invalid state: randomly selected challenge was None.") challenge.is_unlocked = True db.session.commit() log('lah', "[{date}] unlocked challenge '{chal}'", chal=challenge.name)
def during_ctf_time_only_wrapper(*args, **kwargs): if ctftime() or current_user.is_admin(): return f(*args, **kwargs) else: if ctf_ended(): error = '{} has ended'.format(config.ctf_name()) abort(403, description=error) if ctf_started() is False: error = '{} has not started yet'.format(config.ctf_name()) abort(403, description=error)
def during_ctf_time_only_wrapper(*args, **kwargs): if ctftime() or current_user.is_admin(): return f(*args, **kwargs) else: if ctf_ended(): if view_after_ctf(): return f(*args, **kwargs) else: error = '{} 已经结束'.format(config.ctf_name()) abort(403, description=error) if ctf_started() is False: error = '{} 尚未开始'.format(config.ctf_name()) abort(403, description=error)
def wrapper(*args, **kwargs): result = f(*args, **kwargs) if not ctftime(): return result if isinstance(result, JSONMixin): data = result.json if isinstance( data, dict) and data.get("success") == True and isinstance( data.get("data"), dict) and data.get("data").get( "status") == "correct": if request.content_type != "application/json": request_data = request.form else: request_data = request.get_json() challenge_id = request_data.get("challenge_id") challenge = Challenges.query.filter_by( id=challenge_id).first_or_404() solvers = Solves.query.filter_by(challenge_id=challenge.id) if TEAMS_MODE: solvers = solvers.filter(Solves.team.has(hidden=False)) else: solvers = solvers.filter(Solves.user.has(hidden=False)) num_solves = solvers.count() limit = app.config["DISCORD_WEBHOOK_LIMIT"] if limit and num_solves > int(limit): return result webhook = DiscordWebhook( url=app.config['DISCORD_WEBHOOK_URL']) user = get_current_user() team = get_current_team() format_args = { "team": sanitize(team.name), "user": sanitize(user.name), "challenge": sanitize(challenge.name), "solves": num_solves, "fsolves": ordinal(num_solves), "category": sanitize(challenge.category) } message = app.config['DISCORD_WEBHOOK_MESSAGE'].format( **format_args) embed = DiscordEmbed(description=message) webhook.add_embed(embed) webhook.execute() return result
def static_html(route): """ Route in charge of routing users to Pages. :param route: :return: """ page = get_page(route) if page is None: if (ctftime() or current_user.is_admin() or (ctf_ended() and view_after_ctf())): filename = safe_join(app.root_path, "static", route) if os.path.isfile(filename): return send_file(filename) abort(404) else: if page.auth_required and authed() is False: return redirect(url_for("auth.login", next=request.full_path)) return render_template("page.html", content=markdown(page.content))
def files(path): """ Route in charge of dealing with making sure that CTF challenges are only accessible during the competition. :param path: :return: """ f = Files.query.filter_by(location=path).first_or_404() if f.type == 'challenge': if current_user.authed(): if current_user.is_admin() is False: if not ctftime(): abort(403) else: abort(403) uploader = get_uploader() try: return uploader.download(f.location) except IOError: abort(404)
def post(self): if authed() is False: return {"success": True, "data": {"status": "authentication_required"}}, 403 if request.content_type != "application/json": request_data = request.form else: request_data = request.get_json() challenge_id = request_data.get("challenge_id") if current_user.is_admin(): preview = request.args.get("preview", False) if preview: challenge = Challenges.query.filter_by(id=challenge_id).first_or_404() chal_class = get_chal_class(challenge.type) status, message = chal_class.attempt(challenge, request) return { "success": True, "data": { "status": "correct" if status else "incorrect", "message": message, }, } if ctf_paused(): return ( { "success": True, "data": { "status": "paused", "message": "{} is paused".format(config.ctf_name()), }, }, 403, ) user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if config.is_teams_mode() and team is None: abort(403) fails = Fails.query.filter_by( account_id=user.account_id, challenge_id=challenge_id ).count() challenge = Challenges.query.filter_by(id=challenge_id).first_or_404() if challenge.state == "hidden": abort(404) if challenge.state == "locked": abort(403) if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) solve_ids = ( Solves.query.with_entities(Solves.challenge_id) .filter_by(account_id=user.account_id) .order_by(Solves.challenge_id.asc()) .all() ) solve_ids = set([solve_id for solve_id, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs: pass else: abort(403) chal_class = get_chal_class(challenge.type) # Anti-bruteforce / submitting Flags too quickly kpm = current_user.get_wrong_submissions_per_minute(user.account_id) if kpm > 10: if ctftime(): chal_class.fail( user=user, team=team, challenge=challenge, request=request ) log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [TOO FAST]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) # Submitting too fast return ( { "success": True, "data": { "status": "ratelimited", "message": "You're submitting flags too fast. Slow down.", }, }, 429, ) solves = Solves.query.filter_by( account_id=user.account_id, challenge_id=challenge_id ).first() # Challenge not solved yet if not solves: # Hit max attempts max_tries = challenge.max_attempts if max_tries and fails >= max_tries > 0: return ( { "success": True, "data": { "status": "incorrect", "message": "You have 0 tries remaining", }, }, 403, ) status, message = chal_class.attempt(challenge, request) if status: # The challenge plugin says the input is right if ctftime() or current_user.is_admin(): chal_class.solve( user=user, team=team, challenge=challenge, request=request ) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [CORRECT]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) return { "success": True, "data": {"status": "correct", "message": message}, } else: # The challenge plugin says the input is wrong if ctftime() or current_user.is_admin(): chal_class.fail( user=user, team=team, challenge=challenge, request=request ) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [WRONG]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) if max_tries: # Off by one since fails has changed since it was gotten attempts_left = max_tries - fails - 1 tries_str = "tries" if attempts_left == 1: tries_str = "try" # Add a punctuation mark if there isn't one if message[-1] not in "!().;?[]{}": message = message + "." return { "success": True, "data": { "status": "incorrect", "message": "{} You have {} {} remaining.".format( message, attempts_left, tries_str ), }, } else: return { "success": True, "data": {"status": "incorrect", "message": message}, } # Challenge already solved else: log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [ALREADY SOLVED]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) return { "success": True, "data": { "status": "already_solved", "message": "You already solved this", }, }
def post(self): if authed() is False: return { 'success': True, 'data': { 'status': "authentication_required", } }, 403 if request.content_type != 'application/json': request_data = request.form else: request_data = request.get_json() challenge_id = request_data.get('challenge_id') if current_user.is_admin(): preview = request.args.get('preview', False) if preview: challenge = Challenges.query.filter_by( id=challenge_id).first_or_404() chal_class = get_chal_class(challenge.type) status, message = chal_class.attempt(challenge, request) return { 'success': True, 'data': { 'status': "correct" if status else "incorrect", 'message': message } } if ctf_paused(): return { 'success': True, 'data': { 'status': "paused", 'message': '{} приостановлены'.format(config.ctf_name()) } }, 403 user = get_current_user() team = get_current_team() fails = Fails.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).count() challenge = Challenges.query.filter_by(id=challenge_id).first_or_404() if challenge.state == 'hidden': abort(404) if challenge.state == 'locked': abort(403) if challenge.requirements: requirements = challenge.requirements.get('prerequisites', []) solve_ids = Solves.query \ .with_entities(Solves.challenge_id) \ .filter_by(account_id=user.account_id) \ .order_by(Solves.challenge_id.asc()) \ .all() solve_ids = set([solve_id for solve_id, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs: pass else: abort(403) chal_class = get_chal_class(challenge.type) # Anti-bruteforce / submitting Flags too quickly if current_user.get_wrong_submissions_per_minute(session['id']) > 10: if ctftime(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [TOO FAST]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( session['id'])) # Submitting too fast return { 'success': True, 'data': { 'status': "ratelimited", 'message': "Вы отправляете флаги слишком быстро. Помедленнее." } }, 429 solves = Solves.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).first() # Challenge not solved yet if not solves: # Hit max attempts max_tries = challenge.max_attempts if max_tries and fails >= max_tries > 0: return { 'success': True, 'data': { 'status': "incorrect", 'message': "У вас осталось 0 попыток" } }, 403 status, message = chal_class.attempt(challenge, request) if status: # The challenge plugin says the input is right if ctftime() or current_user.is_admin(): chal_class.solve(user=user, team=team, challenge=challenge, request=request) clear_standings() log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [CORRECT]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( session['id'])) return { 'success': True, 'data': { 'status': "correct", 'message': message } } else: # The challenge plugin says the input is wrong if ctftime() or current_user.is_admin(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) clear_standings() log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [WRONG]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( session['id'])) if max_tries: # Off by one since fails has changed since it was gotten attempts_left = max_tries - fails - 1 tries_str = 'попытки' if attempts_left == 1: tries_str = 'попытка' # Add a punctuation mark if there isn't one if message[-1] not in '!().;?[]{}': message = message + '.' return { 'success': True, 'data': { 'status': "incorrect", 'message': '{} У вас {} {} осталось.'.format( message, attempts_left, tries_str) } } else: return { 'success': True, 'data': { 'status': "incorrect", 'message': message } } # Challenge already solved else: log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [ALREADY SOLVED]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( user.account_id)) return { 'success': True, 'data': { 'status': "already_solved", 'message': 'Вы уже решили это' } }
def post(self): if authed() is False: return { 'success': True, 'data': { 'status': "authentication_required", } }, 403 if request.content_type != 'application/json': request_data = request.form else: request_data = request.get_json() challenge_id = request_data.get('challenge_id') if current_user.is_admin(): preview = request.args.get('preview', False) if preview: challenge = Challenges.query.filter_by( id=challenge_id).first_or_404() chal_class = get_chal_class(challenge.type) status, message = chal_class.attempt(challenge, request) return { 'success': True, 'data': { 'status': "correct" if status else "incorrect", 'message': message } } if ctf_paused(): return { 'success': True, 'data': { 'status': "paused", 'message': '{} is paused'.format(config.ctf_name()) } }, 403 user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if config.is_teams_mode() and team is None: abort(403) fails = Fails.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).count() challenge = Challenges.query.filter_by(id=challenge_id).first_or_404() if challenge.state == 'hidden': abort(404) if challenge.state == 'locked': abort(403) if challenge.requirements: requirements = challenge.requirements.get('prerequisites', []) solve_ids = Solves.query \ .with_entities(Solves.challenge_id) \ .filter_by(account_id=user.account_id) \ .order_by(Solves.challenge_id.asc()) \ .all() solve_ids = set([solve_id for solve_id, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs: pass else: abort(403) chal_class = get_chal_class(challenge.type) # Anti-bruteforce / submitting Flags too quickly if current_user.get_wrong_submissions_per_time( get_current_user(), config.get_attempt_limit_type(), config.get_attempt_limit_hour(), config.get_attempt_limit_minute(), config.get_attempt_limit_second() ) > config.get_attempt_limit_count(): if ctftime(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [TOO FAST]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_time( get_current_user(), config.get_attempt_limit_type(), config.get_attempt_limit_hour(), config.get_attempt_limit_minute(), config.get_attempt_limit_second())) # Submitting too fast return { 'success': True, 'data': { 'status': "ratelimited", 'message': "You're submitting flags too fast. Slow down." } }, 429 solves = Solves.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).first() # Challenge not solved yet if not solves: # Hit max attempts max_tries = challenge.max_attempts if max_tries and fails >= max_tries > 0: return { 'success': True, 'data': { 'status': "incorrect", 'message': "You have 0 tries remaining" } }, 403 status, message = chal_class.attempt(challenge, request) if status: # The challenge plugin says the input is right if ctftime() or current_user.is_admin(): chal_class.solve(user=user, team=team, challenge=challenge, request=request) clear_standings() log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [CORRECT]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( session['id'])) return { 'success': True, 'data': { 'status': "correct", 'message': message } } else: # The challenge plugin says the input is wrong if ctftime() or current_user.is_admin(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) clear_standings() log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [WRONG]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( session['id'])) if max_tries: # Off by one since fails has changed since it was gotten attempts_left = max_tries - fails - 1 tries_str = 'tries' if attempts_left == 1: tries_str = 'try' # Add a punctuation mark if there isn't one if message[-1] not in '!().;?[]{}': message = message + '.' return { 'success': True, 'data': { 'status': "incorrect", 'message': '{} You have {} {} remaining.'.format( message, attempts_left, tries_str) } } else: return { 'success': True, 'data': { 'status': "incorrect", 'message': message } } # Challenge already solved else: log('submissions', "[{date}] {name} submitted {submission} with kpm {kpm} [ALREADY SOLVED]", submission=request_data['submission'].encode('utf-8'), kpm=current_user.get_wrong_submissions_per_minute( user.account_id)) return { 'success': True, 'data': { 'status': "already_solved", 'message': 'You already solved this' } }
def wrapper(*args, **kwargs): result = f(*args, **kwargs) if not ctftime(): return result if isinstance(result, JSONMixin): data = result.json if isinstance( data, dict) and data.get("success") == True and isinstance( data.get("data"), dict) and data.get("data").get( "status") == "correct": if request.content_type != "application/json": request_data = request.form else: request_data = request.get_json() challenge_id = request_data.get("challenge_id") challenge = Challenges.query.filter_by( id=challenge_id).first_or_404() solvers = Solves.query.filter_by(challenge_id=challenge.id) if TEAMS_MODE: solvers = solvers.filter(Solves.team.has(hidden=False)) else: solvers = solvers.filter(Solves.user.has(hidden=False)) num_solves = solvers.count() limit = app.config["DISCORD_WEBHOOK_LIMIT"] if limit and num_solves > int(limit): return result user = get_current_user() difficulty = challenge.tags[0].value color = 0x0 img_url = '' if (difficulty == 'Easy'): color = 0x0EEC88 img_url = 'https://i.imgur.com/0dfOYmU.jpg' elif (difficulty == 'Medium'): color = 0xfb901e img_url = 'https://i.imgur.com/FFSgd5o.jpg' elif (difficulty == 'Hard'): color = 0xff2856 img_url = 'https://i.imgur.com/7rz0Xsh.jpg' elif (difficulty == 'Warmup'): color = 0x00fff9 img_url = 'https://i.imgur.com/UYvi3wU.jpg' elif (difficulty == 'Elite'): color = 0x641EBD img_url = 'https://i.imgur.com/9ile1tK.jpg' else: color = 0x00fff9 img_url = 'https://i.imgur.com/UYvi3wU.jpg' emoji = '' if num_solves == 1: emoji = '🥇' elif num_solves == 2: emoji = '🥈' elif num_solves == 3: emoji = '🥉' else: emoji = '🚩' webhook = DiscordWebhook( url=app.config['DISCORD_WEBHOOK_URL']) embed = None if num_solves == 1: embed = DiscordEmbed( title='First Blood!', description= f'```md\n🩸 First blood on the [ {difficulty} ]( {challenge.category.replace(" ", "_")} ) challenge <{challenge.name.replace(" ", "_")}> goes to < {user.name.replace(" ", "_")} >```', color=color) embed.set_image(url=img_url) else: embed = DiscordEmbed( title='Flag Captured!', description= f'```md\n{emoji} Flag captured from the [ {difficulty} ]( {challenge.category.replace(" ", "_")} ) challenge <{challenge.name.replace(" ", "_")}> by < {user.name.replace(" ", "_")} > -- ({num_solves} solves)```', color=color) embed.set_timestamp() webhook.add_embed(embed) webhook.execute() return result
def files(path): """ Route in charge of dealing with making sure that CTF challenges are only accessible during the competition. :param path: :return: """ f = Files.query.filter_by(location=path).first_or_404() if f.type == "challenge": if challenges_visible(): if current_user.is_admin() is False: if not ctftime(): if ctf_ended() and view_after_ctf(): pass else: abort(403) else: # User cannot view challenges based on challenge visibility # e.g. ctf requires registration but user isn't authed or # ctf requires admin account but user isn't admin if not ctftime(): # It's not CTF time. The only edge case is if the CTF is ended # but we have view_after_ctf enabled if ctf_ended() and view_after_ctf(): pass else: # In all other situations we should block challenge files abort(403) # Allow downloads if a valid token is provided token = request.args.get("token", "") try: data = unserialize(token, max_age=3600) user_id = data.get("user_id") team_id = data.get("team_id") file_id = data.get("file_id") user = Users.query.filter_by(id=user_id).first() team = Teams.query.filter_by(id=team_id).first() # Check user is admin if challenge_visibility is admins only if ( get_config(ConfigTypes.CHALLENGE_VISIBILITY) == "admins" and user.type != "admin" ): abort(403) # Check that the user exists and isn't banned if user: if user.banned: abort(403) else: abort(403) # Check that the team isn't banned if team: if team.banned: abort(403) else: pass # Check that the token properly refers to the file if file_id != f.id: abort(403) # The token isn't expired or broken except (BadTimeSignature, SignatureExpired, BadSignature): abort(403) uploader = get_uploader() try: return uploader.download(f.location) except IOError: abort(404)
def post(self): if authed() is False: return { "success": True, "data": { "status": "authentication_required" } }, 403 if request.content_type != "application/json": request_data = request.form else: request_data = request.get_json() challenge_id = request_data.get("challenge_id") if current_user.is_admin(): preview = request.args.get("preview", False) if preview: challenge = Challenges.query.filter_by( id=challenge_id).first_or_404() chal_class = get_chal_class(challenge.type) status, message = chal_class.attempt(challenge, request) return { "success": True, "data": { "status": "correct" if status else "incorrect", "message": message, }, } if ctf_paused(): return ( { "success": True, "data": { "status": "paused", "message": "{} is paused".format(config.ctf_name()), }, }, 403, ) user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if config.is_teams_mode() and team is None: abort(403) fails = Fails.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).count() challenge = Challenges.query.filter_by(id=challenge_id).first_or_404() if challenge.state == "hidden": abort(404) if challenge.state == "locked": abort(403) if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) solve_ids = {solve_id for solve_id, in solve_ids} # Gather all challenge IDs so that we can determine invalid challenge prereqs all_challenge_ids = { c.id for c in Challenges.query.with_entities(Challenges.id).all() } prereqs = set(requirements).intersection(all_challenge_ids) if solve_ids >= prereqs: pass else: abort(403) chal_class = get_chal_class(challenge.type) # Anti-bruteforce / submitting Flags too quickly kpm = current_user.get_wrong_submissions_per_minute(user.account_id) if kpm > 10: if ctftime(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [TOO FAST]", name=user.name, submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) # Submitting too fast return ( { "success": True, "data": { "status": "ratelimited", "message": "You're submitting flags too fast. Slow down.", }, }, 429, ) solves = Solves.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).first() # Challenge not solved yet if not solves: # Hit max attempts max_tries = challenge.max_attempts if max_tries and fails >= max_tries > 0: return ( { "success": True, "data": { "status": "incorrect", "message": "You have 0 tries remaining", }, }, 403, ) status, message = chal_class.attempt(challenge, request) if status: # The challenge plugin says the input is right if ctftime() or current_user.is_admin(): # send discord webhook # @TODO replace static url with a global variable containing the site url in config.py user_url = "https://ctf.sigpwny.com/users/" + str(user.id) challenge_url = "https://ctf.sigpwny.com/challenges#" + quote( challenge.name) description = ":white_check_mark: [{0}]({1}) solved [{2}]({3}) ({4})".format( user.name, user_url, challenge.name, challenge_url, challenge.value) embeds = [{ "description": description, "color": 10553667, "timestamp": datetime.datetime.utcnow().strftime( '%Y-%m-%d %H:%M:%SZ') }] send_discord_webhook(embeds) chal_class.solve(user=user, team=team, challenge=challenge, request=request) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [CORRECT]", name=user.name, submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) return { "success": True, "data": { "status": "correct", "message": message }, } else: # The challenge plugin says the input is wrong if ctftime() or current_user.is_admin(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [WRONG]", name=user.name, submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) if max_tries: # Off by one since fails has changed since it was gotten attempts_left = max_tries - fails - 1 tries_str = "tries" if attempts_left == 1: tries_str = "try" # Add a punctuation mark if there isn't one if message[-1] not in "!().;?[]{}": message = message + "." return { "success": True, "data": { "status": "incorrect", "message": "{} You have {} {} remaining.".format( message, attempts_left, tries_str), }, } else: return { "success": True, "data": { "status": "incorrect", "message": message }, } # Challenge already solved else: log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [ALREADY SOLVED]", name=user.name, submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) return { "success": True, "data": { "status": "already_solved", "message": "You already solved this", }, }
def flaganizer_submit(): if authed() is False: return { "success": True, "data": { "status": "authentication_required" } }, 403 if request.content_type != "application/json": request_data = request.form else: request_data = request.get_json() if ctf_paused(): return ( { "success": True, "data": { "status": "paused", "message": "{} is paused".format(config.ctf_name()), }, }, 403, ) user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if config.is_teams_mode() and team is None: abort(403) kpm = current_user.get_wrong_submissions_per_minute(user.account_id) frsp = requests.post(FLAGANIZER_VERIFY_ENDPOINT, data={ "flag": request_data.get("submission", "") }, headers={ "X-CTFProxy-SubAcc-JWT": request.headers.get("X-CTFProxy-JWT") }).json() if frsp["Success"] == 0: if ctftime() or current_user.is_admin(): placeholder_challenge = Challenges.query.filter_by( name="wrong submission").first() if placeholder_challenge is None: placeholder_challenge = Challenges( name="wrong submission", description=FLAGANIZER_DESCRIPTION_PREFIX + "a placeholder challenge for unrecognized flags", value=0, category="misc", state="hidden", max_attempts=0) db.session.add(placeholder_challenge) db.session.commit() db.session.close() placeholder_challenge = Challenges.query.filter_by( name="wrong submission").first() chal_class = get_chal_class(placeholder_challenge.type) if placeholder_challenge is not None: chal_class.fail(user=user, team=team, challenge=placeholder_challenge, request=request) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} via flaganizer with kpm {kpm} [WRONG]", submission=request_data.get("submission", "").encode("utf-8"), kpm=kpm, ) return { "success": True, "data": { "status": "incorrect", "message": frsp["Message"] }, } challenge = Challenges.query.filter_by( description=FLAGANIZER_DESCRIPTION_PREFIX + frsp["Flag"]["Id"]).first() if challenge is None: challenge = Challenges(name=frsp["Flag"]["DisplayName"], description=FLAGANIZER_DESCRIPTION_PREFIX + frsp["Flag"]["Id"], value=frsp["Flag"]["Points"], category=frsp["Flag"]["Category"], state="hidden", max_attempts=0) db.session.add(challenge) db.session.commit() challenge_id = challenge.id if challenge.state == "locked": db.session.close() abort(403) if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) solve_ids = set([solve_id for solve_id, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs: pass else: db.session.close() abort(403) chal_class = get_chal_class(challenge.type) if kpm > 10: if ctftime(): chal_class.fail(user=user, team=team, challenge=challenge, request=request) log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [TOO FAST]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) # Submitting too fast db.session.close() return ( { "success": True, "data": { "status": "ratelimited", "message": "You're submitting flags too fast. Slow down.", }, }, 429, ) solves = Solves.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).first() # Challenge not solved yet if not solves: status, message = chal_class.attempt(challenge, request) chal_class.solve(user=user, team=team, challenge=challenge, request=request) clear_standings() log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} via flaganizer with kpm {kpm} [CORRECT]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) db.session.close() return { "success": True, "data": { "status": "correct", "message": "Successfully submitted!" }, } else: log( "submissions", "[{date}] {name} submitted {submission} on {challenge_id} via flaganizer with kpm {kpm} [ALREADY SOLVED]", submission=request_data.get("submission", "").encode("utf-8"), challenge_id=challenge_id, kpm=kpm, ) db.session.close() return { "success": True, "data": { "status": "already_solved", "message": "You already solved this", }, }