def validate_captain_id(self, data): captain_id = data.get("captain_id") if captain_id is None: return if is_admin(): team_id = data.get("id") if team_id: target_team = Teams.query.filter_by(id=team_id).first() else: target_team = get_current_team() captain = Users.query.filter_by(id=captain_id).first() if captain in target_team.members: return else: raise ValidationError("Invalid Captain ID", field_names=["captain_id"]) else: current_team = get_current_team() current_user = get_current_user() if current_team.captain_id == current_user.id: return else: raise ValidationError( "Only the captain can change team captain", field_names=["captain_id"], )
def patch(self): team = get_current_team() if team.captain_id != session["id"]: return ( { "success": False, "errors": { "": ["Only team captains can edit team information"] }, }, 403, ) data = request.get_json() response = TeamSchema(view="self", instance=team, partial=True).load(data) if response.errors: return {"success": False, "errors": response.errors}, 400 db.session.commit() response = TeamSchema("self").dump(response.data) db.session.close() return {"success": True, "data": response.data}
def validate_email(self, data): email = data.get("email") if email is None: return existing_team = Teams.query.filter_by(email=email).first() if is_admin(): team_id = data.get("id") if team_id: if existing_team and existing_team.id != team_id: raise ValidationError( "Email address has already been used", field_names=["email"] ) else: if existing_team: raise ValidationError( "Email address has already been used", field_names=["email"] ) else: current_team = get_current_team() if email == current_team.email: return data else: if existing_team: raise ValidationError( "Email address has already been used", field_names=["email"] )
def validate_password_confirmation(self, data): password = data.get("password") confirm = data.get("confirm") if is_admin(): pass else: current_team = get_current_team() current_user = get_current_user() if current_team.captain_id != current_user.id: raise ValidationError( "Only the captain can change the team password", field_names=["captain_id"], ) if password and (bool(confirm) is False): raise ValidationError( "Please confirm your current password", field_names=["confirm"] ) if password and confirm: test = verify_password( plaintext=confirm, ciphertext=current_team.password ) if test is True: return data else: raise ValidationError( "Your previous password is incorrect", field_names=["confirm"] ) else: data.pop("password", None) data.pop("confirm", None)
def get(self): team = get_current_team() response = TeamSchema(view="self").dump(team) if response.errors: return {"success": False, "errors": response.errors}, 400 response.data["place"] = team.place response.data["score"] = team.score return {"success": True, "data": response.data}
def require_team_wrapper(*args, **kwargs): if get_config("user_mode") == TEAMS_MODE: team = get_current_team() if team is None: if request.content_type == "application/json": abort(403) else: return redirect( url_for("teams.private", next=request.full_path)) return f(*args, **kwargs)
def get(self): team = get_current_team() awards = team.get_awards(admin=True) schema = AwardSchema(many=True) response = schema.dump(awards) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def get(self): team = get_current_team() solves = team.get_solves(admin=True) view = "admin" if is_admin() else "user" schema = SubmissionSchema(view=view, many=True) response = schema.dump(solves) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def get(self): team = get_current_team() fails = team.get_fails(admin=True) view = "admin" if is_admin() else "user" schema = SubmissionSchema(view=view, many=True) response = schema.dump(fails) if response.errors: return {"success": False, "errors": response.errors}, 400 if is_admin(): data = response.data else: data = [] count = len(response.data) return {"success": True, "data": data, "meta": {"count": count}}
def validate_name(self, data): name = data.get("name") if name is None: return name = name.strip() existing_team = Teams.query.filter_by(name=name).first() current_team = get_current_team() # Admins should be able to patch anyone but they cannot cause a collision. if is_admin(): team_id = int(data.get("id", 0)) if team_id: if existing_team and existing_team.id != team_id: raise ValidationError( "Team name has already been taken", field_names=["name"] ) else: # If there's no Team ID it means that the admin is creating a team with no ID. if existing_team: if current_team: if current_team.id != existing_team.id: raise ValidationError( "Team name has already been taken", field_names=["name"] ) else: raise ValidationError( "Team name has already been taken", field_names=["name"] ) else: # We need to allow teams to edit themselves and allow the "conflict" if data["name"] == current_team.name: return data else: name_changes = get_config("name_changes", default=True) if bool(name_changes) is False: raise ValidationError( "Name changes are disabled", field_names=["name"] ) if existing_team: raise ValidationError( "Team name has already been taken", field_names=["name"] )
def tracker(): if request.endpoint == "views.themes": return if authed(): track = Tracking.query.filter_by(ip=get_ip(), user_id=session["id"]).first() if not track: visit = Tracking(ip=get_ip(), user_id=session["id"]) db.session.add(visit) else: track.date = datetime.datetime.utcnow() try: db.session.commit() except (InvalidRequestError, IntegrityError): db.session.rollback() logout_user() if authed(): user = get_current_user() team = get_current_team() if request.path.startswith("/themes") is False: if user and user.banned: return ( render_template( "errors/403.html", error="You have been banned from this CTF", ), 403, ) if team and team.banned: return ( render_template( "errors/403.html", error="Your team has been banned from this CTF", ), 403, ) db.session.close()
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 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["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["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["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["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, ) # 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}