Пример #1
0
    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}
Пример #2
0
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
Пример #3
0
    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}
Пример #4
0
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)
Пример #5
0
 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
Пример #6
0
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)
Пример #7
0
    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",
                },
            }
Пример #8
0
    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}
Пример #9
0
    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}
Пример #10
0
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"])
Пример #11
0
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)
Пример #12
0
    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",
                },
            }
Пример #13
0
    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.",
            },
        }
Пример #14
0
    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)
Пример #15
0
    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",
                },
            }
Пример #16
0
    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
        }
Пример #17
0
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)
Пример #18
0
    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'
                }
            }
Пример #19
0
    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}
Пример #20
0
    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
        }
Пример #21
0
    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}
Пример #22
0
    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}