Пример #1
0
def gen_hint(
    db, challenge_id, content="This is a hint", cost=0, type="standard", **kwargs
):
    hint = Hints(
        challenge_id=challenge_id, content=content, cost=cost, type=type, **kwargs
    )
    db.session.add(hint)
    db.session.commit()
    return hint
Пример #2
0
def admin_hints(hintid):
    if hintid:
        hint = Hints.query.filter_by(id=hintid).first_or_404()

        if request.method == 'POST':
            hint.hint = request.form.get('hint')
            hint.chal = int(request.form.get('chal'))
            hint.cost = int(request.form.get('cost') or 0)
            db.session.commit()

        elif request.method == 'DELETE':
            db.session.delete(hint)
            db.session.commit()
            db.session.close()
            return ('', 204)

        json_data = {
            'hint': hint.hint,
            'type': hint.type,
            'chal': hint.chal,
            'cost': hint.cost,
            'id': hint.id
        }
        db.session.close()
        return jsonify(json_data)
    else:
        if request.method == 'GET':
            hints = Hints.query.all()
            json_data = []
            for hint in hints:
                json_data.append({
                    'hint': hint.hint,
                    'type': hint.type,
                    'chal': hint.chal,
                    'cost': hint.cost,
                    'id': hint.id
                })
            return jsonify({'results': json_data})
        elif request.method == 'POST':
            hint = request.form.get('hint')
            chalid = int(request.form.get('chal'))
            cost = int(request.form.get('cost') or 0)
            hint_type = request.form.get('type', 0)
            hint = Hints(chal=chalid, hint=hint, cost=cost)
            db.session.add(hint)
            db.session.commit()
            json_data = {
                'hint': hint.hint,
                'type': hint.type,
                'chal': hint.chal,
                'cost': hint.cost,
                'id': hint.id
            }
            db.session.close()
            return jsonify(json_data)
Пример #3
0
def update_hints(chal, chal_dbobj):
    from CTFd.models import db, Hints
    old_hints = Hints.query.filter_by(challenge_id=chal_dbobj.id).all()
    for hint in old_hints:
        db.session.delete(hint)

    for hint in chal.get('hints', []):
        hint_db = Hints(challenge_id=chal_dbobj.id, content=hint['hint'], type=hint['type'],
                        cost=int(hint['cost']))
        db.session.add(hint_db)

    return []
Пример #4
0
def load_challenges_csv(dict_reader):
    schema = ChallengeSchema()
    errors = []

    for i, line in enumerate(dict_reader):
        # Throw away fields that we can't trust if provided
        _ = line.pop("id", None)
        _ = line.pop("requirements", None)

        flags = line.pop("flags", None)
        tags = line.pop("tags", None)
        hints = line.pop("hints", None)
        challenge_type = line.pop("type", "standard")

        # Load in custom type_data
        type_data = json.loads(line.pop("type_data", "{}") or "{}")
        line.update(type_data)

        response = schema.load(line)
        if response.errors:
            errors.append((i + 1, response.errors))
            continue

        ChallengeClass = get_chal_class(challenge_type)
        challenge = ChallengeClass.challenge_model(**line)
        db.session.add(challenge)
        db.session.commit()

        if flags:
            flags = [flag.strip() for flag in flags.split(",")]
            for flag in flags:
                f = Flags(type="static", challenge_id=challenge.id, content=flag,)
                db.session.add(f)
                db.session.commit()

        if tags:
            tags = [tag.strip() for tag in tags.split(",")]
            for tag in tags:
                t = Tags(challenge_id=challenge.id, value=tag,)
                db.session.add(t)
                db.session.commit()

        if hints:
            hints = [hint.strip() for hint in hints.split(",")]
            for hint in hints:
                h = Hints(challenge_id=challenge.id, content=hint,)
                db.session.add(h)
                db.session.commit()
    if errors:
        return errors
    return True
Пример #5
0
def load_challenges_csv(dict_reader):
    for line in dict_reader:
        flags = line.pop("flags", None)
        tags = line.pop("tags", None)
        hints = line.pop("hints", None)
        challenge_type = line.pop("type", "standard")

        # Load in custom type_data
        type_data = json.loads(line.pop("type_data", "{}") or "{}")
        line.update(type_data)

        ChallengeClass = get_chal_class(challenge_type)
        challenge = ChallengeClass.challenge_model(**line)
        db.session.add(challenge)
        db.session.commit()

        if flags:
            flags = [flag.strip() for flag in flags.split(",")]
            for flag in flags:
                f = Flags(
                    type="static",
                    challenge_id=challenge.id,
                    content=flag,
                )
                db.session.add(f)
                db.session.commit()

        if tags:
            tags = [tag.strip() for tag in tags.split(",")]
            for tag in tags:
                t = Tags(
                    challenge_id=challenge.id,
                    value=tag,
                )
                db.session.add(t)
                db.session.commit()

        if hints:
            hints = [hint.strip() for hint in hints.split(",")]
            for hint in hints:
                h = Hints(
                    challenge_id=challenge.id,
                    content=hint,
                )
                db.session.add(h)
                db.session.commit()
    return True
Пример #6
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}
Пример #7
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}
def import_challenges(in_file,
                      dst_attachments,
                      exit_on_error=True,
                      move=False):
    from CTFd.models import db, Challenges, Flags, Tags, Hints, ChallengeFiles
    chals = []
    with open(in_file, 'r') as in_stream:
        chals = yaml.safe_load_all(in_stream)

        for chal in chals:
            skip = False
            for req_field in REQ_FIELDS:
                if req_field not in chal:
                    if exit_on_error:
                        raise MissingFieldError(req_field)
                    else:
                        print("Skipping challenge: Missing field '{}'".format(
                            req_field))
                        skip = True
                        break
            if skip:
                continue

            for flag in chal['flags']:
                if 'flag' not in flag:
                    if exit_on_error:
                        raise MissingFieldError('flag')
                    else:
                        print("Skipping flag: Missing field 'flag'")
                        continue
                flag['flag'] = flag['flag'].strip()
                if 'type' not in flag:
                    flag['type'] = "static"
                if 'data' not in flag:
                    flag['data'] = ""

            if 'files' in chal:
                norm_files = []
                for file in chal['files']:
                    # make sure we have only relative paths in the yaml file
                    file = os.path.normpath("/" + file).lstrip('/')
                    # skip files that do not exists
                    if not os.path.exists(
                            os.path.join(os.path.dirname(in_file), file)):
                        print(
                            "Skipping file '{}' in challenge '{}': File not found"
                            .format(file, chal['name'].strip()))
                        continue
                    else:
                        norm_files.append(file)
                chal['files'] = norm_files

            for hint in chal['hints']:
                if 'type' not in hint:
                    hint['type'] = "standard"

            # Check what type the challenge is and create a DB object of the appropriate type.
            if chal['type'] == 'dynamic':
                # Lazy load the DynamicChallenge plugin on encountering a challenge of that type.
                try:
                    from CTFd.plugins.dynamic_challenges import DynamicChallenge
                except ImportError as err:
                    print("Failed to import plugin for challenge type {}: {}".
                          format(chal['type'], err))
                    continue

                initial = int(chal['value'])
                if 'initial' in chal:
                    initial = int(chal['initial'])

                minimum = 0
                if 'minimum' in chal:
                    minimum = int(chal['minimum'])

                decay = 0
                if 'decay' in chal:
                    decay = int(chal['decay'])

                chal_dbobj = DynamicChallenge(
                    name=chal['name'].strip(),
                    description=chal['description'].strip(),
                    value=int(chal['value']),
                    category=chal['category'].strip(),
                    initial=initial,
                    decay=decay,
                    minimum=minimum)
            elif chal['type'] == 'naumachia':
                # Lazy load the Naumachia plugin on encountering a challenge of that type.
                try:
                    # Here we use a fixed name, which is the repository name, even though it does
                    # not conform to a proper Python package name. Users may install the package
                    # using any file name they want, but this version of thsi plugin does not
                    # support it.
                    naumachia_plugin = importlib.import_module(
                        '.ctfd-naumachia-plugin', package="CTFd.plugins")
                except ImportError as err:
                    print("Failed to import plugin for challenge type {}: {}".
                          format(chal['type'], err))
                    continue

                naumachia_name = chal.get('naumachia_name', "").strip(),

                chal_dbobj = naumachia_plugin.NaumachiaChallengeModel(
                    name=chal['name'].strip(),
                    naumachia_name=chal['naumachia_name'],
                    description=chal['description'].strip(),
                    value=int(chal['value']),
                    category=chal['category'].strip(),
                )
            else:
                # We ignore traling and leading whitespace when importing challenges
                chal_dbobj = Challenges(
                    name=chal['name'].strip(),
                    description=chal['description'].strip(),
                    value=int(chal['value']),
                    category=chal['category'].strip())

            chal_dbobj.state = 'visible'
            if 'hidden' in chal and chal['hidden']:
                if bool(chal['hidden']):
                    chal_dbobj.state = 'hidden'

            chal_dbobj.max_attempts = 0
            if 'max_attempts' in chal and chal['max_attempts']:
                chal_dbobj.max_attempts = chal['max_attempts']

            chal_dbobj.type = 'standard'
            if 'type' in chal and chal['type']:
                chal_dbobj.type = chal['type']

            matching_chals = Challenges.query.filter_by(
                name=chal_dbobj.name,
                description=chal_dbobj.description,
                value=chal_dbobj.value,
                category=chal_dbobj.category,
                state=chal_dbobj.state,
                type=chal_dbobj.type).all()

            for match in matching_chals:
                if 'tags' in chal:
                    tags_db = [
                        tag.tag
                        for tag in Tags.query.add_columns('tag').filter_by(
                            challenge_id=match.id).all()
                    ]
                    if all([tag not in tags_db for tag in chal['tags']]):
                        continue
                if 'files' in chal:
                    files_db = [
                        f.location for f in ChallengeFiles.query.add_columns(
                            'location').filter_by(challenge_id=match.id).all()
                    ]
                    if len(files_db) != len(chal['files']):
                        continue

                    hashes = []
                    for file_db in files_db:
                        with open(os.path.join(dst_attachments, file_db),
                                  'rb') as f:
                            hash = hashlib.md5(f.read()).digest()
                            hashes.append(hash)

                    mismatch = False
                    for file in chal['files']:
                        filepath = os.path.join(os.path.dirname(in_file), file)
                        with open(filepath, 'rb') as f:
                            hash = hashlib.md5(f.read()).digest()
                            if hash in hashes:
                                hashes.remove(hash)
                            else:
                                mismatch = True
                                break
                    if mismatch:
                        continue

                flags_db = Flags.query.filter_by(challenge_id=match.id).all()
                for flag in chal['flags']:
                    for flag_db in flags_db:
                        if flag['flag'] != flag_db.content:
                            continue
                        if flag['type'] != flag_db.type:
                            continue

                skip = True
                break
            if skip:
                print("Skipping '{}': Duplicate challenge found in DB".format(
                    chal['name'].encode('utf8')))
                continue

            print("Adding {}".format(chal['name'].encode('utf8')))
            db.session.add(chal_dbobj)
            db.session.commit()

            if 'tags' in chal:
                for tag in chal['tags']:
                    tag_dbobj = Tags(chal_dbobj.id, tag)
                    db.session.add(tag_dbobj)

            for flag in chal['flags']:
                flag_db = Flags(challenge_id=chal_dbobj.id,
                                content=flag['flag'],
                                type=flag['type'],
                                data=flag['data'])
                db.session.add(flag_db)

            for hint in chal['hints']:
                hint_db = Hints(challenge_id=chal_dbobj.id,
                                content=hint['hint'],
                                type=hint['type'],
                                cost=int(hint['cost']))
                db.session.add(hint_db)

            if 'files' in chal:
                for file in chal['files']:
                    filename = os.path.basename(file)
                    dst_filename = secure_filename(filename)

                    dst_dir = None
                    while not dst_dir or os.path.exists(dst_dir):
                        md5hash = hashlib.md5(os.urandom(64)).hexdigest()
                        dst_dir = os.path.join(dst_attachments, md5hash)

                    os.makedirs(dst_dir)
                    dstpath = os.path.join(dst_dir, dst_filename)
                    srcpath = os.path.join(os.path.dirname(in_file), file)

                    if move:
                        shutil.move(srcpath, dstpath)
                    else:
                        shutil.copy(srcpath, dstpath)
                    file_dbobj = ChallengeFiles(challenge_id=chal_dbobj.id,
                                                location=os.path.relpath(
                                                    dstpath,
                                                    start=dst_attachments))

                    db.session.add(file_dbobj)
        db.session.commit()

    db.session.close()
def import_challenges(in_file, dst_attachments, exit_on_error=True, move=False):
    from CTFd.models import db, Challenges, Keys, Tags, Files, Hints, Unlocks
    with open(in_file, 'r') as in_stream:
        chals = yaml.safe_load_all(in_stream)

        for chal in chals:
            # ensure all required fields are present before adding or updating a challenge
            try:
                validate_yaml(chal)
            except MissingFieldError as err:
                if exit_on_error:
                    raise
                else:
                    print "Skipping challenge: " + str(err)
                    continue

            # if the challenge already exists, update it
            chal_db = Challenges.query.filter_by(name=chal['name']).first()
            if chal_db is not None:
                print "Updating {}".format(chal['name'].encode('utf8'))
                chal_db.description = chal['description']
                chal_db.value = chal['value']
                chal_db.category = chal['category']
            else:
                print "Adding {}".format(chal['name'].encode('utf8'))
                chal_db = Challenges(
                    chal['name'],
                    chal['description'],
                    chal['value'],
                    chal['category'])
            chal_db.type = chal['type']
            chal_db.hidden = chal['hidden']

            db.session.add(chal_db)
            db.session.commit()

            # delete all tags and re-add them
            Tags.query.filter_by(chal=chal_db.id).delete()
            for tag in chal['tags']:
                tag_dbobj = Tags(chal_db.id, tag)
                db.session.add(tag_dbobj)

            # delete all flags and re-add them
            Keys.query.filter_by(chal=chal_db.id).delete()
            for flag in chal['flags']:
                flag_db = Keys(chal_db.id, flag['flag'], flag['type'])
                db.session.add(flag_db)

            # delete or update existing hints
            hints = {h['id']: h for h in chal['hints']}
            hints_db = Hints.query.filter_by(chal=chal_db.id).all()
            for hint_db in hints_db:
                if hint_db.type in hints:
                    # the hint is being updated
                    hint_db.hint = hints[hint_db.type]['hint']
                    hint_db.cost = hints[hint_db.type]['cost']
                    del hints[hint_db.type]
                else:
                    # the hint is being deleted - delete the hint and any related unlocks
                    print "  Removing hint {:d}".format(hint_db.type)
                    Unlocks.query.filter_by(model='hints', itemid=hint_db.id).delete()
                    Hints.query.filter_by(id=hint_db.id).delete()

            # add new hints
            for hint in hints.values():
                print "  Adding hint {:d}".format(hint['id'])
                hint_db = Hints(chal_db.id, hint['hint'], cost=hint['cost'], type=hint['id'])
                db.session.add(hint_db)

            # hash and compare existing files with the new uploaded files
            hashes_db = {}
            files_db = Files.query.filter_by(chal=chal_db.id).all()
            for file_db in files_db:
                with open(os.path.join(dst_attachments, file_db.location), 'rb') as f:
                    h = hashlib.md5(f.read()).digest()
                    hashes_db[h] = file_db

            to_upload = []
            for file in chal['files']:
                path = os.path.join(os.path.dirname(in_file), file)
                with open(path, "rb") as f:
                    h = hashlib.md5(f.read()).digest()
                if h in hashes_db and os.path.basename(file) == os.path.basename(hashes_db[h].location):
                    # the file is up to date
                    del hashes_db[h]
                else:
                    # the file has changed name or content
                    to_upload.append(path)

            # remove out of date files and add new uploads
            for file_db in hashes_db.values():
                print "  Removing file {}".format(file_db.location)
                utils.delete_file(file_db.id)
            for path in to_upload:
                basename = os.path.basename(path)
                print "  Adding file {}".format(basename)
                with open(path, "rb") as f:
                    f = FileStorage(stream=f, filename=basename)
                    utils.upload_file(file=f, chalid=chal_db.id)
                if move:
                    os.unlink(path)

            db.session.commit()

    db.session.commit()
    db.session.close()
Пример #10
0
def admin_hints(hintid):
    if hintid:
        hint = Hints.query.filter_by(id=hintid).first_or_404()

        if request.method == 'POST':
            hint.hint = request.form.get('hint')
            hint.chal = int(request.form.get('chal'))
            hint.cost = int(request.form.get('cost') or 0)
            db.session.commit()
        elif request.method == 'DELETE':
            db.session.delete(hint)
            db.session.commit()
            db.session.close()
            return ('', 204)

        json_data = {
            'hint': hint.hint,
            'type': hint.type,
            'chal': hint.chal,
            'cost': hint.cost,
            'id': hint.id
        }
        db.session.close()
        return jsonify(json_data)
    else:
        if request.method == 'GET':
            hints = Hints.query.all()
            json_data = []
            for hint in hints:
                json_data.append({
                    'hint': hint.hint,
                    'type': hint.type,
                    'chal': hint.chal,
                    'cost': hint.cost,
                    'id': hint.id
                })
            return jsonify({'results': json_data})
        elif request.method == 'POST':
            hint = request.form.get('hint')
            chalid = int(request.form.get('chal'))
            cost = int(request.form.get('cost') or 0)
            hint_type = request.form.get('type', 0)
            hint = Hints(chal=chalid, hint=hint, cost=cost)
            filter = Challenges.query.filter_by(id=chalid).all()
            chalname = filter[0].name
            chal_category = filter[0].category
            chal_value = filter[0].value
            t = datetime.datetime.fromtimestamp(
                time.time()).strftime('%c').split(' ')[3]
            broadcast(t + ": Thử thách " + chal_category + " " +
                      str(chal_value) + ": " + chalname + " đã thêm gợi ý")
            db.session.add(hint)
            db.session.commit()
            json_data = {
                'hint': hint.hint,
                'type': hint.type,
                'chal': hint.chal,
                'cost': hint.cost,
                'id': hint.id
            }
            db.session.close()
            return jsonify(json_data)
def import_challenges(in_file, dst_attachments, move=False):
    from CTFd.models import db, Challenges, Flags, Tags, ChallengeFiles, Hints
    from CTFd.utils import uploads
    from CTFd.plugins.dynamic_challenges import DynamicChallenge, DynamicValueChallenge

    with open(in_file, "r") as in_stream:
        data = list(yaml.safe_load_all(in_stream))
        if len(data) == 0 or data[0] is None or "challs" not in data[
                0] or data[0]["challs"] is None:
            raise ValueError("Invalid YAML format. Missing field 'challs'.")

        for chal in data[0]["challs"]:
            for req_field in REQ_FIELDS:
                if req_field not in chal:
                    raise ValueError(
                        "Invalid YAML format. Missing field '{0}'.".format(
                            req_field))

            if chal.get("type", "standard") == "dynamic":
                for req_field in ["minimum", "decay"]:
                    if req_field not in chal:
                        raise ValueError(
                            "Invalid YAML format. Missing field '{0}'.".format(
                                req_field))

            if chal["flags"] is None:
                raise ValueError("Invalid YAML format. Missing field 'flag'.")

            for flag in chal["flags"]:
                if "flag" not in flag:
                    raise ValueError(
                        "Invalid YAML format. Missing field 'flag'.")

                flag["flag"] = flag["flag"].strip()
                if "type" not in flag:
                    flag["type"] = "static"

            matching_chal = Challenges.query.filter_by(
                name=chal["name"].strip()).first()
            if matching_chal:
                print(("Updating {}: Duplicate challenge "
                       "found in DB (id: {})").format(
                           chal["name"].encode("utf8"), matching_chal.id))
                Tags.query.filter_by(challenge_id=matching_chal.id).delete()
                ChallengeFiles.query.filter_by(
                    challenge_id=matching_chal.id).delete()
                Flags.query.filter_by(challenge_id=matching_chal.id).delete()
                Hints.query.filter_by(challenge_id=matching_chal.id).delete()

                matching_chal.name = chal["name"].strip()
                matching_chal.description = chal["description"].strip()
                matching_chal.category = chal["category"].strip()

                if chal.get("type", "standard") == "standard":
                    matching_chal.value = chal["value"]

                if chal.get("type", "standard") == "dynamic":
                    matching_chal.minimum = chal["minimum"]
                    matching_chal.decay = chal["decay"]
                    matching_chal.initial = chal["value"]
                    DynamicValueChallenge.calculate_value(matching_chal)

                db.session.commit()
                chal_dbobj = matching_chal

            else:
                print("Adding {}".format(chal["name"].encode("utf8")))

                chal_type = chal.get("type", "standard")
                if chal_type == "standard":
                    # We ignore traling and leading whitespace when
                    # importing challenges
                    chal_dbobj = Challenges(
                        name=chal["name"].strip(),
                        description=chal["description"].strip(),
                        value=chal["value"],
                        category=chal["category"].strip(),
                    )
                elif chal_type == "dynamic":
                    # We ignore traling and leading whitespace when
                    # importing challenges
                    chal_dbobj = DynamicChallenge(
                        name=chal["name"].strip(),
                        description=chal["description"].strip(),
                        category=chal["category"].strip(),
                        value=int(chal["value"]),
                        minimum=int(chal["minimum"]),
                        decay=int(chal["decay"]),
                    )
                else:
                    raise ValueError("Unknown type of challenge")

                db.session.add(chal_dbobj)
                db.session.commit()

            for tag in chal.get("tags", []):
                tag_dbobj = Tags(challenge_id=chal_dbobj.id, value=tag)
                db.session.add(tag_dbobj)

            for flag in chal["flags"]:
                flag_db = Flags(challenge_id=chal_dbobj.id,
                                content=flag["flag"],
                                type=flag["type"])
                db.session.add(flag_db)

            for hint in chal.get("hints", []):
                hint_dbobj = Hints(challenge_id=chal_dbobj.id,
                                   content=hint["content"],
                                   cost=hint["cost"])
                db.session.add(hint_dbobj)

            chal_dbobj.state = "hidden" if (
                "hidden" in chal and chal["hidden"] == True) else "visible"
            chal_dbobj.max_attempts = chal[
                "max_attempts"] if "max_attempts" in chal else 0

            if "files" in chal:
                from io import FileIO

                for filename in chal["files"]:
                    try:
                        # upload_file takes a werkzeug.FileStorage object, but we
                        # can get close enough with a file object with a
                        # filename property added
                        filepath = os.path.join(os.path.dirname(in_file),
                                                filename)
                        with FileIO(filepath, mode="rb") as f:
                            f.filename = os.path.basename(f.name)
                            uploads.upload_file(file=f,
                                                challenge=chal_dbobj.id,
                                                type="challenge")
                    except FileNotFoundError:
                        raise ValueError(
                            "Unable to import challenges. Missing file: " +
                            filename)

    db.session.commit()

    # update challenge prerequisites after all the challenges were created
    with open(in_file, "r") as in_stream:
        data = list(yaml.safe_load_all(in_stream))
        for chal in data[0]["challs"]:

            chal_dbobj = Challenges.query.filter_by(
                name=chal["name"].strip()).first()

            prerequisites = set()
            for prerequisite in chal.get("prerequisites", []):
                prerequisites.update([
                    c.id for c in Challenges.query.filter_by(
                        name=prerequisite).all()
                ])
            chal_dbobj.requirements = {"prerequisites": list(prerequisites)}

    db.session.commit()
    db.session.close()