def get(self, challenge_id): if is_admin(): chal = Challenges.query.filter( Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() try: chal_class = get_chal_class(chal.type) except KeyError: abort( 500, f"The underlying challenge type ({chal.type}) is not installed. This challenge can not be loaded.", ) if chal.requirements: requirements = chal.requirements.get("prerequisites", []) anonymize = chal.requirements.get("anonymize") # 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() } if challenges_visible(): user = get_current_user() if user: solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = {value for value, in solve_ids} prereqs = set(requirements).intersection(all_challenge_ids) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { "success": True, "data": { "id": chal.id, "type": "hidden", "name": "???", "value": 0, "solves": None, "solved_by_me": False, "category": "???", "tags": [], "template": "", "script": "", }, } abort(403) else: abort(403) tags = [ tag["value"] for tag in TagSchema("user", many=True).dump(chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = { u.target for u in HintUnlocks.query.filter_by( type="hints", account_id=user.account_id) } files = [] for f in chal.files: token = { "user_id": user.id, "team_id": team.id if team else None, "file_id": f.id, } files.append( url_for("views.files", path=f.location, token=serialize(token))) else: files = [ url_for("views.files", path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ "id": hint.id, "cost": hint.cost, "content": hint.content }) else: hints.append({"id": hint.id, "cost": hint.cost}) response = chal_class.read(challenge=chal) solves_q, user_solves = _build_solves_query( extra_filters=(Solves.challenge_id == chal.id, )) # If there are no solves for this challenge ID then we have 0 rows maybe_row = solves_q.first() if maybe_row: challenge_id, solve_count = maybe_row solved_by_user = challenge_id in user_solves else: solve_count, solved_by_user = 0, False # Hide solve counts if we are hiding solves/accounts if scores_visible() is False or accounts_visible() is False: solve_count = None if authed(): # Get current attempts for the user attempts = Submissions.query.filter_by( account_id=user.account_id, challenge_id=challenge_id).count() else: attempts = 0 response["solves"] = solve_count response["solved_by_me"] = solved_by_user response["attempts"] = attempts response["files"] = files response["tags"] = tags response["hints"] = hints response["view"] = render_template( chal_class.templates["view"].lstrip("/"), solves=solve_count, solved_by_me=solved_by_user, files=files, tags=tags, hints=[Hints(**h) for h in hints], max_attempts=chal.max_attempts, attempts=attempts, challenge=chal, ) db.session.close() return {"success": True, "data": response}
def dump_scoreboard_csv(): # TODO: Add fields to scoreboard data temp = StringIO() writer = csv.writer(temp) standings = get_standings() # Get all user fields in a specific order user_fields = UserFields.query.all() user_field_ids = [f.id for f in user_fields] user_field_names = [f.name for f in user_fields] if is_teams_mode(): team_fields = TeamFields.query.all() team_field_ids = [f.id for f in team_fields] team_field_names = [f.name for f in team_fields] header = ([ "place", "team", "team id", "score", "member name", "member id", "member email", "member score", ] + user_field_names + team_field_names) writer.writerow(header) for i, standing in enumerate(standings): team = Teams.query.filter_by(id=standing.account_id).first() # Build field entries using the order of the field values team_field_entries = { f.field_id: f.value for f in team.field_entries } team_field_values = [ team_field_entries.get(f_id, "") for f_id in team_field_ids ] team_row = [ i + 1, team.name, team.id, standing.score, "", "", ] + team_field_values writer.writerow(team_row) for member in team.members: user_field_entries = { f.field_id: f.value for f in member.field_entries } user_field_values = [ user_field_entries.get(f_id, "") for f_id in user_field_ids ] user_row = [ "", "", "", "", member.name, member.id, member.email, member.score, ] + user_field_values writer.writerow(user_row) elif is_users_mode(): header = ["place", "user", "score"] + user_field_names writer.writerow(header) for i, standing in enumerate(standings): user = Users.query.filter_by(id=standing.account_id).first() # Build field entries using the order of the field values user_field_entries = { f.field_id: f.value for f in user.field_entries } user_field_values = [ user_field_entries.get(f_id, "") for f_id in user_field_ids ] user_row = [i + 1, user.name, standing.score] + user_field_values writer.writerow(user_row) # In Python 3 send_file requires bytes output = BytesIO() output.write(temp.getvalue().encode("utf-8")) output.seek(0) temp.close() return output
def get(self, query_args): # Require a team if in teams mode # TODO: Convert this into a re-useable decorator # TODO: The require_team decorator doesnt work because of no admin passthru if get_current_user_attrs(): if is_admin(): pass else: if config.is_teams_mode() and get_current_team_attrs() is None: abort(403) # Build filtering queries q = query_args.pop("q", None) field = str(query_args.pop("field", None)) filters = build_model_filters(model=Challenges, query=q, field=field) # Admins get a shortcut to see all challenges despite pre-requisites admin_view = is_admin() and request.args.get("view") == "admin" solve_counts = {} # Build a query for to show challenge solve information. We only # give an admin view if the request argument has been provided. # # NOTE: This is different behaviour to the challenge detail # endpoint which only needs the current user to be an admin rather # than also also having to provide `view=admin` as a query arg. solves_q, user_solves = _build_solves_query(admin_view=admin_view) # Aggregate the query results into the hashes defined at the top of # this block for later use for chal_id, solve_count in solves_q: solve_counts[chal_id] = solve_count if scores_visible() and accounts_visible(): solve_count_dfl = 0 else: # Empty out the solves_count if we're hiding scores/accounts solve_counts = {} # This is necessary to match the challenge detail API which returns # `None` for the solve count if visiblity checks fail solve_count_dfl = None # Build the query for the challenges which may be listed chal_q = Challenges.query # Admins can see hidden and locked challenges in the admin view if admin_view is False: chal_q = chal_q.filter( and_(Challenges.state != "hidden", Challenges.state != "locked")) chal_q = (chal_q.filter_by(**query_args).filter(*filters).order_by( Challenges.value, Challenges.id)) # Iterate through the list of challenges, adding to the object which # will be JSONified back to the client response = [] tag_schema = TagSchema(view="user", many=True) # 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() } for challenge in chal_q: if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) anonymize = challenge.requirements.get("anonymize") prereqs = set(requirements).intersection(all_challenge_ids) if user_solves >= prereqs or admin_view: pass else: if anonymize: response.append({ "id": challenge.id, "type": "hidden", "name": "???", "value": 0, "solves": None, "solved_by_me": False, "category": "???", "tags": [], "template": "", "script": "", }) # Fallthrough to continue continue try: challenge_type = get_chal_class(challenge.type) except KeyError: # Challenge type does not exist. Fall through to next challenge. continue # Challenge passes all checks, add it to response response.append({ "id": challenge.id, "type": challenge_type.name, "name": challenge.name, "value": challenge.value, "solves": solve_counts.get(challenge.id, solve_count_dfl), "solved_by_me": challenge.id in user_solves, "category": challenge.category, "tags": tag_schema.dump(challenge.tags).data, "template": challenge_type.templates["view"], "script": challenge_type.scripts["view"], }) db.session.close() # Reorder response # TODO: Able to set arbitrary total ordering # For now just set the ones matching config.top_category to the front # JS will put categories in order of first appearance at top response.sort( key=lambda x: x["category"] == get_config("top_category")) return {"success": True, "data": response}
def register(): return redirect( "https://discord.com/api/oauth2/authorize?client_id=704010814356455508&redirect_uri=https%3A%2F%2Fplayground.secarmy.org%2Fcallback&response_type=code&scope=identify%20email" ) errors = get_errors() if request.method == "POST": name = request.form.get("name", "").strip() email_address = request.form.get("email", "").strip().lower() password = request.form.get("password", "").strip() website = request.form.get("website") affiliation = request.form.get("affiliation") country = request.form.get("country") name_len = len(name) == 0 names = Users.query.add_columns("name", "id").filter_by(name=name).first() emails = (Users.query.add_columns( "email", "id").filter_by(email=email_address).first()) pass_short = len(password) == 0 pass_long = len(password) > 128 valid_email = validators.validate_email(email_address) team_name_email_check = validators.validate_email(name) if country: try: validators.validate_country_code(country) valid_country = True except ValidationError: valid_country = False else: valid_country = True if website: valid_website = validators.validate_url(website) else: valid_website = True if affiliation: valid_affiliation = len(affiliation) < 128 else: valid_affiliation = True if not valid_email: errors.append("Please enter a valid email address") if email.check_email_is_whitelisted(email_address) is False: errors.append( "Only email addresses under {domains} may register".format( domains=get_config("domain_whitelist"))) if names: errors.append("That user name is already taken") if team_name_email_check is True: errors.append("Your user name cannot be an email address") if emails: errors.append("That email has already been used") if pass_short: errors.append("Pick a longer password") if pass_long: errors.append("Pick a shorter password") if name_len: errors.append("Pick a longer user name") if valid_website is False: errors.append( "Websites must be a proper URL starting with http or https") if valid_country is False: errors.append("Invalid country") if valid_affiliation is False: errors.append("Please provide a shorter affiliation") if len(errors) > 0: return render_template( "register.html", errors=errors, name=request.form["name"], email=request.form["email"], password=request.form["password"], ) else: with app.app_context(): user = Users(name=name, email=email_address, password=password) if website: user.website = website if affiliation: user.affiliation = affiliation if country: user.country = country db.session.add(user) db.session.commit() db.session.flush() login_user(user) if config.can_send_mail() and get_config( "verify_emails" ): # Confirming users is enabled and we can send email. log( "registrations", format= "[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}", ) email.verify_email_address(user.email) db.session.close() return redirect(url_for("auth.confirm")) else: # Don't care about confirming users if ( config.can_send_mail() ): # We want to notify the user that they have registered. email.successful_registration_notification(user.email) log("registrations", "[{date}] {ip} - {name} registered with {email}") db.session.close() if is_teams_mode(): return redirect(url_for("teams.private")) return redirect(url_for("challenges.listing")) else: return render_template("register.html", errors=errors)
def get(self): container = request.args.get('name') if not container: return abort(403) docker = DockerConfig.query.filter_by(id=1).first() containers = DockerChallengeTracker.query.all() if container not in get_repositories(docker, tags=True): return abort(403) if is_teams_mode(): session = get_current_team() # First we'll delete all old docker containers (+2 hours) for i in containers: if int(session.id) == int( i.team_id) and (unix_time(datetime.utcnow()) - int(i.timestamp)) >= 7200: delete_container(docker, i.instance_id) DockerChallengeTracker.query.filter_by( instance_id=i.instance_id).delete() db.session.commit() check = DockerChallengeTracker.query.filter_by( team_id=session.id).filter_by(docker_image=container).first() else: session = get_current_user() for i in containers: if int(session.id) == int( i.user_id) and (unix_time(datetime.utcnow()) - int(i.timestamp)) >= 7200: delete_container(docker, i.instance_id) DockerChallengeTracker.query.filter_by( instance_id=i.instance_id).delete() db.session.commit() check = DockerChallengeTracker.query.filter_by( user_id=session.id).filter_by(docker_image=container).first() # If this container is already created, we don't need another one. if check != None and not (unix_time(datetime.utcnow()) - int(check.timestamp)) >= 300: return abort(403) # The exception would be if we are reverting a box. So we'll delete it if it exists and has been around for more than 5 minutes. elif check != None: delete_container(docker, check.instance_id) if is_teams_mode(): DockerChallengeTracker.query.filter_by( team_id=session.id).filter_by( docker_image=container).delete() else: DockerChallengeTracker.query.filter_by( user_id=session.id).filter_by( docker_image=container).delete() db.session.commit() portsbl = get_unavailable_ports(docker) create = create_container(docker, container, session.name, portsbl) ports = json.loads(create[1])['HostConfig']['PortBindings'].values() entry = DockerChallengeTracker( team_id=session.id if is_teams_mode() else None, user_id=session.id if not is_teams_mode() else None, docker_image=container, timestamp=unix_time(datetime.utcnow()), revert_time=unix_time(datetime.utcnow()) + 300, instance_id=create[0]['Id'], ports=','.join([p[0]['HostPort'] for p in ports]), host=str(docker.hostname).split(':')[0]) db.session.add(entry) db.session.commit() db.session.close() return
def register(): errors = get_errors() if request.method == "POST" and get_app_config('REGISTRATIONS_ENABLED', True): name = request.form.get("name", "").strip() email_address = request.form.get("email", "").strip().lower() password = request.form.get("password", "").strip() name_len = len(name) == 0 names = Users.query.add_columns("name", "id").filter_by(name=name).first() emails = (Users.query.add_columns( "email", "id").filter_by(email=email_address).first()) pass_short = len(password) == 0 pass_long = len(password) > 128 valid_email = validators.validate_email(email_address) team_name_email_check = validators.validate_email(name) if not valid_email: errors.append("Please enter a valid email address") if email.check_email_is_whitelisted(email_address) is False: errors.append( "Only email addresses under {domains} may register".format( domains=get_config("domain_whitelist"))) if names: errors.append("That user name is already taken") if team_name_email_check is True: errors.append("Your user name cannot be an email address") if emails: errors.append("That email has already been used") if pass_short: errors.append("Pick a longer password") if pass_long: errors.append("Pick a shorter password") if name_len: errors.append("Pick a longer user name") if len(errors) > 0: return render_template( "register.html", errors=errors, name=request.form["name"], email=request.form["email"], password=request.form["password"], ) else: with app.app_context(): user = Users(name=name, email=email_address, password=password) db.session.add(user) db.session.commit() db.session.flush() login_user(user) if config.can_send_mail() and get_config( "verify_emails" ): # Confirming users is enabled and we can send email. log( "registrations", format= "[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}", ) email.verify_email_address(user.email) db.session.close() return redirect(url_for("auth.confirm")) else: # Don't care about confirming users if ( config.can_send_mail() ): # We want to notify the user that they have registered. email.successful_registration_notification(user.email) log("registrations", "[{date}] {ip} - {name} registered with {email}") db.session.close() if is_teams_mode(): return redirect(url_for("teams.private")) return redirect(url_for("challenges.listing")) else: return render_template("register.html", errors=errors)
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} with kpm {kpm} [TOO FAST]", submission=request_data["submission"].encode("utf-8"), 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() rfps = RFP.query.filter_by(account_id=user.account_id, challenge_id=challenge_id).first() # Challenge not solved yet if not solves and not rfps: # 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=kpm, ) if chal_class.name is "rfp": return { "success": True, "data": { "status": "rfp", "message": message }, } else: 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=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} with kpm {kpm} [ALREADY SOLVED]", submission=request_data["submission"].encode("utf-8"), kpm=kpm, ) return { "success": True, "data": { "status": "already_solved", "message": "You already solved this", }, }
def get(self, query_args): # Build filtering queries q = query_args.pop("q", None) field = str(query_args.pop("field", None)) filters = build_model_filters(model=Challenges, query=q, field=field) # This can return None (unauth) if visibility is set to public user = get_current_user() # Admins can request to see everything if is_admin() and request.args.get("view") == "admin": challenges = (Challenges.query.filter_by(**query_args).filter( *filters).order_by(Challenges.value).all()) solve_ids = set([challenge.id for challenge in challenges]) else: challenges = (Challenges.query.filter( and_(Challenges.state != "hidden", Challenges.state != "locked")).filter_by( **query_args).filter(*filters).order_by( Challenges.value).all()) if user: 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([value for value, in solve_ids]) # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and get_current_team() is None: abort(403) else: solve_ids = set() response = [] tag_schema = TagSchema(view="user", many=True) for challenge in challenges: if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) anonymize = challenge.requirements.get("anonymize") prereqs = set(requirements) if solve_ids >= prereqs: pass else: if anonymize: response.append({ "id": challenge.id, "type": "hidden", "name": "???", "value": 0, "category": "???", "tags": [], "template": "", "script": "", }) # Fallthrough to continue continue try: challenge_type = get_chal_class(challenge.type) except KeyError: # Challenge type does not exist. Fall through to next challenge. continue # Challenge passes all checks, add it to response response.append({ "id": challenge.id, "type": challenge_type.name, "name": challenge.name, "value": challenge.value, "category": challenge.category, "tags": tag_schema.dump(challenge.tags).data, "template": challenge_type.templates["view"], "script": challenge_type.scripts["view"], }) db.session.close() return {"success": True, "data": response}
def get(self, challenge_id): if is_admin(): chal = Challenges.query.filter( Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() try: chal_class = get_chal_class(chal.type) except KeyError: abort( 500, f"The underlying challenge type ({chal.type}) is not installed. This challenge can not be loaded.", ) if chal.requirements: requirements = chal.requirements.get("prerequisites", []) anonymize = chal.requirements.get("anonymize") if challenges_visible(): user = get_current_user() if user: solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = set([value for value, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { "success": True, "data": { "id": chal.id, "type": "hidden", "name": "???", "value": 0, "category": "???", "tags": [], "template": "", "script": "", }, } abort(403) else: abort(403) tags = [ tag["value"] for tag in TagSchema("user", many=True).dump(chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = set([ u.target for u in HintUnlocks.query.filter_by( type="hints", account_id=user.account_id) ]) files = [] for f in chal.files: token = { "user_id": user.id, "team_id": team.id if team else None, "file_id": f.id, } files.append( url_for("views.files", path=f.location, token=serialize(token))) else: files = [ url_for("views.files", path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ "id": hint.id, "cost": hint.cost, "content": hint.content }) else: hints.append({"id": hint.id, "cost": hint.cost}) response = chal_class.read(challenge=chal) Model = get_model() if scores_visible() is True and accounts_visible() is True: solves = Solves.query.join(Model, Solves.account_id == Model.id).filter( Solves.challenge_id == chal.id, Model.banned == False, Model.hidden == False, ) # Only show solves that happened before freeze time if configured freeze = get_config("freeze") if not is_admin() and freeze: solves = solves.filter(Solves.date < unix_time_to_utc(freeze)) solves = solves.count() response["solves"] = solves else: response["solves"] = None solves = None if authed(): # Get current attempts for the user attempts = Submissions.query.filter_by( account_id=user.account_id, challenge_id=challenge_id).count() else: attempts = 0 response["attempts"] = attempts response["files"] = files response["tags"] = tags response["hints"] = hints response["view"] = render_template( chal_class.templates["view"].lstrip("/"), solves=solves, files=files, tags=tags, hints=[Hints(**h) for h in hints], max_attempts=chal.max_attempts, attempts=attempts, challenge=chal, ) db.session.close() return {"success": True, "data": response}
def load(app): config(app) TEAMS_MODE = ctfd_config.is_teams_mode() if not app.config["DISCORD_WEBHOOK_URL"]: print("No DISCORD_WEBHOOK_URL set! Plugin disabled.") return print("Loading plugin discord webhook!!") def challenge_attempt_decorator(f): @wraps(f) 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) solvers = solvers.filter(Solves.user.has(hidden=False)) num_solves = solvers.count() limit = app.config["DISCORD_WEBHOOK_FIRST_SOLVES"] if limit and num_solves > int(limit): return result webhook = DiscordWebhook( url=app.config["DISCORD_WEBHOOK_URL"]) user = get_current_user() format_args = { "user": sanitize(user.name), "challenge": sanitize(challenge.name), "solves": num_solves, "fsolves": ordinal(num_solves), "category": sanitize(challenge.category), "points": challenge.value, } message = app.config["DISCORD_WEBHOOK_MESSAGE"].format( **format_args) embed = DiscordEmbed(description=message) webhook.add_embed(embed) webhook.execute() return result return wrapper app.view_functions[ "api.challenges_challenge_attempt"] = challenge_attempt_decorator( app.view_functions["api.challenges_challenge_attempt"])
def register(): errors = get_errors() if request.method == "POST": name = request.form.get("name", "").strip() email_address = request.form.get("email", "").strip().lower() password = request.form.get("password", "").strip() website = request.form.get("website") affiliation = request.form.get("affiliation") country = request.form.get("country") name_len = len(name) == 0 names = Users.query.add_columns("name", "id").filter_by(name=name).first() emails = (Users.query.add_columns( "email", "id").filter_by(email=email_address).first()) pass_short = len(password) == 0 pass_long = len(password) > 128 valid_email = validators.validate_email(email_address) team_name_email_check = validators.validate_email(name) # Process additional user fields fields = {} for field in UserFields.query.all(): fields[field.id] = field entries = {} for field_id, field in fields.items(): value = request.form.get(f"fields[{field_id}]", "").strip() if field.required is True and (value is None or value == ""): errors.append("Please provide all required fields") break # Handle special casing of existing profile fields if field.name.lower() == "affiliation": affiliation = value break elif field.name.lower() == "website": website = value break if field.field_type == "boolean": entries[field_id] = bool(value) else: entries[field_id] = value if country: try: validators.validate_country_code(country) valid_country = True except ValidationError: valid_country = False else: valid_country = True if website: valid_website = validators.validate_url(website) else: valid_website = True if affiliation: valid_affiliation = len(affiliation) < 128 else: valid_affiliation = True if not valid_email: errors.append( "Пожалуйста, введите действительный адрес электронной почты") if email.check_email_is_whitelisted(email_address) is False: errors.append( "Только адреса электронной почты ниже {domains} могут быть зарегистрированы" .format(domains=get_config("domain_whitelist"))) if names: errors.append("Этот никнейм уже используется") if team_name_email_check is True: errors.append( "Ваше имя пользователя не может быть адресом электронной почты" ) if emails: errors.append("Этот адрес электронной почты уже был использован") if pass_short: errors.append("Введите более длинный пароль") if pass_long: errors.append("Введите более короткий пароль") if name_len: errors.append("Введите более длинное имя пользователя") if valid_website is False: errors.append( "Сайт должен иметь правильный URL, начинающийся с http или https." ) if valid_country is False: errors.append("Введите существующую страна") if valid_affiliation is False: errors.append("Укажите более короткое название учреждения") if len(errors) > 0: return render_template( "register.html", errors=errors, name=request.form["name"], email=request.form["email"], password=request.form["password"], ) else: with app.app_context(): user = Users(name=name, email=email_address, password=password) if website: user.website = website if affiliation: user.affiliation = affiliation if country: user.country = country db.session.add(user) db.session.commit() db.session.flush() for field_id, value in entries.items(): entry = UserFieldEntries(field_id=field_id, value=value, user_id=user.id) db.session.add(entry) db.session.commit() login_user(user) if config.can_send_mail() and get_config( "verify_emails" ): # Confirming users is enabled and we can send email. log( "registrations", format= "[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}", ) email.verify_email_address(user.email) db.session.close() return redirect(url_for("auth.confirm")) else: # Don't care about confirming users if ( config.can_send_mail() ): # We want to notify the user that they have registered. email.successful_registration_notification(user.email) log("registrations", "[{date}] {ip} - {name} registered with {email}") db.session.close() if is_teams_mode(): return redirect(url_for("teams.private")) return redirect(url_for("challenges.listing")) else: return render_template("register.html", errors=errors)
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", }, }
def post(self): if authed() is False: return { "success": True, "data": { "status": "authentication_required" } }, 403 if ctf_paused(): return ( { "success": True, "data": { "status": "paused", "message": "{} is paused".format(config.ctf_name()), }, }, 403, ) if request.content_type != "application/json": request_data = request.form else: request_data = request.get_json() user = get_current_user() team = get_current_team() if config.is_teams_mode() and team is None: abort(403) # Anti-bruteforce / submitting Flags too quickly kpm = current_user.get_wrong_submissions_per_minute(user.account_id) if kpm > 10: log( "submissions", "[{date}] {name} submitted {submission} with kpm {kpm} [TOO FAST]", submission=request_data["submission"].encode("utf-8"), kpm=kpm, ) # Submitting too fast return ( { "success": True, "data": { "status": "ratelimited", "message": "You're submitting flags too fast. Slow down.", }, }, 429, ) challs = Challenges.query.all() for challenge in challs: correct, message = self.attempt_single(request, request_data, user, team, kpm, challenge) if correct: return { "success": True, "data": { "status": "correct", "message": message }, } chal_class = get_chal_class('standard') challenge = Challenges.query.filter_by( name='__SECRET__').first_or_404() chal_class.fail(user=user, team=team, challenge=challenge, request=request) return { "success": True, "data": { "status": "incorrect", "message": "Incorrect flag.", }, }
def login(): errors = get_errors() if request.method == "POST": login_info = { 'username': request.form["name"], 'password': request.form["password"] } # Check if the user submitted an email address or username if validators.validate_email(login_info['username']) is True: user = Users.query.filter_by(email=login_info['username']).first() # If this is the first time logging inn you need to use your username errors.append("Use your username instead of email for first login") else: user = Users.query.filter_by(name=login_info['username']).first() # Ldap credentials prep login = login_info["username"].strip().lower() login_dn = 'uid=' + login + ',' + settings['type_dn'] + ',' + settings['base_dn'] password = login_info["password"] if password.rstrip() == "": errors.append("Empty passwordfield") db.session.close() return render_template("login.html", errors=errors) try: # Connect to the ldap print("connection to ldap") server = ldap3.Server(settings['host'], port=settings['port'], use_ssl=settings["encryption"] == 'ssl', get_info=ldap3.ALL) conn = ldap3.Connection(server, user=login_dn, password=password, auto_bind='NONE', version=3, authentication='SIMPLE', client_strategy='SYNC', auto_referrals=True, check_names=True, read_only=False, lazy=False, raise_exceptions=False) # Start tls for confidentiality conn.start_tls() # Check authenticity of credentials if not conn.bind(): # I'll leave this print for troubleshooting with login. Tip: if login isn't working check 'type_dn' in settings. I assume all people are registered as 'ou=people' in the system # print("ERROR ", conn.result) errors.append("Your username or password is incorrect") log("logins", "[{date}] {ip} - submitted invalid password for {name}") db.session.close() return render_template("login.html", errors=errors) print("Connected") except Exception as e: errors.append("Can't initialze connection to " + settings['host'] + ': ' + str(e)) db.session.close() return render_template("login.html", errors=errors) # If we have gotten to this point it means that the user credentials matched an entry in ldap # Check if user has logged inn before if user: session.regenerate() login_user(user) log("logins", "[{date}] {ip} - {name} logged in") db.session.close() if request.args.get("next") and validators.is_safe_url(request.args.get("next")): return redirect(request.args.get("next")) return redirect(url_for("challenges.listing")) else: # Register the user in our system # First we get email from ldap try: ldap_request = settings["request"].format(login) conn.search(settings["base_dn"], ldap_request, attributes=["cn", "mail"]) response = conn.response except Exception as ex: errors.append("Can't get user data : " + str(ex)) conn.unbind() db.session.close() return render_template("login.html", errors=errors) try: # In some systems users have multiple entries on the same username, we search for one that has an email attribute. for entry in response: if entry["attributes"]["mail"] != []: email = entry["attributes"]["mail"][0] break conn.unbind() except KeyError as e: errors.append("Can't get field " + str(e) + " from your LDAP server") db.session.close() return render_template("login.html", errors=errors) except Exception as e: errors.append("Can't get some user fields", e) db.session.close() return render_template("login.html", errors=errors) # Add the new user to the DB with app.app_context(): # We create a random password, this won't be used and is simply here because it is required in CTFd # It is random so the account cannot be accessed by conventional loggin dummy_password = randomString(28) user = Users(name=login, email=email, password=dummy_password) db.session.add(user) db.session.commit() db.session.flush() login_user(user) log("registrations", "[{date}] {ip} - {name} registered with {email}") db.session.close() if is_teams_mode(): return redirect(url_for("teams.private")) return redirect(url_for("challenges.listing")) else: db.session.close() return render_template("login.html", errors=errors)
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 get(self, challenge_id): if is_admin(): chal = Challenges.query.filter(Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != 'hidden', Challenges.state != 'locked') ).first_or_404() chal_class = get_chal_class(chal.type) if chal.requirements: requirements = chal.requirements.get('prerequisites', []) anonymize = chal.requirements.get('anonymize') if challenges_visible(): user = get_current_user() if user: solve_ids = Solves.query \ .with_entities(Solves.challenge_id) \ .filter_by(account_id=user.account_id) \ .order_by(Solves.challenge_id.asc()) \ .all() else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = set([value for value, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { 'success': True, 'data': { 'id': chal.id, 'type': 'hidden', 'name': '???', 'value': 0, 'category': '???', 'tags': [], 'template': '', 'script': '' } } abort(403) else: abort(403) tags = [ tag['value'] for tag in TagSchema( "user", many=True).dump( chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = set([ u.target for u in HintUnlocks.query.filter_by(type='hints', account_id=user.account_id) ]) files = [] for f in chal.files: token = { 'user_id': user.id, 'team_id': team.id if team else None, 'file_id': f.id, } files.append( url_for('views.files', path=f.location, token=serialize(token)) ) else: files = [ url_for('views.files', path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ 'id': hint.id, 'cost': hint.cost, 'content': hint.content }) else: hints.append({'id': hint.id, 'cost': hint.cost}) response = chal_class.read(challenge=chal) Model = get_model() if scores_visible() is True and accounts_visible() is True: solves = Solves.query\ .join(Model, Solves.account_id == Model.id)\ .filter(Solves.challenge_id == chal.id, Model.banned == False, Model.hidden == False)\ .count() response['solves'] = solves else: response['solves'] = None response['files'] = files response['tags'] = tags response['hints'] = hints db.session.close() return { 'success': True, 'data': response }
def register(): errors = get_errors() if current_user.authed(): return redirect(url_for("challenges.listing")) if request.method == "POST": name = request.form.get("name", "").strip() email_address = request.form.get("email", "").strip().lower() password = request.form.get("password", "").strip() website = request.form.get("website") affiliation = request.form.get("affiliation") country = request.form.get("country") registration_code = request.form.get("registration_code", "") name_len = len(name) == 0 names = Users.query.add_columns("name", "id").filter_by(name=name).first() emails = (Users.query.add_columns( "email", "id").filter_by(email=email_address).first()) pass_short = len(password) == 0 pass_long = len(password) > 128 valid_email = validators.validate_email(email_address) team_name_email_check = validators.validate_email(name) if get_config("registration_code"): if (registration_code.lower() != get_config("registration_code", default="").lower()): errors.append( "The registration code you entered was incorrect") # Process additional user fields fields = {} for field in UserFields.query.all(): fields[field.id] = field entries = {} for field_id, field in fields.items(): value = request.form.get(f"fields[{field_id}]", "").strip() if field.required is True and (value is None or value == ""): errors.append("Please provide all required fields") break # Handle special casing of existing profile fields if field.name.lower() == "affiliation": affiliation = value break elif field.name.lower() == "website": website = value break if field.field_type == "boolean": entries[field_id] = bool(value) else: entries[field_id] = value if country: try: validators.validate_country_code(country) valid_country = True except ValidationError: valid_country = False else: valid_country = True if website: valid_website = validators.validate_url(website) else: valid_website = True if affiliation: valid_affiliation = len(affiliation) < 128 else: valid_affiliation = True if not valid_email: errors.append("Please enter a valid email address") if email.check_email_is_whitelisted(email_address) is False: errors.append( "Only email addresses under {domains} may register".format( domains=get_config("domain_whitelist"))) if names: errors.append("That user name is already taken") if team_name_email_check is True: errors.append("Your user name cannot be an email address") if emails: errors.append("That email has already been used") if pass_short: errors.append("Pick a longer password") if pass_long: errors.append("Pick a shorter password") if name_len: errors.append("Pick a longer user name") if valid_website is False: errors.append( "Websites must be a proper URL starting with http or https") if valid_country is False: errors.append("Invalid country") if valid_affiliation is False: errors.append("Please provide a shorter affiliation") if len(errors) > 0: return render_template( "register.html", errors=errors, name=request.form["name"], email=request.form["email"], password=request.form["password"], ) else: with app.app_context(): user = Users(name=name, email=email_address, password=password) if website: user.website = website if affiliation: user.affiliation = affiliation if country: user.country = country db.session.add(user) db.session.commit() db.session.flush() for field_id, value in entries.items(): entry = UserFieldEntries(field_id=field_id, value=value, user_id=user.id) db.session.add(entry) db.session.commit() login_user(user) if request.args.get("next") and validators.is_safe_url( request.args.get("next")): return redirect(request.args.get("next")) if config.can_send_mail() and get_config( "verify_emails" ): # Confirming users is enabled and we can send email. log( "registrations", format= "[{date}] {ip} - {name} registered (UNCONFIRMED) with {email}", name=user.name, email=user.email, ) email.verify_email_address(user.email) db.session.close() return redirect(url_for("auth.confirm")) else: # Don't care about confirming users if ( config.can_send_mail() ): # We want to notify the user that they have registered. email.successful_registration_notification(user.email) log( "registrations", format="[{date}] {ip} - {name} registered with {email}", name=user.name, email=user.email, ) db.session.close() if is_teams_mode(): return redirect(url_for("teams.private")) return redirect(url_for("challenges.listing")) else: return render_template("register.html", errors=errors)
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} with kpm {kpm} [TOO FAST]", submission=request_data['submission'].encode('utf-8'), 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} with kpm {kpm} [CORRECT]", submission=request_data['submission'].encode('utf-8'), 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} with kpm {kpm} [WRONG]", submission=request_data['submission'].encode('utf-8'), 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} with kpm {kpm} [ALREADY SOLVED]", submission=request_data['submission'].encode('utf-8'), kpm=kpm ) return { 'success': True, 'data': { 'status': "already_solved", 'message': 'You already solved this' } }
def get(self, challenge_id): if is_admin(): chal = Challenges.query.filter( Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() chal_class = get_chal_class(chal.type) if chal.requirements: requirements = chal.requirements.get("prerequisites", []) anonymize = chal.requirements.get("anonymize") if challenges_visible(): user = get_current_user() if user: solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = set([value for value, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { "success": True, "data": { "id": chal.id, "type": "hidden", "name": "???", "value": 0, "category": "???", "tags": [], "template": "", "script": "", }, } abort(403) else: abort(403) tags = [ tag["value"] for tag in TagSchema("user", many=True).dump(chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = set([ u.target for u in HintUnlocks.query.filter_by( type="hints", account_id=user.account_id) ]) files = [] for f in chal.files: token = { "user_id": user.id, "team_id": team.id if team else None, "file_id": f.id, } files.append( url_for("views.files", path=f.location, token=serialize(token))) else: files = [ url_for("views.files", path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ "id": hint.id, "cost": hint.cost, "content": hint.content }) else: hints.append({"id": hint.id, "cost": hint.cost}) response = chal_class.read(challenge=chal) Model = get_model() if scores_visible() is True and accounts_visible() is True: solves = (Solves.query.join(Model, Solves.account_id == Model.id).filter( Solves.challenge_id == chal.id, Model.banned == False, Model.hidden == False, ).count()) response["solves"] = solves else: response["solves"] = None response["files"] = files response["tags"] = tags response["hints"] = hints db.session.close() return {"success": True, "data": response}
def get(self): # This can return None (unauth) if visibility is set to public user = get_current_user() challenges = Challenges.query.filter( and_(Challenges.state != 'hidden', Challenges.state != 'locked') ).order_by(Challenges.value).all() if user: 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([value for value, in solve_ids]) # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and get_current_team() is None: abort(403) else: solve_ids = set() response = [] tag_schema = TagSchema(view='user', many=True) for challenge in challenges: if challenge.requirements: requirements = challenge.requirements.get('prerequisites', []) anonymize = challenge.requirements.get('anonymize') prereqs = set(requirements) if solve_ids >= prereqs: pass else: if anonymize: response.append({ 'id': challenge.id, 'type': 'hidden', 'name': '???', 'value': 0, 'category': '???', 'tags': [], 'template': '', 'script': '' }) # Fallthrough to continue continue challenge_type = get_chal_class(challenge.type) response.append({ 'id': challenge.id, 'type': challenge_type.name, 'name': challenge.name, 'value': challenge.value, 'category': challenge.category, 'tags': tag_schema.dump(challenge.tags).data, 'template': challenge_type.templates['view'], 'script': challenge_type.scripts['view'], }) db.session.close() return { 'success': True, 'data': response }
def get(self): # This can return None (unauth) if visibility is set to public user = get_current_user() challenges = (Challenges.query.filter( and_(Challenges.state != "hidden", Challenges.state != "locked")).order_by(Challenges.id).all()) if user: 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([value for value, in solve_ids]) # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and get_current_team() is None: abort(403) else: solve_ids = set() response = [] tag_schema = TagSchema(view="user", many=True) for challenge in challenges: if challenge.requirements: requirements = challenge.requirements.get("prerequisites", []) anonymize = challenge.requirements.get("anonymize") prereqs = set(requirements) if solve_ids >= prereqs: pass else: if anonymize: response.append({ "id": challenge.id, "type": "hidden", "name": "???", "value": 0, "category": "???", "tags": [], "template": "", "script": "", }) # Fallthrough to continue continue challenge_type = get_chal_class(challenge.type) response.append({ "id": challenge.id, "type": challenge_type.name, "name": challenge.name, "value": challenge.value, "category": challenge.category, "tags": tag_schema.dump(challenge.tags).data, "template": challenge_type.templates["view"], "script": challenge_type.scripts["view"], }) db.session.close() return {"success": True, "data": response}
def get(self, challenge_id): challenges = Challenges.query.order_by(Challenges.category, Challenges.value) chal_matrix = [[0] for j in range(50)] i = 0 current_category = False for ch in challenges: if not current_category or ch.category != current_category: current_category = ch.category i += 1 chal_matrix[i].append(ch.id) if is_admin(): chal = Challenges.query.filter( Challenges.id == challenge_id).first_or_404() else: chal = Challenges.query.filter( Challenges.id == challenge_id, and_(Challenges.state != "hidden", Challenges.state != "locked"), ).first_or_404() chal_class = get_chal_class(chal.type) user = get_current_user() if not user: abort(403) 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 = [value for value, in solve_ids] # solve_ids.append(0) # Чтобы не проверять, что таск находится в самом верху или слева. # Проверяем таски по матрице chal_status = chal_is_available(chal.id, chal_matrix, solve_ids) if type(chal_status) == dict: return chal_status if chal.requirements: requirements = chal.requirements.get("prerequisites", []) anonymize = chal.requirements.get("anonymize") if challenges_visible(): user = get_current_user() if user: solve_ids = (Solves.query.with_entities( Solves.challenge_id).filter_by( account_id=user.account_id).order_by( Solves.challenge_id.asc()).all()) else: # We need to handle the case where a user is viewing challenges anonymously solve_ids = [] solve_ids = set([value for value, in solve_ids]) prereqs = set(requirements) if solve_ids >= prereqs or is_admin(): pass else: if anonymize: return { "success": True, "data": { "id": chal.id, "type": "hidden", "name": "???", "value": 0, "category": "???", "tags": [], "template": "", "script": "", }, } abort(403) else: abort(403) tags = [ tag["value"] for tag in TagSchema("user", many=True).dump(chal.tags).data ] unlocked_hints = set() hints = [] if authed(): user = get_current_user() team = get_current_team() # TODO: Convert this into a re-useable decorator if is_admin(): pass else: if config.is_teams_mode() and team is None: abort(403) unlocked_hints = set([ u.target for u in HintUnlocks.query.filter_by( type="hints", account_id=user.account_id) ]) files = [] for f in chal.files: token = { "user_id": user.id, "team_id": team.id if team else None, "file_id": f.id, } files.append( url_for("views.files", path=f.location, token=serialize(token))) else: files = [ url_for("views.files", path=f.location) for f in chal.files ] for hint in Hints.query.filter_by(challenge_id=chal.id).all(): if hint.id in unlocked_hints or ctf_ended(): hints.append({ "id": hint.id, "cost": hint.cost, "content": hint.content }) else: hints.append({"id": hint.id, "cost": hint.cost}) response = chal_class.read(challenge=chal) Model = get_model() if scores_visible() is True and accounts_visible() is True: solves = Solves.query.join(Model, Solves.account_id == Model.id).filter( Solves.challenge_id == chal.id, Model.banned == False, Model.hidden == False, ) # Only show solves that happened before freeze time if configured freeze = get_config("freeze") if not is_admin() and freeze: solves = solves.filter(Solves.date < unix_time_to_utc(freeze)) solves = solves.count() response["solves"] = solves else: response["solves"] = None response["files"] = files response["tags"] = tags response["hints"] = hints db.session.close() return {"success": True, "data": response}