def db_create_schema(): db_conn = database.db_connect('postgres') db_conn.autocommit = True with db_conn.cursor() as cur: cur.execute('DROP DATABASE IF EXISTS {}'.format(app.config['DB_NAME'])) cur.execute("CREATE DATABASE {} WITH ENCODING 'UTF8'".format(app.config['DB_NAME'])) db_conn.close() db_conn = database.db_connect() with db_conn.cursor() as cur: cur.execute(pkgutil.get_data('ctforge', 'db/schema.sql')) db_conn.commit() db_conn.close()
def db_add_user(name, surname, mail, password, admin=False, hidden=False, team_id=None): db_conn = database.db_connect() with db_conn.cursor() as cur: cur.execute(( 'INSERT INTO users (team_id, name, surname, mail, password, admin, hidden) ' 'VALUES (%s, %s, %s, %s, %s, %s, %s)'), [team_id, name, surname, mail, bcrypt.hashpw(password, bcrypt.gensalt()), admin, hidden]) db_conn.commit() db_conn.close()
def edit_evaluation(challenge_id, user_id): # check if an evaluation has already been performed or not db_conn = db_connect() with db_conn.cursor() as cur: cur.execute(( 'SELECT U.mail AS mail, U.name, U.surname, C.name AS challenge, ' ' W.timestamp AS timestamp, W.writeup AS writeup, E.grade, E.feedback ' 'FROM writeups AS W ' 'JOIN challenges AS C ON W.challenge_id = C.id ' 'JOIN users AS U ON W.user_id = U.id ' 'LEFT JOIN challenges_evaluations AS E ' ' ON W.user_id = E.user_id AND W.challenge_id = E.challenge_id ' 'WHERE W.challenge_id = %s AND W.user_id = %s ' 'ORDER BY W.id DESC'), [challenge_id, user_id]) evaluation = cur.fetchone() if evaluation is None: flash('Writeup not submitted, cannot evaluate!', 'error') return redirect(url_for('admin', tab='evaluations')) if request.method == 'POST': form = ctforge.forms.AdminWriteupForm() if form.validate_on_submit(): if evaluation['feedback'] is None: # do a fresh insert query_handler(('INSERT INTO challenges_evaluations ' ' (user_id, challenge_id, grade, feedback) ' 'VALUES (%s, %s, %s, %s) '), [ user_id, challenge_id, form.grade.data, form.feedback.data ]) else: # only allow if not yet graded if evaluation['grade'] is None: query_handler( ('UPDATE challenges_evaluations ' 'SET grade = %s, feedback = %s, timestamp = NOW() ' 'WHERE user_id = %s AND challenge_id = %s'), [ form.grade.data, form.feedback.data, user_id, challenge_id ]) else: flash( 'Cannot modify a writeup evaluation once a grade has been set!', 'error') else: flash_errors(form) else: form = ctforge.forms.AdminWriteupForm(**evaluation) return render_template('admin/data.html', form=form, target='evaluation', action='edit') return redirect(url_for('admin', tab='evaluations'))
def main(): global logger, db_conn # parse command line options, the round parameter is required parser = argparse.ArgumentParser(description='Flag dispatcher and checker') parser.add_argument('--advance', action='store_true', default=False, help="Advance the current round") parser.add_argument('--dispatch', action='store_true', default=False, help="Dispatch new flags to all the virtual machines") parser.add_argument('--check', action='store_true', default=False, help="Check the integrity of the services") parser.add_argument('-n', dest='num_workers', type=int, default=1, help="Number of concurrent workers (default 1)") parser.add_argument( '-t', dest='timeout', type=int, default=10, help="Seconds to wait before killing a spawned script (default 10)") parser.add_argument('-v', dest='verbose', action='store_true', default=False, help="Set logging level to debug") args = parser.parse_args() if not any([args.advance, args.dispatch, args.check]): sys.stderr.write("At least one action is required, aborting.\n") abort() # register the killer handler signal.signal(signal.SIGINT, interrupt) # set variables n_workers = args.num_workers log_level = logging.DEBUG if args.verbose else logging.INFO # set logging logger.setLevel(log_level) formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s') fh = logging.FileHandler(config['BOT_LOG_FILE']) fh.setFormatter(formatter) logger.addHandler(fh) # if the verbose mode is selected, log also on the console if args.verbose: ch = logging.StreamHandler() ch.setFormatter(formatter) logger.addHandler(ch) # start the global db connection db_conn = database.db_connect(logger=logger) # retrieve the list of teams and services teams, services = get_teams_services() if args.advance: # advance the round advance_round(teams, services) if args.check or args.dispatch: # fill the queue of tasks, choosing the team order randomly :) for service in services: for team in random.sample(teams, len(teams)): tasks.put_nowait((team, service)) # create the list of workers workers = [] for i in range(n_workers): worker = Worker(i, args.dispatch, args.check, args.timeout) workers.append(worker) worker.start() # wait responsively until the queue is empty or an event is thrown while not tasks.empty(): Worker.killing_time.wait(1) # join all workers for worker in workers: worker.join() # close the connection to the db db_conn.close() # exit gracefully sys.exit(0)
def challenge(name): """Display information about a challenge plus the flag submission form and the writeup.""" db_conn = db_connect() cur = db_conn.cursor() # get challenge data if the challenge exists cur.execute('SELECT * FROM challenges WHERE name = %s', [name]) challenge = cur.fetchone() # if the challenge is not valid abort if challenge is None: cur.close() abort(404) # check if the current user already solved the challenge cur.execute(('SELECT * FROM challenge_attacks ' 'WHERE user_id = %s AND challenge_id = %s'), [current_user.id, challenge['id']]) solved = cur.fetchone() is not None # get the list of all the writeups submitted by this user for this challenge cur.execute(('SELECT id, timestamp FROM writeups ' 'WHERE user_id = %s AND challenge_id = %s ' 'ORDER BY id DESC'), [current_user.id, challenge['id']]) writeups = cur.fetchall() # get the evaluation for this challenge evaluation = None if writeups: cur.execute( ('SELECT feedback, grade, timestamp FROM challenges_evaluations ' 'WHERE user_id = %s AND challenge_id = %s '), [current_user.id, challenge['id']]) evaluation = cur.fetchone() graded = evaluation is not None and evaluation['grade'] is not None # retrieve the writeup form, if any writeup_form = ctforge.forms.ChallengeWriteupForm( writeup=challenge['writeup_template']) # retrive the flag form flag_form = ctforge.forms.ChallengeFlagForm() # accept POST requests only if the challenge is active if request.method == 'POST' and challenge['active']: # process the two mutually exclusive forms writeup_data = request.form.get('writeup') flag = request.form.get('flag') if writeup_data is not None: # only allow writeup submission if writeup support is enabled for this chal if challenge['writeup'] and writeup_form.validate_on_submit(): if graded: # writeup already submitted, resubmission allowed only if there's no grade flash( 'Your submission has already been graded, you cannot modify it', 'error') else: writeup_data = writeup_form.writeup.data try: # save this writeup into the db cur.execute(( 'INSERT INTO writeups (user_id, challenge_id, writeup) ' 'VALUES (%s, %s, %s) RETURNING id'), [ current_user.id, challenge['id'], writeup_data ]) writeup_id = cur.fetchone()['id'] cur.close() db_conn.commit() flash('Writeup added', 'success') except psycopg2.Error as e: db_conn.rollback() error_msg = 'Unknown database error: {}'.format(e) flash(error_msg, 'error') app.logger.error(error_msg) else: flash_errors(writeup_form) else: if cur is None: cur = db_conn.cursor() if flag is not None and flag_form.validate_on_submit(): flag = flag_form.flag.data if flag == challenge['flag']: try: # save this attack into the db cur.execute(( 'INSERT INTO challenge_attacks (user_id, challenge_id) ' 'VALUES (%s, %s)'), [current_user.id, challenge['id']]) cur.close() db_conn.commit() flash('Flag accepted!', 'success') except psycopg2.IntegrityError: # this exception is raised not only on duplicated entry, # but also when key constraint fails db_conn.rollback() flash('You already solved this challenge') except psycopg2.Error as e: db_conn.rollback() error_msg = 'Unknown database error: {}'.format(e) flash(error_msg, 'error') app.logger.error(error_msg) else: flash('Invalid flag', 'error') else: flash_errors(flag_form) # close the pending connection to the database db_conn.close() return redirect(url_for('challenge', name=challenge['name'])) db_conn.close() return render_template('challenge.html', flag_form=flag_form, writeup_form=writeup_form, challenge=challenge, evaluation=evaluation, solved=solved, graded=graded, writeups=writeups)
def submit(): """Flag submitter service.""" team_token = None # get the token associated with user's team if current_user.is_authenticated: db_conn = get_db_connection() with db_conn.cursor() as cur: cur.execute('SELECT token FROM teams WHERE id = %s', [current_user.team_id]) res = cur.fetchone() team_token = res['token'] if res is not None else None # initialize the flag form form = ctforge.forms.ServiceFlagForm(csrf_enabled=False) if request.method == 'POST': # process the form if form.validate_on_submit(): team_token = form.team_token.data flag = form.flag.data try: db_conn = db_connect() db_conn.autocommit = False cur = db_conn.cursor() # get the team associated with the retrieved token cur.execute('SELECT id FROM teams WHERE token = %s', [team_token]) res = cur.fetchone() if res is None: raise ctforge.exceptions.InvalidToken() team_id = res['id'] # get the flag that the user is trying to submit, if valid # (i.e. active and not one of the flags of his team) cur.execute(('SELECT service_id FROM active_flags ' 'WHERE flag = %s AND team_id != %s'), [flag, team_id]) res = cur.fetchone() if res is None: raise ctforge.exceptions.InvalidFlag() service_id = res['service_id'] # check whether the team's service is well-functioning or not cur.execute(('SELECT I.successful, I.timestamp ' 'FROM active_flags AS A ' 'JOIN integrity_checks AS I ' 'ON A.flag = I.flag ' 'WHERE A.team_id = %s AND A.service_id = %s ' 'ORDER BY I.timestamp DESC LIMIT 1'), [team_id, service_id]) res = cur.fetchone() if res is None or res['successful'] != 1: raise ctforge.exceptions.ServiceCorrupted() # store the attack in the database cur.execute(('INSERT INTO service_attacks (team_id, flag) ' 'VALUES (%s, %s) '), [team_id, flag]) db_conn.commit() flash('Flag accepted!', 'success') except psycopg2.IntegrityError: # this exception is raised not only on duplicated entry, but also # when key constraint fails db_conn.rollback() flash('Duplicated flag!', 'error') except psycopg2.Error as e: db_conn.rollback() error_msg = 'Unknown database error: {}'.format(e) flash(error_msg, 'error') app.logger.error(error_msg) except ctforge.exceptions.InvalidToken: db_conn.rollback() flash('The provided token does not match any team!', 'error') except ctforge.exceptions.InvalidFlag: db_conn.rollback() flash('The submitted flag is invalid!', 'error') except ctforge.exceptions.ServiceCorrupted: db_conn.rollback() flash( 'Your service is corrupted, fix it before submitting flags!', 'error') except Exception as e: # this should never occur, but we keep it for safety reasons error_msg = 'Unknown error: {}'.format(e) flash(error_msg, 'error') app.logger.error(error_msg) finally: cur.close() db_conn.close() else: flash_errors(form) return render_template('submit.html', form=form, team_token=team_token)
def db_create_procedures(): db_conn = database.db_connect() with db_conn.cursor() as cur: cur.execute(pkgutil.get_data('ctforge', 'db/procedures.sql')) db_conn.commit() db_conn.close()