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 unique_email(email, model=Users): obj = model.query.filter_by(email=email).first() if is_admin(): if obj: raise ValidationError("Email address has already been used") if obj and obj.id != get_current_user().id: raise ValidationError("Email address has already been used")
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 validate_email(self, data): email = data.get("email") if email is None: return email = email.strip() existing_user = Users.query.filter_by(email=email).first() current_user = get_current_user() if is_admin(): user_id = data.get("id") if user_id: if existing_user and existing_user.id != user_id: raise ValidationError( "Email address has already been used", field_names=["email"]) else: if existing_user: if current_user: if current_user.id != existing_user.id: raise ValidationError( "Email address has already been used", field_names=["email"], ) else: raise ValidationError( "Email address has already been used", field_names=["email"]) else: if email == current_user.email: return data else: confirm = data.get("confirm") if bool(confirm) is False: raise ValidationError( "Please confirm your current password", field_names=["confirm"]) test = verify_password(plaintext=confirm, ciphertext=current_user.password) if test is False: raise ValidationError( "Your previous password is incorrect", field_names=["confirm"]) if existing_user: raise ValidationError( "Email address has already been used", field_names=["email"]) if check_email_is_whitelisted(email) is False: raise ValidationError( "Only email addresses under {domains} may register". format(domains=get_config("domain_whitelist")), field_names=["email"], ) if get_config("verify_emails"): current_user.verified = False
def get(self): user = get_current_user() tokens = Tokens.query.filter_by(user_id=user.id) response = TokenSchema(view=["id", "type", "expiration"], many=True).dump(tokens) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def get(self): user = get_current_user() awards = user.get_awards(admin=True) view = "user" if not is_admin() else "admin" response = AwardSchema(view=view, many=True).dump(awards) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def get(self): user = get_current_user() solves = user.get_solves(admin=True) view = "user" if not is_admin() else "admin" response = SubmissionSchema(view=view, many=True).dump(solves) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def delete(self, token_id): if is_admin(): token = Tokens.query.filter_by(id=token_id).first_or_404() else: user = get_current_user() token = Tokens.query.filter_by(id=token_id, user_id=user.id).first_or_404() db.session.delete(token) db.session.commit() db.session.close() return {"success": True}
def post(self): req = request.get_json() user = get_current_user() req["user_id"] = user.id req["team_id"] = user.team_id Model = get_class_by_tablename(req["type"]) target = Model.query.filter_by(id=req["target"]).first_or_404() if target.cost > user.score: return ( { "success": False, "errors": { "score": "You do not have enough points to unlock this hint" }, }, 400, ) schema = UnlockSchema() response = schema.load(req, session=db.session) if response.errors: return {"success": False, "errors": response.errors}, 400 db.session.add(response.data) award_schema = AwardSchema() award = { "user_id": user.id, "team_id": user.team_id, "name": target.name, "description": target.description, "value": (-target.cost), "category": target.category, } award = award_schema.load(award) db.session.add(award.data) db.session.commit() clear_standings() response = schema.dump(response.data) return {"success": True, "data": response.data}
def get(self): user = get_current_user() fails = user.get_fails(admin=True) view = "user" if not is_admin() else "admin" response = SubmissionSchema(view=view, many=True).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 patch(self): user = get_current_user() data = request.get_json() schema = UserSchema(view="self", instance=user, partial=True) response = schema.load(data) if response.errors: return {"success": False, "errors": response.errors}, 400 db.session.commit() response = schema.dump(response.data) db.session.close() clear_standings() return {"success": True, "data": response.data}
def post(self): req = request.get_json() expiration = req.get("expiration") if expiration: expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d") user = get_current_user() token = generate_user_token(user, expiration=expiration) # Explicitly use admin view so that user's can see the value of their token schema = TokenSchema(view="admin") response = schema.dump(token) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def private(): user = get_current_user() solves = user.get_solves() awards = user.get_awards() place = user.place score = user.score return render_template( "users/private.html", solves=solves, awards=awards, user=user, score=score, place=place, score_frozen=config.is_scoreboard_frozen(), )
def join(): infos = get_infos() errors = get_errors() if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append( "Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=plural ) ) return render_template("teams/join_team.html", infos=infos, errors=errors) if request.method == "POST": teamname = request.form.get("name") passphrase = request.form.get("password", "").strip() team = Teams.query.filter_by(name=teamname).first() user = get_current_user() if team and verify_password(passphrase, team.password): team_size_limit = get_config("team_size", default=0) if team_size_limit and len(team.members) >= team_size_limit: errors.append( "{name} has already reached the team size limit of {limit}".format( name=team.name, limit=team_size_limit ) ) return render_template( "teams/join_team.html", infos=infos, errors=errors ) user.team_id = team.id db.session.commit() if len(team.members) == 1: team.captain_id = user.id db.session.commit() return redirect(url_for("challenges.listing")) else: errors.append("That information is incorrect") return render_template("teams/join_team.html", infos=infos, errors=errors)
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, hint_id): user = get_current_user() hint = Hints.query.filter_by(id=hint_id).first_or_404() view = "unlocked" if hint.cost: view = "locked" unlocked = HintUnlocks.query.filter_by(account_id=user.account_id, target=hint.id).first() if unlocked: view = "unlocked" if is_admin(): if request.args.get("preview", False): view = "admin" response = HintSchema(view=view).dump(hint) if response.errors: return {"success": False, "errors": response.errors}, 400 return {"success": True, "data": response.data}
def new(): infos = get_infos() errors = get_errors() if request.method == "GET": team_size_limit = get_config("team_size", default=0) if team_size_limit: plural = "" if team_size_limit == 1 else "s" infos.append( "Teams are limited to {limit} member{plural}".format( limit=team_size_limit, plural=plural ) ) return render_template("teams/new_team.html", infos=infos, errors=errors) elif request.method == "POST": teamname = request.form.get("name", "").strip() passphrase = request.form.get("password", "").strip() errors = get_errors() user = get_current_user() existing_team = Teams.query.filter_by(name=teamname).first() if existing_team: errors.append("That team name is already taken") if not teamname: errors.append("That team name is invalid") if errors: return render_template("teams/new_team.html", errors=errors) team = Teams(name=teamname, password=passphrase, captain_id=user.id) db.session.add(team) db.session.commit() user.team_id = team.id db.session.commit() return redirect(url_for("challenges.listing"))
def settings(): user = get_current_user() name = user.name email = user.email website = user.website affiliation = user.affiliation country = user.country tokens = UserTokens.query.filter_by(user_id=user.id).all() prevent_name_change = get_config("prevent_name_change") confirm_email = get_config("verify_emails") and not user.verified return render_template( "settings.html", name=name, email=email, website=website, affiliation=affiliation, country=country, tokens=tokens, prevent_name_change=prevent_name_change, confirm_email=confirm_email, )
def private(): user = get_current_user() if not user.team_id: return render_template("teams/team_enrollment.html") team_id = user.team_id team = Teams.query.filter_by(id=team_id).first_or_404() solves = team.get_solves() awards = team.get_awards() place = team.place score = team.score return render_template( "teams/private.html", solves=solves, awards=awards, user=user, team=team, score=score, place=place, score_frozen=config.is_scoreboard_frozen(), )
def validate_name(self, data): name = data.get("name") if name is None: return name = name.strip() existing_user = Users.query.filter_by(name=name).first() current_user = get_current_user() if is_admin(): user_id = data.get("id") if user_id: if existing_user and existing_user.id != user_id: raise ValidationError("User name has already been taken", field_names=["name"]) else: if existing_user: if current_user: if current_user.id != existing_user.id: raise ValidationError( "User name has already been taken", field_names=["name"]) else: raise ValidationError( "User name has already been taken", field_names=["name"]) else: if name == current_user.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_user: raise ValidationError("User name has already been taken", field_names=["name"])
def validate_password_confirmation(self, data): password = data.get("password") confirm = data.get("confirm") target_user = get_current_user() if is_admin(): pass else: 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=target_user.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): # 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}
def get(self): user = get_current_user() response = UserSchema("self").dump(user).data response["place"] = user.place response["score"] = user.score return {"success": True, "data": response}