def confirm(data=None): if not get_config("verify_emails"): # If the CTF doesn't care about confirming email addresses then redierct to challenges return redirect(url_for("challenges.listing")) # User is confirming email account if data and request.method == "GET": try: user_email = unserialize(data, max_age=1800) except (BadTimeSignature, SignatureExpired): return render_template( "confirm.html", errors=["Your confirmation link has expired"]) except (BadSignature, TypeError, base64.binascii.Error): return render_template( "confirm.html", errors=["Your confirmation token is invalid"]) user = Users.query.filter_by(email=user_email).first_or_404() if user.verified: return redirect(url_for("views.settings")) user.verified = True log( "registrations", format="[{date}] {ip} - successful confirmation for {name}", name=user.name, ) db.session.commit() email.successful_registration_notification(user.email) db.session.close() if current_user.authed(): return redirect(url_for("challenges.listing")) return redirect(url_for("auth.login")) # User is trying to start or restart the confirmation flow if not current_user.authed(): return redirect(url_for("auth.login")) user = Users.query.filter_by(id=session["id"]).first_or_404() if user.verified: return redirect(url_for("views.settings")) if data is None: if request.method == "POST": # User wants to resend their confirmation email email.verify_email_address(user.email) log( "registrations", format= "[{date}] {ip} - {name} initiated a confirmation email resend", ) return render_template( "confirm.html", user=user, infos=["Your confirmation email has been resent!"], ) elif request.method == "GET": # User has been directed to the confirm page return render_template("confirm.html", user=user)
def _check_score_visibility(*args, **kwargs): v = get_config("score_visibility") if v == "public": return f(*args, **kwargs) elif v == "private": if authed(): return f(*args, **kwargs) else: if request.content_type == "application/json": abort(403) else: return redirect( url_for("auth.login", next=request.full_path)) elif v == "hidden": return ( render_template("errors/403.html", error="Scores are currently hidden"), 403, ) elif v == "admins": if is_admin(): return f(*args, **kwargs) else: abort(404)
def challenges_visible(): v = get_config("challenge_visibility") if v == "public": return True elif v == "private": return authed() elif v == "admins": return is_admin()
def accounts_visible(): v = get_config("account_visibility") if v == "public": return True elif v == "private": return authed() elif v == "admins": return is_admin()
def authed_only_wrapper(*args, **kwargs): if authed(): return f(*args, **kwargs) else: if (request.content_type == "application/json" or request.accept_mimetypes.best == "text/event-stream"): abort(403) else: return redirect(url_for("auth.login", next=request.full_path))
def scores_visible(): v = get_config("score_visibility") if v == "public": return True elif v == "private": return authed() elif v == "hidden": return False elif v == "admins": return is_admin()
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 _require_verified_emails(*args, **kwargs): if get_config("verify_emails"): if current_user.authed(): if (current_user.is_admin() is False and current_user.is_verified() is False): # User is not confirmed if request.content_type == "application/json": abort(403) else: return redirect(url_for("auth.confirm")) return f(*args, **kwargs)
def static_html(route): """ Route in charge of routing users to Pages. :param route: :return: """ page = get_page(route) if page is None: abort(404) else: if page.auth_required and authed() is False: return redirect(url_for("auth.login", next=request.full_path)) return render_template("page.html", content=markdown(page.content))
def _check_challenge_visibility(*args, **kwargs): v = get_config("challenge_visibility") if v == "public": return f(*args, **kwargs) elif v == "private": if authed(): return f(*args, **kwargs) else: if request.content_type == "application/json": abort(403) else: return redirect( url_for("auth.login", next=request.full_path)) elif v == "admins": if is_admin(): return f(*args, **kwargs) else: if authed(): abort(403) else: return redirect( url_for("auth.login", next=request.full_path))
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 __require_authentication_if_config(*args, **kwargs): value = get_config(config_key) if value and current_user.authed(): return redirect(url_for("auth.login", next=request.full_path)) else: return f(*args, **kwargs)
def logout(): if current_user.authed(): logout_user() return redirect(url_for("views.static_html"))