def post(self): files = request.files.getlist("file") # challenge_id # page_id objs = [] for f in files: # uploads.upload_file(file=f, chalid=req.get('challenge')) obj = uploads.upload_file(file=f, **request.form.to_dict()) objs.append(obj) schema = FileSchema(many=True) response = schema.dump(objs) if response.errors: return {"success": False, "errors": response.errorss}, 400 return {"success": True, "data": response.data}
def import_challenges(in_file, dst_attachments, exit_on_error=True, move=False): from CTFd.models import db, Challenges, Flags, Tags, ChallengeFiles from CTFd.utils import uploads 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" 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'] 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': from CTFd.plugins.dynamic_challenges import DynamicChallenge # 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) 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)} if 'files' in chal: from io import FileIO for filename in chal['files']: # 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') db.session.commit() db.session.close()
def setup(): errors = get_errors() if not config.is_setup(): if not session.get("nonce"): session["nonce"] = generate_nonce() if request.method == "POST": # General ctf_name = request.form.get("ctf_name") ctf_description = request.form.get("ctf_description") user_mode = request.form.get("user_mode", USERS_MODE) set_config("ctf_name", ctf_name) set_config("ctf_description", ctf_description) set_config("user_mode", user_mode) # Style ctf_logo = request.files.get("ctf_logo") if ctf_logo: f = upload_file(file=ctf_logo) set_config("ctf_logo", f.location) ctf_small_icon = request.files.get("ctf_small_icon") if ctf_small_icon: f = upload_file(file=ctf_small_icon) set_config("ctf_small_icon", f.location) theme = request.form.get("ctf_theme", DEFAULT_THEME) set_config("ctf_theme", theme) theme_color = request.form.get("theme_color") theme_header = get_config("theme_header") if theme_color and bool(theme_header) is False: # Uses {{ and }} to insert curly braces while using the format method css = ( '<style id="theme-color">\n' ":root {{--theme-color: {theme_color};}}\n" ".navbar{{background-color: var(--theme-color) !important;}}\n" ".jumbotron{{background-color: var(--theme-color) !important;}}\n" "</style>\n" ).format(theme_color=theme_color) set_config("theme_header", css) # DateTime start = request.form.get("start") end = request.form.get("end") set_config("start", start) set_config("end", end) set_config("freeze", None) # Administration name = request.form["name"] email = request.form["email"] password = request.form["password"] name_len = len(name) == 0 names = Users.query.add_columns("name", "id").filter_by(name=name).first() emails = ( Users.query.add_columns("email", "id").filter_by(email=email).first() ) pass_short = len(password) == 0 pass_long = len(password) > 128 valid_email = validators.validate_email(request.form["email"]) team_name_email_check = validators.validate_email(name) if not valid_email: errors.append("Please enter a valid email address") if names: errors.append("That user name is already taken") if team_name_email_check is True: errors.append("Your user name cannot be an email address") if emails: errors.append("That email has already been used") if pass_short: errors.append("Pick a longer password") if pass_long: errors.append("Pick a shorter password") if name_len: errors.append("Pick a longer user name") if len(errors) > 0: return render_template( "setup.html", errors=errors, name=name, email=email, password=password, state=serialize(generate_nonce()), ) admin = Admins( name=name, email=email, password=password, type="admin", hidden=True ) # Create an empty index page page = Pages(title=None, route="index", content="", draft=False) # Upload banner default_ctf_banner_location = url_for("views.themes", path="img/logo.png") ctf_banner = request.files.get("ctf_banner") if ctf_banner: f = upload_file(file=ctf_banner, page_id=page.id) default_ctf_banner_location = url_for("views.files", path=f.location) # Splice in our banner index = f"""<div class="row"> <div class="col-md-6 offset-md-3"> <img class="w-100 mx-auto d-block" style="max-width: 500px;padding: 50px;padding-top: 14vh;" src="{default_ctf_banner_location}" /> <h3 class="text-center"> <p>A cool CTF platform from <a href="https://ctfd.io">ctfd.io</a></p> <p>Follow us on social media:</p> <a href="https://twitter.com/ctfdio"><i class="fab fa-twitter fa-2x" aria-hidden="true"></i></a> <a href="https://facebook.com/ctfdio"><i class="fab fa-facebook fa-2x" aria-hidden="true"></i></a> <a href="https://github.com/ctfd"><i class="fab fa-github fa-2x" aria-hidden="true"></i></a> </h3> <br> <h4 class="text-center"> <a href="admin">Click here</a> to login and setup your CTF </h4> </div> </div>""" page.content = index # Visibility set_config( ConfigTypes.CHALLENGE_VISIBILITY, ChallengeVisibilityTypes.PRIVATE ) set_config( ConfigTypes.REGISTRATION_VISIBILITY, RegistrationVisibilityTypes.PUBLIC ) set_config(ConfigTypes.SCORE_VISIBILITY, ScoreVisibilityTypes.PUBLIC) set_config(ConfigTypes.ACCOUNT_VISIBILITY, AccountVisibilityTypes.PUBLIC) # Verify emails set_config("verify_emails", None) set_config("mail_server", None) set_config("mail_port", None) set_config("mail_tls", None) set_config("mail_ssl", None) set_config("mail_username", None) set_config("mail_password", None) set_config("mail_useauth", None) # Set up default emails set_config("verification_email_subject", DEFAULT_VERIFICATION_EMAIL_SUBJECT) set_config("verification_email_body", DEFAULT_VERIFICATION_EMAIL_BODY) set_config( "successful_registration_email_subject", DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_SUBJECT, ) set_config( "successful_registration_email_body", DEFAULT_SUCCESSFUL_REGISTRATION_EMAIL_BODY, ) set_config( "user_creation_email_subject", DEFAULT_USER_CREATION_EMAIL_SUBJECT ) set_config("user_creation_email_body", DEFAULT_USER_CREATION_EMAIL_BODY) set_config("password_reset_subject", DEFAULT_PASSWORD_RESET_SUBJECT) set_config("password_reset_body", DEFAULT_PASSWORD_RESET_BODY) set_config( "password_change_alert_subject", "Password Change Confirmation for {ctf_name}", ) set_config( "password_change_alert_body", ( "Your password for {ctf_name} has been changed.\n\n" "If you didn't request a password change you can reset your password here: {url}" ), ) set_config("setup", True) try: db.session.add(admin) db.session.commit() except IntegrityError: db.session.rollback() try: db.session.add(page) db.session.commit() except IntegrityError: db.session.rollback() login_user(admin) db.session.close() with app.app_context(): cache.clear() return redirect(url_for("views.static_html")) try: return render_template("setup.html", state=serialize(generate_nonce())) except TemplateNotFound: # Set theme to default and try again set_config("ctf_theme", DEFAULT_THEME) return render_template("setup.html", state=serialize(generate_nonce())) return redirect(url_for("views.static_html"))
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()