Exemplo n.º 1
0
def files(path):
    """
    Route in charge of dealing with making sure that CTF challenges are only accessible during the competition.
    :param path:
    :return:
    """
    f = Files.query.filter_by(location=path).first_or_404()
    if f.type == 'challenge':
        if challenges_visible():
            if current_user.is_admin() is False:
                if not ctftime():
                    abort(403)
        else:
            if not ctftime():
                abort(403)

            # Allow downloads if a valid token is provided
            token = request.args.get('token', '')
            try:
                data = unserialize(token, max_age=3600)
                user_id = data.get('user_id')
                team_id = data.get('team_id')
                file_id = data.get('file_id')
                user = Users.query.filter_by(id=user_id).first()
                team = Teams.query.filter_by(id=team_id).first()

                # Check user is admin if challenge_visibility is admins only
                if get_config('challenge_visibility'
                              ) == 'admins' and user.type != 'admin':
                    abort(403)

                # Check that the user exists and isn't banned
                if user:
                    if user.banned:
                        abort(403)
                else:
                    abort(403)

                # Check that the team isn't banned
                if team:
                    if team.banned:
                        abort(403)
                else:
                    pass

                # Check that the token properly refers to the file
                if file_id != f.id:
                    abort(403)

            # The token isn't expired or broken
            except (BadTimeSignature, SignatureExpired, BadSignature):
                abort(403)

    uploader = get_uploader()
    try:
        return uploader.download(f.location)
    except IOError:
        abort(404)
Exemplo n.º 2
0
    def attempt_single(self, request, request_data, user, team, kpm,
                       challenge):
        challenge_id = challenge.id

        if challenge.state == "hidden":
            return False, ''

        if challenge.state == "locked":
            return False, ''

        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:
                return False, ''

        chal_class = get_chal_class(challenge.type)

        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)
            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 True, message
            else:  # The challenge plugin says the input is wrong
                if ctftime() or current_user.is_admin():
                    clear_standings()

                return False, message

        # Challenge already solved
        else:
            return False, ''
    def get_challenges():
        if not is_admin():
            if not ctftime():
                if view_after_ctf():
                    pass
                else:
                    return []
        if challenges_visible() and (ctf_started() or is_admin()):
            chals = db.session.query(Challenges.id, Challenges.name,
                                     Challenges.category).filter(
                                         or_(Challenges.state != 'hidden',
                                             Challenges.state is None)).all()
            jchals = []
            for x in chals:
                jchals.append({
                    'id': x.id,
                    'name': x.name,
                    'category': x.category
                })

            # Sort into groups
            categories = set(map(lambda x: x['category'], jchals))
            jchals = [
                j for c in categories for j in jchals if j['category'] == c
            ]
            return jchals
        return []
Exemplo n.º 4
0
def rand_unlock_callback():
    with APP_REF.app_context():
        if not ctftime():
            log('lah', "[{date}] unlocking did not run because ctf has not started")
            return
        if datetime.datetime.utcnow().minute not in RAND_UNLOCK_MINUTES:
            log('lah', "[{date}] unlocking did not run because minute is not aligned")
            return
        for i in range(RAND_UNLOCK_QUESTIONS):
            # Unlock one random question, that is visible, not unlocked, and of the lowest available unlock_order
            min_order = db.session.query(
                            func.min(LahChallenge.unlock_order).label("min_order"),
                            func.count().label("count")
                        ).filter(
                            LahChallenge.state == "visible",
                            LahChallenge.is_unlocked == False,
                            LahChallenge.unlock_order > 0,
                        ).one()
            count = min_order.count
            order = min_order.min_order
            if not min_order or count == 0:
                log('lah', "[{date}] unlocking finished early because no locked challenges were found.")
                return
            rand_offset = randrange(count)
            challenge = LahChallenge.query.filter_by(unlock_order=order, is_unlocked=False, state="visible").order_by(LahChallenge.id).offset(rand_offset).first()
            if not challenge:
                log('lah', "[{date}] encountered invalid state: randomly selected challenge was None.")
            challenge.is_unlocked = True
            db.session.commit()
            log('lah', "[{date}] unlocked challenge '{chal}'", chal=challenge.name)
Exemplo n.º 5
0
    def during_ctf_time_only_wrapper(*args, **kwargs):
        if ctftime() or current_user.is_admin():
            return f(*args, **kwargs)
        else:
            if ctf_ended():
                error = '{} has ended'.format(config.ctf_name())
                abort(403, description=error)

            if ctf_started() is False:
                error = '{} has not started yet'.format(config.ctf_name())
                abort(403, description=error)
Exemplo n.º 6
0
 def during_ctf_time_only_wrapper(*args, **kwargs):
     if ctftime() or current_user.is_admin():
         return f(*args, **kwargs)
     else:
         if ctf_ended():
             if view_after_ctf():
                 return f(*args, **kwargs)
             else:
                 error = '{} 已经结束'.format(config.ctf_name())
                 abort(403, description=error)
         if ctf_started() is False:
             error = '{} 尚未开始'.format(config.ctf_name())
             abort(403, description=error)
Exemplo n.º 7
0
        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)
                    if TEAMS_MODE:
                        solvers = solvers.filter(Solves.team.has(hidden=False))
                    else:
                        solvers = solvers.filter(Solves.user.has(hidden=False))
                    num_solves = solvers.count()

                    limit = app.config["DISCORD_WEBHOOK_LIMIT"]
                    if limit and num_solves > int(limit):
                        return result
                    webhook = DiscordWebhook(
                        url=app.config['DISCORD_WEBHOOK_URL'])

                    user = get_current_user()
                    team = get_current_team()

                    format_args = {
                        "team": sanitize(team.name),
                        "user": sanitize(user.name),
                        "challenge": sanitize(challenge.name),
                        "solves": num_solves,
                        "fsolves": ordinal(num_solves),
                        "category": sanitize(challenge.category)
                    }

                    message = app.config['DISCORD_WEBHOOK_MESSAGE'].format(
                        **format_args)
                    embed = DiscordEmbed(description=message)
                    webhook.add_embed(embed)
                    webhook.execute()
            return result
Exemplo n.º 8
0
def static_html(route):
    """
    Route in charge of routing users to Pages.
    :param route:
    :return:
    """
    page = get_page(route)
    if page is None:
        if (ctftime() or current_user.is_admin()
                or (ctf_ended() and view_after_ctf())):
            filename = safe_join(app.root_path, "static", route)
            if os.path.isfile(filename):
                return send_file(filename)
        abort(404)
    else:
        if page.auth_required and authed() is False:
            return redirect(url_for("auth.login", next=request.full_path))

        return render_template("page.html", content=markdown(page.content))
Exemplo n.º 9
0
def files(path):
    """
    Route in charge of dealing with making sure that CTF challenges are only accessible during the competition.
    :param path:
    :return:
    """
    f = Files.query.filter_by(location=path).first_or_404()
    if f.type == 'challenge':
        if current_user.authed():
            if current_user.is_admin() is False:
                if not ctftime():
                    abort(403)
        else:
            abort(403)

    uploader = get_uploader()
    try:
        return uploader.download(f.location)
    except IOError:
        abort(404)
Exemplo n.º 10
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} 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
            return (
                {
                    "success": True,
                    "data": {
                        "status": "ratelimited",
                        "message": "You're submitting flags too fast. Slow down.",
                    },
                },
                429,
            )

        solves = Solves.query.filter_by(
            account_id=user.account_id, challenge_id=challenge_id
        ).first()

        # Challenge not solved yet
        if not solves:
            # Hit max attempts
            max_tries = challenge.max_attempts
            if max_tries and fails >= max_tries > 0:
                return (
                    {
                        "success": True,
                        "data": {
                            "status": "incorrect",
                            "message": "You have 0 tries remaining",
                        },
                    },
                    403,
                )

            status, message = chal_class.attempt(challenge, request)
            if status:  # The challenge plugin says the input is right
                if ctftime() or current_user.is_admin():
                    chal_class.solve(
                        user=user, team=team, challenge=challenge, request=request
                    )
                    clear_standings()

                log(
                    "submissions",
                    "[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [CORRECT]",
                    submission=request_data.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]",
                    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]",
                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",
                },
            }
Exemplo n.º 11
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': '{} приостановлены'.format(config.ctf_name())
                }
            }, 403

        user = get_current_user()
        team = get_current_team()

        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
        if current_user.get_wrong_submissions_per_minute(session['id']) > 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=current_user.get_wrong_submissions_per_minute(
                    session['id']))
            # Submitting too fast
            return {
                'success': True,
                'data': {
                    'status': "ratelimited",
                    'message':
                    "Вы отправляете флаги слишком быстро. Помедленнее."
                }
            }, 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': "У вас осталось 0 попыток"
                    }
                }, 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=current_user.get_wrong_submissions_per_minute(
                        session['id']))
                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=current_user.get_wrong_submissions_per_minute(
                        session['id']))

                if max_tries:
                    # Off by one since fails has changed since it was gotten
                    attempts_left = max_tries - fails - 1
                    tries_str = 'попытки'
                    if attempts_left == 1:
                        tries_str = 'попытка'
                    # Add a punctuation mark if there isn't one
                    if message[-1] not in '!().;?[]{}':
                        message = message + '.'
                    return {
                        'success': True,
                        'data': {
                            'status':
                            "incorrect",
                            'message':
                            '{} У вас {} {} осталось.'.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=current_user.get_wrong_submissions_per_minute(
                    user.account_id))
            return {
                'success': True,
                'data': {
                    'status': "already_solved",
                    'message': 'Вы уже решили это'
                }
            }
Exemplo n.º 12
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
        if current_user.get_wrong_submissions_per_time(
                get_current_user(), config.get_attempt_limit_type(),
                config.get_attempt_limit_hour(),
                config.get_attempt_limit_minute(),
                config.get_attempt_limit_second()
        ) > config.get_attempt_limit_count():
            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=current_user.get_wrong_submissions_per_time(
                    get_current_user(), config.get_attempt_limit_type(),
                    config.get_attempt_limit_hour(),
                    config.get_attempt_limit_minute(),
                    config.get_attempt_limit_second()))
            # 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=current_user.get_wrong_submissions_per_minute(
                        session['id']))
                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=current_user.get_wrong_submissions_per_minute(
                        session['id']))

                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=current_user.get_wrong_submissions_per_minute(
                    user.account_id))
            return {
                'success': True,
                'data': {
                    'status': "already_solved",
                    'message': 'You already solved this'
                }
            }
        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)
                    if TEAMS_MODE:
                        solvers = solvers.filter(Solves.team.has(hidden=False))
                    else:
                        solvers = solvers.filter(Solves.user.has(hidden=False))
                    num_solves = solvers.count()

                    limit = app.config["DISCORD_WEBHOOK_LIMIT"]
                    if limit and num_solves > int(limit):
                        return result

                    user = get_current_user()
                    difficulty = challenge.tags[0].value
                    color = 0x0
                    img_url = ''

                    if (difficulty == 'Easy'):
                        color = 0x0EEC88
                        img_url = 'https://i.imgur.com/0dfOYmU.jpg'
                    elif (difficulty == 'Medium'):
                        color = 0xfb901e
                        img_url = 'https://i.imgur.com/FFSgd5o.jpg'
                    elif (difficulty == 'Hard'):
                        color = 0xff2856
                        img_url = 'https://i.imgur.com/7rz0Xsh.jpg'
                    elif (difficulty == 'Warmup'):
                        color = 0x00fff9
                        img_url = 'https://i.imgur.com/UYvi3wU.jpg'
                    elif (difficulty == 'Elite'):
                        color = 0x641EBD
                        img_url = 'https://i.imgur.com/9ile1tK.jpg'
                    else:
                        color = 0x00fff9
                        img_url = 'https://i.imgur.com/UYvi3wU.jpg'

                    emoji = ''
                    if num_solves == 1:
                        emoji = '🥇'
                    elif num_solves == 2:
                        emoji = '🥈'
                    elif num_solves == 3:
                        emoji = '🥉'
                    else:
                        emoji = '🚩'

                    webhook = DiscordWebhook(
                        url=app.config['DISCORD_WEBHOOK_URL'])
                    embed = None
                    if num_solves == 1:
                        embed = DiscordEmbed(
                            title='First Blood!',
                            description=
                            f'```md\n🩸 First blood on the [ {difficulty} ]( {challenge.category.replace(" ", "_")} ) challenge <{challenge.name.replace(" ", "_")}> goes to < {user.name.replace(" ", "_")} >```',
                            color=color)
                        embed.set_image(url=img_url)
                    else:
                        embed = DiscordEmbed(
                            title='Flag Captured!',
                            description=
                            f'```md\n{emoji} Flag captured from the [ {difficulty} ]( {challenge.category.replace(" ", "_")} ) challenge <{challenge.name.replace(" ", "_")}> by < {user.name.replace(" ", "_")} > -- ({num_solves} solves)```',
                            color=color)
                    embed.set_timestamp()
                    webhook.add_embed(embed)
                    webhook.execute()
            return result
Exemplo n.º 14
0
def files(path):
    """
    Route in charge of dealing with making sure that CTF challenges are only accessible during the competition.
    :param path:
    :return:
    """
    f = Files.query.filter_by(location=path).first_or_404()
    if f.type == "challenge":
        if challenges_visible():
            if current_user.is_admin() is False:
                if not ctftime():
                    if ctf_ended() and view_after_ctf():
                        pass
                    else:
                        abort(403)
        else:
            # User cannot view challenges based on challenge visibility
            # e.g. ctf requires registration but user isn't authed or
            # ctf requires admin account but user isn't admin
            if not ctftime():
                # It's not CTF time. The only edge case is if the CTF is ended
                # but we have view_after_ctf enabled
                if ctf_ended() and view_after_ctf():
                    pass
                else:
                    # In all other situations we should block challenge files
                    abort(403)

            # Allow downloads if a valid token is provided
            token = request.args.get("token", "")
            try:
                data = unserialize(token, max_age=3600)
                user_id = data.get("user_id")
                team_id = data.get("team_id")
                file_id = data.get("file_id")
                user = Users.query.filter_by(id=user_id).first()
                team = Teams.query.filter_by(id=team_id).first()

                # Check user is admin if challenge_visibility is admins only
                if (
                    get_config(ConfigTypes.CHALLENGE_VISIBILITY) == "admins"
                    and user.type != "admin"
                ):
                    abort(403)

                # Check that the user exists and isn't banned
                if user:
                    if user.banned:
                        abort(403)
                else:
                    abort(403)

                # Check that the team isn't banned
                if team:
                    if team.banned:
                        abort(403)
                else:
                    pass

                # Check that the token properly refers to the file
                if file_id != f.id:
                    abort(403)

            # The token isn't expired or broken
            except (BadTimeSignature, SignatureExpired, BadSignature):
                abort(403)

    uploader = get_uploader()
    try:
        return uploader.download(f.location)
    except IOError:
        abort(404)
Exemplo n.º 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",
                },
            }
Exemplo n.º 16
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",
                },
            }