Example #1
0
def admin_plugin_config(plugin):
    if request.method == 'GET':
        if plugin in utils.get_configurable_plugins():
            config = open(os.path.join(app.root_path, 'plugins', plugin, 'config.html')).read()
            return render_template_string(config)
        abort(404)
    elif request.method == 'POST':
        for k, v in request.form.items():
            if k == "nonce":
                continue
            utils.set_config(k, v)
        return '1'
Example #2
0
def admin_css():
    if request.method == 'POST':
        css = request.form['css']
        css = set_config('css', css)
        print css
        return "1"
    return "0"
Example #3
0
def test_get_config_and_set_config():
    """Does get_config and set_config work properly"""
    app = create_ctfd()
    with app.app_context():
        assert get_config('setup') == True
        config = set_config('TEST_CONFIG_ENTRY', 'test_config_entry')
        assert config.value == 'test_config_entry'
        assert get_config('TEST_CONFIG_ENTRY') == 'test_config_entry'
Example #4
0
def admin_css():
    if request.method == 'POST':
        css = request.form['css']
        css = utils.set_config('css', css)
        with app.app_context():
            cache.clear()
        return '1'
    return '0'
Example #5
0
def test_sendmail_with_smtp(mock_smtp):
    """Does sendmail work properly with simple SMTP mail servers"""
    from email.mime.text import MIMEText
    app = create_ctfd()
    with app.app_context():
        set_config('mail_server', 'localhost')
        set_config('mail_port', 25)
        set_config('mail_username', 'username')
        set_config('mail_password', 'password')

        from_addr = get_config('mailfrom_addr') or app.config.get('MAILFROM_ADDR')
        to_addr = '*****@*****.**'
        msg = 'this is a test'

        sendmail(to_addr, msg)

        ctf_name = get_config('ctf_name')
        email_msg = MIMEText(msg)
        email_msg['Subject'] = "Message from {0}".format(ctf_name)
        email_msg['From'] = from_addr
        email_msg['To'] = to_addr

        mock_smtp.return_value.sendmail.assert_called_once_with(from_addr, [to_addr], email_msg.as_string())
Example #6
0
def test_user_can_confirm_email(mock_smtp):
    """Test that a user is capable of confirming their email address"""
    app = create_ctfd()
    with app.app_context(), freeze_time("2012-01-14 03:21:34"):
        # Set CTFd to only allow confirmed users and send emails
        set_config('verify_emails', True)
        set_config('mail_server', 'localhost')
        set_config('mail_port', 25)
        set_config('mail_useauth', True)
        set_config('mail_username', 'username')
        set_config('mail_password', 'password')

        register_user(app, name="user1", email="*****@*****.**")

        # Teams are not verified by default
        user = Users.query.filter_by(email='*****@*****.**').first()
        assert user.verified is False

        client = login_as_user(app, name="user1", password="******")

        r = client.get('http://localhost/confirm')
        assert "Need to resend the confirmation email?" in r.get_data(
            as_text=True)

        # smtp.sendmail was called
        mock_smtp.return_value.sendmail.assert_called()

        with client.session_transaction() as sess:
            data = {"nonce": sess.get('nonce')}
            r = client.post('http://localhost/confirm', data=data)
            assert "confirmation email has been resent" in r.get_data(
                as_text=True)

            r = client.get('/challenges')
            assert r.location == "http://localhost/confirm"  # We got redirected to /confirm

            r = client.get('http://localhost/confirm/' +
                           serialize('*****@*****.**'))
            assert r.location == 'http://localhost/challenges'

            # The team is now verified
            user = Users.query.filter_by(email='*****@*****.**').first()
            assert user.verified is True

            r = client.get('http://localhost/confirm')
            assert r.location == "http://localhost/settings"
    destroy_ctfd(app)
Example #7
0
def admin_config():
    if request.method == "POST":
        start = None
        end = None
        freeze = None
        if request.form.get('start'):
            start = int(request.form['start'])
        if request.form.get('end'):
            end = int(request.form['end'])
        if request.form.get('freeze'):
            freeze = int(request.form['freeze'])

        try:
            view_challenges_unregistered = bool(
                request.form.get('view_challenges_unregistered', None))
            view_scoreboard_if_authed = bool(
                request.form.get('view_scoreboard_if_authed', None))
            hide_scores = bool(request.form.get('hide_scores', None))
            prevent_registration = bool(
                request.form.get('prevent_registration', None))
            prevent_name_change = bool(
                request.form.get('prevent_name_change', None))
            view_after_ctf = bool(request.form.get('view_after_ctf', None))
            verify_emails = bool(request.form.get('verify_emails', None))
            mail_tls = bool(request.form.get('mail_tls', None))
            mail_ssl = bool(request.form.get('mail_ssl', None))
            mail_useauth = bool(request.form.get('mail_useauth', None))
        except (ValueError, TypeError):
            view_challenges_unregistered = None
            view_scoreboard_if_authed = None
            hide_scores = None
            prevent_registration = None
            prevent_name_change = None
            view_after_ctf = None
            verify_emails = None
            mail_tls = None
            mail_ssl = None
            mail_useauth = None
        finally:
            view_challenges_unregistered = utils.set_config(
                'view_challenges_unregistered', view_challenges_unregistered)
            view_scoreboard_if_authed = utils.set_config(
                'view_scoreboard_if_authed', view_scoreboard_if_authed)
            hide_scores = utils.set_config('hide_scores', hide_scores)
            prevent_registration = utils.set_config('prevent_registration',
                                                    prevent_registration)
            prevent_name_change = utils.set_config('prevent_name_change',
                                                   prevent_name_change)
            view_after_ctf = utils.set_config('view_after_ctf', view_after_ctf)
            verify_emails = utils.set_config('verify_emails', verify_emails)
            mail_tls = utils.set_config('mail_tls', mail_tls)
            mail_ssl = utils.set_config('mail_ssl', mail_ssl)
            mail_useauth = utils.set_config('mail_useauth', mail_useauth)

        mail_server = utils.set_config("mail_server",
                                       request.form.get('mail_server', None))
        mail_port = utils.set_config("mail_port",
                                     request.form.get('mail_port', None))

        mail_username = utils.set_config(
            "mail_username", request.form.get('mail_username', None))
        mail_password = utils.set_config(
            "mail_password", request.form.get('mail_password', None))

        ctf_name = utils.set_config("ctf_name",
                                    request.form.get('ctf_name', None))
        ctf_theme = utils.set_config("ctf_theme",
                                     request.form.get('ctf_theme', None))

        mailfrom_addr = utils.set_config(
            "mailfrom_addr", request.form.get('mailfrom_addr', None))
        mg_base_url = utils.set_config("mg_base_url",
                                       request.form.get('mg_base_url', None))
        mg_api_key = utils.set_config("mg_api_key",
                                      request.form.get('mg_api_key', None))

        db_freeze = utils.set_config("freeze", freeze)

        db_start = Config.query.filter_by(key='start').first()
        db_start.value = start

        db_end = Config.query.filter_by(key='end').first()
        db_end.value = end

        db.session.add(db_start)
        db.session.add(db_end)

        db.session.commit()
        db.session.close()
        with app.app_context():
            cache.clear()
        return redirect(url_for('admin.admin_config'))

    with app.app_context():
        cache.clear()
    ctf_name = utils.get_config('ctf_name')
    ctf_theme = utils.get_config('ctf_theme')
    hide_scores = utils.get_config('hide_scores')

    mail_server = utils.get_config('mail_server')
    mail_port = utils.get_config('mail_port')
    mail_username = utils.get_config('mail_username')
    mail_password = utils.get_config('mail_password')

    mailfrom_addr = utils.get_config('mailfrom_addr')
    mg_api_key = utils.get_config('mg_api_key')
    mg_base_url = utils.get_config('mg_base_url')

    view_after_ctf = utils.get_config('view_after_ctf')
    start = utils.get_config('start')
    end = utils.get_config('end')
    freeze = utils.get_config('freeze')

    mail_tls = utils.get_config('mail_tls')
    mail_ssl = utils.get_config('mail_ssl')
    mail_useauth = utils.get_config('mail_useauth')

    view_challenges_unregistered = utils.get_config(
        'view_challenges_unregistered')
    view_scoreboard_if_authed = utils.get_config('view_scoreboard_if_authed')
    prevent_registration = utils.get_config('prevent_registration')
    prevent_name_change = utils.get_config('prevent_name_change')
    verify_emails = utils.get_config('verify_emails')

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

    themes = utils.get_themes()
    themes.remove(ctf_theme)

    return render_template(
        'admin/config.html',
        ctf_name=ctf_name,
        ctf_theme_config=ctf_theme,
        start=start,
        end=end,
        freeze=freeze,
        hide_scores=hide_scores,
        mail_server=mail_server,
        mail_port=mail_port,
        mail_useauth=mail_useauth,
        mail_username=mail_username,
        mail_password=mail_password,
        mail_tls=mail_tls,
        mail_ssl=mail_ssl,
        view_challenges_unregistered=view_challenges_unregistered,
        view_scoreboard_if_authed=view_scoreboard_if_authed,
        prevent_registration=prevent_registration,
        mailfrom_addr=mailfrom_addr,
        mg_base_url=mg_base_url,
        mg_api_key=mg_api_key,
        prevent_name_change=prevent_name_change,
        verify_emails=verify_emails,
        view_after_ctf=view_after_ctf,
        themes=themes)
Example #8
0
def setup():
    # with app.app_context():
        # admin = Teams.query.filter_by(admin=True).first()

    if not is_setup():
        if not session.get('nonce'):
            session['nonce'] = sha512(os.urandom(10))
        if request.method == 'POST':
            ctf_name = request.form['ctf_name']
            ctf_name = set_config('ctf_name', ctf_name)

            ## CSS
            css = set_config('start', '')

            ## Admin user
            name = request.form['name']
            email = request.form['email']
            password = request.form['password']
            admin = Teams(name, email, password)
            admin.admin = True
            admin.banned = True


            #max attempts per challenge
            max_tries = set_config("max_tries",0)

            ## Start time
            start = set_config('start', None)
            end = set_config('end', None)

            ## Challenges cannot be viewed by unregistered users
            view_challenges_unregistered = set_config('view_challenges_unregistered', None)

            ## Allow/Disallow registration
            prevent_registration = set_config('prevent_registration', None)

            ## Verify emails
            verify_emails = set_config('verify_emails', None)

            mail_server = set_config('mail_server', None)
            mail_port = set_config('mail_port', None)
            mail_tls = set_config('mail_tls', None)
            mail_ssl = set_config('mail_ssl', None)
            mail_username = set_config('mail_username', None)
            mail_password = set_config('mail_password', None)

            setup = set_config('setup', True)

            db.session.add(page)
            db.session.add(admin)
            db.session.commit()
            app.setup = False
            return redirect('/')
        return render_template('setup.html', nonce=session.get('nonce'))
    return redirect('/')
Example #9
0
def create_app(config='CTFd.config.Config'):
    app = CTFdFlask(__name__)
    with app.app_context():
        app.config.from_object(config)

        theme_loader = ThemeLoader(os.path.join(app.root_path, 'themes'),
                                   followlinks=True)
        app.jinja_loader = theme_loader

        from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking

        url = make_url(app.config['SQLALCHEMY_DATABASE_URI'])
        if url.drivername == 'postgres':
            url.drivername = 'postgresql'

        if url.drivername.startswith('mysql'):
            url.query['charset'] = 'utf8mb4'

        # Creates database if the database database does not exist
        if not database_exists(url):
            if url.drivername.startswith('mysql'):
                create_database(url, encoding='utf8mb4')
            else:
                create_database(url)

        # This allows any changes to the SQLALCHEMY_DATABASE_URI to get pushed back in
        # This is mostly so we can force MySQL's charset
        app.config['SQLALCHEMY_DATABASE_URI'] = str(url)

        # Register database
        db.init_app(app)

        # Register Flask-Migrate
        migrate.init_app(app, db)

        # Alembic sqlite support is lacking so we should just create_all anyway
        if url.drivername.startswith('sqlite'):
            db.create_all()
        else:
            if len(db.engine.table_names()) == 0:
                # This creates tables instead of db.create_all()
                # Allows migrations to happen properly
                migrate_upgrade()
            elif 'alembic_version' not in db.engine.table_names():
                # There is no alembic_version because CTFd is from before it had migrations
                # Stamp it to the base migration
                if confirm_upgrade():
                    migrate_stamp(revision='cb3cfcc47e2f')
                    run_upgrade()
                else:
                    exit()

        app.db = db
        app.VERSION = __version__

        cache.init_app(app)
        app.cache = cache

        update_check(force=True)

        version = utils.get_config('ctf_version')

        # Upgrading from an older version of CTFd
        if version and (StrictVersion(version) < StrictVersion(__version__)):
            if confirm_upgrade():
                run_upgrade()
            else:
                exit()

        if not version:
            utils.set_config('ctf_version', __version__)

        if not utils.get_config('ctf_theme'):
            utils.set_config('ctf_theme', 'core')

        from CTFd.views import views
        from CTFd.challenges import challenges
        from CTFd.scoreboard import scoreboard
        from CTFd.auth import auth
        from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_keys, admin_teams
        from CTFd.utils import init_utils, init_errors, init_logs

        init_utils(app)
        init_errors(app)
        init_logs(app)

        app.register_blueprint(views)
        app.register_blueprint(challenges)
        app.register_blueprint(scoreboard)
        app.register_blueprint(auth)

        app.register_blueprint(admin)
        app.register_blueprint(admin_statistics)
        app.register_blueprint(admin_challenges)
        app.register_blueprint(admin_teams)
        app.register_blueprint(admin_scoreboard)
        app.register_blueprint(admin_keys)
        app.register_blueprint(admin_pages)

        from CTFd.plugins import init_plugins

        init_plugins(app)

        return app
Example #10
0
def test_api_user_send_email():
    """Can an admin post /api/v1/users/<user_id>/email"""
    app = create_ctfd()
    with app.app_context():

        register_user(app)

        with login_as_user(app) as client:
            r = client.post("/api/v1/users/2/email",
                            json={"text": "email should get rejected"})
            assert r.status_code == 403

        with login_as_user(app, "admin") as admin:
            r = admin.post("/api/v1/users/2/email",
                           json={"text": "email should be accepted"})
            assert r.get_json() == {
                "success": False,
                "errors": {
                    "": ["Email settings not configured"]
                },
            }
            assert r.status_code == 400

        set_config("verify_emails", True)
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")

        with login_as_user(app, "admin") as admin:
            r = admin.post("/api/v1/users/2/email", json={"text": ""})
            assert r.get_json() == {
                "success": False,
                "errors": {
                    "text": ["Email text cannot be empty"]
                },
            }
            assert r.status_code == 400

        with login_as_user(app, "admin") as admin:
            r = admin.post("/api/v1/users/2/email",
                           json={"text": "email should be accepted"})
            assert r.status_code == 200

    destroy_ctfd(app)
Example #11
0
def import_ctf(backup, erase=True):
    if not zipfile.is_zipfile(backup):
        raise zipfile.BadZipfile

    if erase:
        drop_database()
        create_database()
        # We explicitly do not want to upgrade or stamp here.
        # The import will have this information.

    side_db = dataset.connect(get_app_config("SQLALCHEMY_DATABASE_URI"))
    sqlite = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("sqlite")
    postgres = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("postgres")

    backup = zipfile.ZipFile(backup)

    members = backup.namelist()
    max_content_length = get_app_config("MAX_CONTENT_LENGTH")
    for f in members:
        if f.startswith("/") or ".." in f:
            # Abort on malicious zip files
            raise zipfile.BadZipfile
        info = backup.getinfo(f)
        if max_content_length:
            if info.file_size > max_content_length:
                raise zipfile.LargeZipFile

    first = [
        "db/teams.json",
        "db/users.json",
        "db/challenges.json",
        "db/dynamic_challenge.json",
        "db/flags.json",
        "db/hints.json",
        "db/unlocks.json",
        "db/awards.json",
        "db/tags.json",
        "db/submissions.json",
        "db/solves.json",
        "db/files.json",
        "db/notifications.json",
        "db/pages.json",
        "db/tracking.json",
        "db/config.json",
    ]

    for item in first:
        if item in members:
            members.remove(item)

    members = first + members

    alembic_version = json.loads(
        backup.open(
            "db/alembic_version.json").read())["results"][0]["version_num"]
    upgrade(revision=alembic_version)

    # Create tables created by plugins
    try:
        app.db.create_all()
    except OperationalError as e:
        if not postgres:
            raise e
        else:
            print("Allowing error during app.db.create_all() due to Postgres")

    members.remove("db/alembic_version.json")

    for member in members:
        if member.startswith("db/"):
            table_name = member[3:-5]

            try:
                # Try to open a file but skip if it doesn't exist.
                data = backup.open(member).read()
            except KeyError:
                continue

            if data:
                table = side_db[table_name]

                saved = json.loads(data)
                for entry in saved["results"]:
                    # This is a hack to get SQLite to properly accept datetime values from dataset
                    # See Issue #246
                    if sqlite:
                        for k, v in entry.items():
                            if isinstance(v, six.string_types):
                                match = re.match(
                                    r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d",
                                    v)
                                if match:
                                    entry[k] = datetime.datetime.strptime(
                                        v, "%Y-%m-%dT%H:%M:%S.%f")
                                    continue
                                match = re.match(
                                    r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", v)
                                if match:
                                    entry[k] = datetime.datetime.strptime(
                                        v, "%Y-%m-%dT%H:%M:%S")
                                    continue
                    # From v2.0.0 to v2.1.0 requirements could have been a string or JSON because of a SQLAlchemy issue
                    # This is a hack to ensure we can still accept older exports. See #867
                    if member in (
                            "db/challenges.json",
                            "db/hints.json",
                            "db/awards.json",
                    ):
                        requirements = entry.get("requirements")
                        if requirements and isinstance(requirements,
                                                       six.string_types):
                            entry["requirements"] = json.loads(requirements)

                    try:
                        table.insert(entry)
                    except ProgrammingError:
                        # MariaDB does not like JSON objects and prefers strings because it internally
                        # represents JSON with LONGTEXT.
                        # See Issue #973
                        requirements = entry.get("requirements")
                        if requirements and isinstance(requirements, dict):
                            entry["requirements"] = json.dumps(requirements)
                        table.insert(entry)

                    db.session.commit()
                if postgres:
                    # This command is to set the next primary key ID for the re-inserted tables in Postgres. However,
                    # this command is very difficult to translate into SQLAlchemy code. Because Postgres is not
                    # officially supported, no major work will go into this functionality.
                    # https://stackoverflow.com/a/37972960
                    if '"' not in table_name and "'" not in table_name:
                        query = "SELECT setval(pg_get_serial_sequence('{table_name}', 'id'), coalesce(max(id)+1,1), false) FROM \"{table_name}\"".format(  # nosec
                            table_name=table_name)
                        side_db.engine.execute(query)
                    else:
                        raise Exception(
                            "Table name {table_name} contains quotes".format(
                                table_name=table_name))

    # Extracting files
    files = [f for f in backup.namelist() if f.startswith("uploads/")]
    uploader = get_uploader()
    for f in files:
        filename = f.split(os.sep, 1)

        if len(filename
               ) < 2:  # just an empty uploads directory (e.g. uploads/)
            continue

        filename = filename[
            1]  # Get the second entry in the list (the actual filename)
        source = backup.open(f)
        uploader.store(fileobj=source, filename=filename)

    # Alembic sqlite support is lacking so we should just create_all anyway
    try:
        upgrade(revision="head")
    except (CommandError, RuntimeError, SystemExit):
        app.db.create_all()
        stamp()

    # Invalidate all cached data
    cache.clear()

    # Set default theme in case the current instance or the import does not provide it
    set_config("ctf_theme", "core")
Example #12
0
def create_app(config="CTFd.config.Config"):
    app = CTFdFlask(__name__)
    with app.app_context():
        app.config.from_object(config)

        app.theme_loader = ThemeLoader(os.path.join(app.root_path, "themes"),
                                       followlinks=True)
        # Weird nested solution for accessing plugin templates
        app.plugin_loader = jinja2.PrefixLoader({
            "plugins":
            jinja2.FileSystemLoader(searchpath=os.path.join(
                app.root_path, "plugins"),
                                    followlinks=True)
        })
        # Load from themes first but fallback to loading from the plugin folder
        app.jinja_loader = jinja2.ChoiceLoader(
            [app.theme_loader, app.plugin_loader])

        from CTFd.models import (  # noqa: F401
            db, Teams, Solves, Challenges, Fails, Flags, Tags, Files, Tracking,
        )

        url = create_database()

        # This allows any changes to the SQLALCHEMY_DATABASE_URI to get pushed back in
        # This is mostly so we can force MySQL's charset
        app.config["SQLALCHEMY_DATABASE_URI"] = str(url)

        # Register database
        db.init_app(app)

        # Register Flask-Migrate
        migrations.init_app(app, db)

        # Alembic sqlite support is lacking so we should just create_all anyway
        if url.drivername.startswith("sqlite"):
            # Enable foreign keys for SQLite. This must be before the
            # db.create_all call because tests use the in-memory SQLite
            # database (each connection, including db creation, is a new db).
            # https://docs.sqlalchemy.org/en/13/dialects/sqlite.html#foreign-key-support
            from sqlalchemy.engine import Engine
            from sqlalchemy import event

            @event.listens_for(Engine, "connect")
            def set_sqlite_pragma(dbapi_connection, connection_record):
                cursor = dbapi_connection.cursor()
                cursor.execute("PRAGMA foreign_keys=ON")
                cursor.close()

            db.create_all()
            stamp_latest_revision()
        else:
            # This creates tables instead of db.create_all()
            # Allows migrations to happen properly
            upgrade()

        from CTFd.models import ma

        ma.init_app(app)

        app.db = db
        app.VERSION = __version__
        app.CHANNEL = __channel__

        from CTFd.cache import cache

        cache.init_app(app)
        app.cache = cache

        reverse_proxy = app.config.get("REVERSE_PROXY")
        if reverse_proxy:
            if type(reverse_proxy) is str and "," in reverse_proxy:
                proxyfix_args = [int(i) for i in reverse_proxy.split(",")]
                app.wsgi_app = ProxyFix(app.wsgi_app, *proxyfix_args)
            else:
                app.wsgi_app = ProxyFix(app.wsgi_app,
                                        x_for=1,
                                        x_proto=1,
                                        x_host=1,
                                        x_port=1,
                                        x_prefix=1)

        version = utils.get_config("ctf_version")

        # Upgrading from an older version of CTFd
        if version and (StrictVersion(version) < StrictVersion(__version__)):
            if confirm_upgrade():
                run_upgrade()
            else:
                exit()

        if not version:
            utils.set_config("ctf_version", __version__)

        if not utils.get_config("ctf_theme"):
            utils.set_config("ctf_theme", "core")

        update_check(force=True)

        init_request_processors(app)
        init_template_filters(app)
        init_template_globals(app)

        # Importing here allows tests to use sensible names (e.g. api instead of api_bp)
        from CTFd.views import views
        from CTFd.teams import teams
        from CTFd.users import users
        from CTFd.challenges import challenges
        from CTFd.training import training
        from CTFd.scoreboard import scoreboard
        from CTFd.auth import auth
        from CTFd.admin import admin
        from CTFd.api import api
        from CTFd.events import events
        from CTFd.errors import page_not_found, forbidden, general_error, gateway_error

        app.register_blueprint(views)
        app.register_blueprint(teams)
        app.register_blueprint(users)
        app.register_blueprint(challenges)
        app.register_blueprint(training)
        app.register_blueprint(scoreboard)
        app.register_blueprint(auth)
        app.register_blueprint(api)
        app.register_blueprint(events)

        app.register_blueprint(admin)

        app.register_error_handler(404, page_not_found)
        app.register_error_handler(403, forbidden)
        app.register_error_handler(500, general_error)
        app.register_error_handler(502, gateway_error)

        init_logs(app)
        init_events(app)
        init_plugins(app)

        return app
Example #13
0
def admin_config():
    if request.method == "POST":
        start = None
        end = None
        freeze = None
        if request.form.get('start'):
            start = int(request.form['start'])
        if request.form.get('end'):
            end = int(request.form['end'])
        if request.form.get('freeze'):
            freeze = int(request.form['freeze'])

        try:
            view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
            view_scoreboard_if_authed = bool(request.form.get('view_scoreboard_if_authed', None))
            hide_scores = bool(request.form.get('hide_scores', None))
            prevent_registration = bool(request.form.get('prevent_registration', None))
            prevent_name_change = bool(request.form.get('prevent_name_change', None))
            view_after_ctf = bool(request.form.get('view_after_ctf', None))
            verify_emails = bool(request.form.get('verify_emails', None))
            mail_tls = bool(request.form.get('mail_tls', None))
            mail_ssl = bool(request.form.get('mail_ssl', None))
        except (ValueError, TypeError):
            view_challenges_unregistered = None
            view_scoreboard_if_authed = None
            hide_scores = None
            prevent_registration = None
            prevent_name_change = None
            view_after_ctf = None
            verify_emails = None
            mail_tls = None
            mail_ssl = None
        finally:
            view_challenges_unregistered = utils.set_config('view_challenges_unregistered', view_challenges_unregistered)
            view_scoreboard_if_authed = utils.set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
            hide_scores = utils.set_config('hide_scores', hide_scores)
            prevent_registration = utils.set_config('prevent_registration', prevent_registration)
            prevent_name_change = utils.set_config('prevent_name_change', prevent_name_change)
            view_after_ctf = utils.set_config('view_after_ctf', view_after_ctf)
            verify_emails = utils.set_config('verify_emails', verify_emails)
            mail_tls = utils.set_config('mail_tls', mail_tls)
            mail_ssl = utils.set_config('mail_ssl', mail_ssl)

        mail_server = utils.set_config("mail_server", request.form.get('mail_server', None))
        mail_port = utils.set_config("mail_port", request.form.get('mail_port', None))

        mail_username = utils.set_config("mail_username", request.form.get('mail_username', None))
        mail_password = utils.set_config("mail_password", request.form.get('mail_password', None))

        ctf_name = utils.set_config("ctf_name", request.form.get('ctf_name', None))
        ctf_theme = utils.set_config("ctf_theme", request.form.get('ctf_theme', None))

        mailfrom_addr = utils.set_config("mailfrom_addr", request.form.get('mailfrom_addr', None))
        mg_base_url = utils.set_config("mg_base_url", request.form.get('mg_base_url', None))
        mg_api_key = utils.set_config("mg_api_key", request.form.get('mg_api_key', None))

        db_freeze = utils.set_config("freeze", freeze)

        db_start = Config.query.filter_by(key='start').first()
        db_start.value = start

        db_end = Config.query.filter_by(key='end').first()
        db_end.value = end

        db.session.add(db_start)
        db.session.add(db_end)

        db.session.commit()
        db.session.close()
        with app.app_context():
            cache.clear()
        return redirect(url_for('admin.admin_config'))

    with app.app_context():
        cache.clear()
    ctf_name = utils.get_config('ctf_name')
    ctf_theme = utils.get_config('ctf_theme')
    hide_scores = utils.get_config('hide_scores')

    mail_server = utils.get_config('mail_server')
    mail_port = utils.get_config('mail_port')
    mail_username = utils.get_config('mail_username')
    mail_password = utils.get_config('mail_password')

    mailfrom_addr = utils.get_config('mailfrom_addr')
    mg_api_key = utils.get_config('mg_api_key')
    mg_base_url = utils.get_config('mg_base_url')

    view_after_ctf = utils.get_config('view_after_ctf')
    start = utils.get_config('start')
    end = utils.get_config('end')
    freeze = utils.get_config('freeze')

    mail_tls = utils.get_config('mail_tls')
    mail_ssl = utils.get_config('mail_ssl')

    view_challenges_unregistered = utils.get_config('view_challenges_unregistered')
    view_scoreboard_if_authed = utils.get_config('view_scoreboard_if_authed')
    prevent_registration = utils.get_config('prevent_registration')
    prevent_name_change = utils.get_config('prevent_name_change')
    verify_emails = utils.get_config('verify_emails')

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

    themes = utils.get_themes()
    themes.remove(ctf_theme)

    return render_template('admin/config.html',
                           ctf_name=ctf_name,
                           ctf_theme_config=ctf_theme,
                           start=start,
                           end=end,
                           freeze=freeze,
                           hide_scores=hide_scores,
                           mail_server=mail_server,
                           mail_port=mail_port,
                           mail_username=mail_username,
                           mail_password=mail_password,
                           mail_tls=mail_tls,
                           mail_ssl=mail_ssl,
                           view_challenges_unregistered=view_challenges_unregistered,
                           view_scoreboard_if_authed=view_scoreboard_if_authed,
                           prevent_registration=prevent_registration,
                           mailfrom_addr=mailfrom_addr,
                           mg_base_url=mg_base_url,
                           mg_api_key=mg_api_key,
                           prevent_name_change=prevent_name_change,
                           verify_emails=verify_emails,
                           view_after_ctf=view_after_ctf,
                           themes=themes)
Example #14
0
def create_app(config='CTFd.config.Config'):
    app = Flask(__name__)
    babel = Babel(app)
    app.config['BABEL_DEFAULT_LOCALE'] = 'hi'
    with app.app_context():
        app.config.from_object(config)
        app.jinja_loader = ThemeLoader(os.path.join(app.root_path,
                                                    app.template_folder),
                                       followlinks=True)

        from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking

        url = make_url(app.config['SQLALCHEMY_DATABASE_URI'])
        if url.drivername == 'postgres':
            url.drivername = 'postgresql'

        db.init_app(app)

        try:
            if not (url.drivername.startswith('sqlite')
                    or database_exists(url)):
                create_database(url)
            db.create_all()
        except OperationalError:
            db.create_all()
        except ProgrammingError:  ## Database already exists
            pass
        else:
            db.create_all()

        app.db = db

        migrate.init_app(app, db)

        app.config["CACHE_TYPE"] = "null"

        cache.init_app(app)
        app.cache = cache

        version = utils.get_config('ctf_version')

        if not version:  ## Upgrading from an unversioned CTFd
            utils.set_config('ctf_version', __version__)

        if version and (StrictVersion(version) < StrictVersion(__version__)
                        ):  ## Upgrading from an older version of CTFd
            print("/*\\ CTFd has updated and must update the database! /*\\")
            print("/*\\ Please backup your database before proceeding! /*\\")
            print(
                "/*\\ CTFd maintainers are not responsible for any data loss! /*\\"
            )
            if input('Run database migrations (Y/N)').lower().strip() == 'y':
                migrate_stamp()
                migrate_upgrade()
                utils.set_config('ctf_version', __version__)
            else:
                print('/*\\ Ignored database migrations... /*\\')
                exit()

        if not utils.get_config('ctf_theme'):
            utils.set_config('ctf_theme', 'original')

        from CTFd.views import views
        from CTFd.challenges import challenges
        from CTFd.scoreboard import scoreboard
        from CTFd.auth import auth
        from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_containers, admin_keys, admin_teams
        from CTFd.utils import init_utils, init_errors, init_logs

        init_utils(app)
        init_errors(app)
        init_logs(app)

        app.register_blueprint(views)
        app.register_blueprint(challenges)
        app.register_blueprint(scoreboard)
        app.register_blueprint(auth)

        app.register_blueprint(admin)
        app.register_blueprint(admin_statistics)
        app.register_blueprint(admin_challenges)
        app.register_blueprint(admin_teams)
        app.register_blueprint(admin_scoreboard)
        app.register_blueprint(admin_keys)
        app.register_blueprint(admin_containers)
        app.register_blueprint(admin_pages)

        from CTFd.plugins import init_plugins

        init_plugins(app)

        return app
Example #15
0
    def admin_config():
        if request.method == "POST":
            try:
                start = int(request.form['start'])
                end = int(request.form['end'])
            except (ValueError, TypeError):
                start = None
                end = None

            try:
                view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
                prevent_registration = bool(request.form.get('prevent_registration', None))
            except (ValueError, TypeError):
                view_challenges_unregistered = None
                prevent_registration = None

            ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
            mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
            do_api_key = set_config("do_api_key", request.form.get('do_api_key', None))

            db_start = Config.query.filter_by(key='start').first()
            db_start.value = start

            db_end = Config.query.filter_by(key='end').first()
            db_end.value = end

            db_view_challenges_unregistered = Config.query.filter_by(key='view_challenges_unregistered').first()
            db_view_challenges_unregistered.value = view_challenges_unregistered

            db_prevent_registration = Config.query.filter_by(key='prevent_registration').first()
            db_prevent_registration.value = prevent_registration

            db.session.add(db_start)
            db.session.add(db_end)
            db.session.add(db_view_challenges_unregistered)
            db.session.add(db_prevent_registration)

            db.session.commit()
            return redirect('/admin/config')

        ctf_name = get_config('ctf_name')
        if not ctf_name:
            set_config('do_api_key', None)

        mg_api_key = get_config('do_api_key')
        if not mg_api_key:
            set_config('do_api_key', None)

        do_api_key = get_config('do_api_key')
        if not do_api_key:
            set_config('do_api_key', None)

        start = get_config('start')
        if not start:
            set_config('start', None)

        end = get_config('end')
        if not end:
            set_config('end', None)

        view_challenges_unregistered = get_config('view_challenges_unregistered') == '1'
        if not view_challenges_unregistered:
            set_config('view_challenges_unregistered', None)

        prevent_registration = get_config('prevent_registration') == '1'
        if not prevent_registration:
            set_config('prevent_registration', None)

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

        return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end,
                               view_challenges_unregistered=view_challenges_unregistered,
                               prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key)
Example #16
0
def test_verify_email(mock_smtp):
    """Does verify_email send emails"""
    app = create_ctfd()
    with app.app_context():
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")
        set_config("verify_emails", True)

        from_addr = get_config("mailfrom_addr") or app.config.get(
            "MAILFROM_ADDR")
        to_addr = "*****@*****.**"

        verify_email_address(to_addr)

        # This is currently not actually validated
        msg = (
            "Please click the following link to confirm"
            " your email address for CTFd:"
            " http://localhost/confirm/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U"
        )

        ctf_name = get_config("ctf_name")
        email_msg = MIMEText(msg)
        email_msg["Subject"] = "Message from {0}".format(ctf_name)
        email_msg["From"] = from_addr
        email_msg["To"] = to_addr

        # Need to freeze time to predict the value of the itsdangerous token.
        # For now just assert that sendmail was called.
        mock_smtp.return_value.sendmail.assert_called_with(
            from_addr, [to_addr], email_msg.as_string())
    destroy_ctfd(app)
Example #17
0
def load(app):
    ########################
    # Plugin Configuration #
    ########################
    authentication_url_prefix = "/auth"
    oauth_client_id = utils.get_app_config('OAUTHLOGIN_CLIENT_ID')
    oauth_client_secret = utils.get_app_config('OAUTHLOGIN_CLIENT_SECRET')
    oauth_provider = utils.get_app_config('OAUTHLOGIN_PROVIDER')
    create_missing_user = utils.get_app_config(
        'OAUTHLOGIN_CREATE_MISSING_USER')

    ##################
    # User Functions #
    ##################
    def retrieve_user_from_database(username):
        user = Users.query.filter_by(email=username).first()
        if user is not None:
            log(
                'logins', "[{date}] {ip} - " + user.name +
                " - OAuth2 bridged user found")
            return user

    def create_user(username, displayName):
        with app.app_context():
            log(
                'logins', "[{date}] {ip} - " + user.name +
                " - No OAuth2 bridged user found, creating user")
            user = Users(email=username, name=displayName.strip())
            db.session.add(user)
            db.session.commit()
            db.session.flush()
            return user

    def create_or_get_user(username, displayName):
        user = retrieve_user_from_database(username)
        if user is not None:
            return user
        if create_missing_user:
            return create_user(username, displayName)
        else:
            log(
                'logins', "[{date}] {ip} - " + user.name +
                " - No OAuth2 bridged user found and not configured to create missing users"
            )
            return None

    ##########################
    # Provider Configuration #
    ##########################
    provider_blueprints = {
        'azure':
        lambda: flask_dance.contrib.azure.make_azure_blueprint(
            login_url='/azure',
            client_id=oauth_client_id,
            client_secret=oauth_client_secret,
            redirect_url=authentication_url_prefix + "/azure/confirm"),
        'github':
        lambda: flask_dance.contrib.github.make_github_blueprint(
            login_url='/github',
            client_id=oauth_client_id,
            client_secret=oauth_client_secret,
            redirect_url=authentication_url_prefix + "/github/confirm")
    }

    def get_azure_user():
        user_info = flask_dance.contrib.azure.azure.get("/v1.0/me").json()
        return create_or_get_user(username=user_info["userPrincipalName"],
                                  displayName=user_info["displayName"])

    def get_github_user():
        user_info = flask_dance.contrib.github.github.get("/user").json()
        return create_or_get_user(username=user_info["email"],
                                  displayName=user_info["name"])

    provider_users = {
        'azure': lambda: get_azure_user(),
        'github': lambda: get_github_user()
    }

    provider_blueprint = provider_blueprints[oauth_provider](
    )  # Resolved lambda

    #######################
    # Blueprint Functions #
    #######################
    @provider_blueprint.route('/<string:auth_provider>/confirm',
                              methods=['GET'])
    def confirm_auth_provider(auth_provider):
        if not provider_users.has_key(auth_provider):
            return redirect('/')

        provider_user = provider_users[oauth_provider]()  # Resolved lambda
        session.regenerate()
        if provider_user is not None:
            login_user(provider_user)
        return redirect('/')

    app.register_blueprint(provider_blueprint,
                           url_prefix=authentication_url_prefix)

    ###############################
    # Application Reconfiguration #
    ###############################
    # ('', 204) is "No Content" code
    set_config('registration_visibility', False)
    app.view_functions['auth.login'] = lambda: redirect(
        authentication_url_prefix + "/" + oauth_provider)
    app.view_functions['auth.register'] = lambda: ('', 204)
    app.view_functions['auth.reset_password'] = lambda: ('', 204)
    app.view_functions['auth.confirm'] = lambda: ('', 204)
Example #18
0
def test_successful_registration_email(mock_smtp):
    """Does successful_registration_notification send emails"""
    app = create_ctfd()
    with app.app_context():
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")
        set_config("verify_emails", True)

        ctf_name = get_config("ctf_name")
        from_addr = get_config("mailfrom_addr") or app.config.get(
            "MAILFROM_ADDR")
        from_addr = "{} <{}>".format(ctf_name, from_addr)

        to_addr = "*****@*****.**"

        successful_registration_notification(to_addr)

        msg = "You've successfully registered for CTFd!"

        email_msg = MIMEText(msg)
        email_msg["Subject"] = "Successfully registered for {ctf_name}".format(
            ctf_name=ctf_name)
        email_msg["From"] = from_addr
        email_msg["To"] = to_addr

        # Need to freeze time to predict the value of the itsdangerous token.
        # For now just assert that sendmail was called.
        mock_smtp.return_value.sendmail.assert_called_with(
            from_addr, [to_addr], email_msg.as_string())
    destroy_ctfd(app)
Example #19
0
def import_ctf(backup, erase=True):
    if not zipfile.is_zipfile(backup):
        raise zipfile.BadZipfile

    backup = zipfile.ZipFile(backup)

    members = backup.namelist()
    max_content_length = get_app_config("MAX_CONTENT_LENGTH")
    for f in members:
        if f.startswith("/") or ".." in f:
            # Abort on malicious zip files
            raise zipfile.BadZipfile
        info = backup.getinfo(f)
        if max_content_length:
            if info.file_size > max_content_length:
                raise zipfile.LargeZipFile

    # Get list of directories in zipfile
    member_dirs = [os.path.split(m)[0] for m in members if "/" in m]
    if "db" not in member_dirs:
        raise Exception(
            'CTFd couldn\'t find the "db" folder in this backup. '
            "The backup may be malformed or corrupted and the import process cannot continue."
        )

    try:
        alembic_version = json.loads(
            backup.open("db/alembic_version.json").read())
        alembic_version = alembic_version["results"][0]["version_num"]
    except Exception:
        raise Exception(
            "Could not determine appropriate database version. This backup cannot be automatically imported."
        )

    # Check if the alembic version is from CTFd 1.x
    if alembic_version in (
            "1ec4a28fe0ff",
            "2539d8b5082e",
            "7e9efd084c5a",
            "87733981ca0e",
            "a4e30c94c360",
            "c12d2a1b0926",
            "c7225db614c1",
            "cb3cfcc47e2f",
            "cbf5620f8e15",
            "d5a224bf5862",
            "d6514ec92738",
            "dab615389702",
            "e62fd69bd417",
    ):
        raise Exception(
            "The version of CTFd that this backup is from is too old to be automatically imported."
        )

    if erase:
        # Clear out existing connections to release any locks
        db.session.close()
        db.engine.dispose()

        # Drop database and recreate it to get to a clean state
        drop_database()
        create_database()
        # We explicitly do not want to upgrade or stamp here.
        # The import will have this information.

    side_db = dataset.connect(get_app_config("SQLALCHEMY_DATABASE_URI"))
    sqlite = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("sqlite")
    postgres = get_app_config("SQLALCHEMY_DATABASE_URI").startswith("postgres")

    try:
        if postgres:
            side_db.query("SET session_replication_role=replica;")
        else:
            side_db.query("SET FOREIGN_KEY_CHECKS=0;")
    except Exception:
        print("Failed to disable foreign key checks. Continuing.")

    first = [
        "db/teams.json",
        "db/users.json",
        "db/challenges.json",
        "db/dynamic_challenge.json",
        "db/flags.json",
        "db/hints.json",
        "db/unlocks.json",
        "db/awards.json",
        "db/tags.json",
        "db/submissions.json",
        "db/solves.json",
        "db/files.json",
        "db/notifications.json",
        "db/pages.json",
        "db/tracking.json",
        "db/config.json",
    ]

    # We want to insert certain database tables first so we are specifying
    # the order with a list. The leftover tables are tables that are from a
    # plugin (more likely) or a table where we do not care about insertion order
    for item in first:
        if item in members:
            members.remove(item)

    # Upgrade the database to the point in time that the import was taken from
    migration_upgrade(revision=alembic_version)

    members.remove("db/alembic_version.json")

    # Combine the database insertion code into a function so that we can pause
    # insertion between official database tables and plugin tables
    def insertion(table_filenames):
        for member in table_filenames:
            if member.startswith("db/"):
                table_name = member[3:-5]

                try:
                    # Try to open a file but skip if it doesn't exist.
                    data = backup.open(member).read()
                except KeyError:
                    continue

                if data:
                    table = side_db[table_name]

                    saved = json.loads(data)
                    for entry in saved["results"]:
                        # This is a hack to get SQLite to properly accept datetime values from dataset
                        # See Issue #246
                        if sqlite:
                            direct_table = get_class_by_tablename(table.name)
                            for k, v in entry.items():
                                if isinstance(v, string_types):
                                    # We only want to apply this hack to columns that are expecting a datetime object
                                    try:
                                        is_dt_column = (type(
                                            getattr(
                                                direct_table,
                                                k).type) == sqltypes.DateTime)
                                    except AttributeError:
                                        is_dt_column = False

                                    # If the table is expecting a datetime, we should check if the string is one and convert it
                                    if is_dt_column:
                                        match = re.match(
                                            r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d",
                                            v,
                                        )
                                        if match:
                                            entry[
                                                k] = datetime.datetime.strptime(
                                                    v, "%Y-%m-%dT%H:%M:%S.%f")
                                            continue
                                        match = re.match(
                                            r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}",
                                            v)
                                        if match:
                                            entry[
                                                k] = datetime.datetime.strptime(
                                                    v, "%Y-%m-%dT%H:%M:%S")
                                            continue
                        # From v2.0.0 to v2.1.0 requirements could have been a string or JSON because of a SQLAlchemy issue
                        # This is a hack to ensure we can still accept older exports. See #867
                        if member in (
                                "db/challenges.json",
                                "db/hints.json",
                                "db/awards.json",
                        ):
                            requirements = entry.get("requirements")
                            if requirements and isinstance(
                                    requirements, string_types):
                                entry["requirements"] = json.loads(
                                    requirements)

                        try:
                            table.insert(entry)
                        except ProgrammingError:
                            # MariaDB does not like JSON objects and prefers strings because it internally
                            # represents JSON with LONGTEXT.
                            # See Issue #973
                            requirements = entry.get("requirements")
                            if requirements and isinstance(requirements, dict):
                                entry["requirements"] = json.dumps(
                                    requirements)
                            table.insert(entry)

                        db.session.commit()
                    if postgres:
                        # This command is to set the next primary key ID for the re-inserted tables in Postgres. However,
                        # this command is very difficult to translate into SQLAlchemy code. Because Postgres is not
                        # officially supported, no major work will go into this functionality.
                        # https://stackoverflow.com/a/37972960
                        if '"' not in table_name and "'" not in table_name:
                            query = "SELECT setval(pg_get_serial_sequence('{table_name}', 'id'), coalesce(max(id)+1,1), false) FROM \"{table_name}\"".format(  # nosec
                                table_name=table_name)
                            side_db.engine.execute(query)
                        else:
                            raise Exception(
                                "Table name {table_name} contains quotes".
                                format(table_name=table_name))

    # Insert data from official tables
    insertion(first)

    # Create tables created by plugins
    try:
        # Run plugin migrations
        plugins = get_plugin_names()
        try:
            for plugin in plugins:
                revision = plugin_current(plugin_name=plugin)
                plugin_upgrade(plugin_name=plugin, revision=revision)
        finally:
            # Create tables that don't have migrations
            app.db.create_all()
    except OperationalError as e:
        if not postgres:
            raise e
        else:
            print("Allowing error during app.db.create_all() due to Postgres")

    # Insert data for plugin tables
    insertion(members)

    # Bring plugin tables up to head revision
    plugins = get_plugin_names()
    for plugin in plugins:
        plugin_upgrade(plugin_name=plugin)

    # Extracting files
    files = [f for f in backup.namelist() if f.startswith("uploads/")]
    uploader = get_uploader()
    for f in files:
        filename = f.split(os.sep, 1)

        if (
                len(filename) < 2 or os.path.basename(filename[1]) == ""
        ):  # just an empty uploads directory (e.g. uploads/) or any directory
            continue

        filename = filename[
            1]  # Get the second entry in the list (the actual filename)
        source = backup.open(f)
        uploader.store(fileobj=source, filename=filename)

    # Alembic sqlite support is lacking so we should just create_all anyway
    try:
        migration_upgrade(revision="head")
    except (OperationalError, CommandError, RuntimeError, SystemExit,
            Exception):
        app.db.create_all()
        stamp_latest_revision()

    try:
        if postgres:
            side_db.query("SET session_replication_role=DEFAULT;")
        else:
            side_db.query("SET FOREIGN_KEY_CHECKS=1;")
    except Exception:
        print("Failed to enable foreign key checks. Continuing.")

    # Invalidate all cached data
    cache.clear()

    # Set default theme in case the current instance or the import does not provide it
    set_config("ctf_theme", "core")
    set_config("ctf_version", CTFD_VERSION)
Example #20
0
def create_app(config='CTFd.config.Config'):
    app = CTFdFlask(__name__)
    with app.app_context():
        app.config.from_object(config)

        theme_loader = ThemeLoader(os.path.join(app.root_path, 'themes'),
                                   followlinks=True)
        app.jinja_loader = theme_loader

        from CTFd.models import db, Teams, Solves, Challenges, Fails, Flags, Tags, Files, Tracking

        url = create_database()

        # This allows any changes to the SQLALCHEMY_DATABASE_URI to get pushed back in
        # This is mostly so we can force MySQL's charset
        app.config['SQLALCHEMY_DATABASE_URI'] = str(url)

        # Register database
        db.init_app(app)

        # Register Flask-Migrate
        migrations.init_app(app, db)

        # Alembic sqlite support is lacking so we should just create_all anyway
        if url.drivername.startswith('sqlite'):
            db.create_all()
            stamp()
        else:
            # This creates tables instead of db.create_all()
            # Allows migrations to happen properly
            upgrade()

        from CTFd.models import ma

        ma.init_app(app)

        app.db = db
        app.VERSION = __version__

        from CTFd.cache import cache

        cache.init_app(app)
        app.cache = cache

        # If you have multiple workers you must have a shared cache
        socketio.init_app(app,
                          async_mode=app.config.get('SOCKETIO_ASYNC_MODE'),
                          message_queue=app.config.get('CACHE_REDIS_URL'))

        if app.config.get('REVERSE_PROXY'):
            app.wsgi_app = ProxyFix(app.wsgi_app)

        version = utils.get_config('ctf_version')

        # Upgrading from an older version of CTFd
        if version and (StrictVersion(version) < StrictVersion(__version__)):
            if confirm_upgrade():
                run_upgrade()
            else:
                exit()

        if not version:
            utils.set_config('ctf_version', __version__)

        if not utils.get_config('ctf_theme'):
            utils.set_config('ctf_theme', 'core')

        update_check(force=True)

        init_request_processors(app)
        init_template_filters(app)
        init_template_globals(app)

        # Importing here allows tests to use sensible names (e.g. api instead of api_bp)
        from CTFd.views import views
        from CTFd.teams import teams
        from CTFd.users import users
        from CTFd.challenges import challenges
        from CTFd.scoreboard import scoreboard
        from CTFd.auth import auth
        from CTFd.admin import admin
        from CTFd.api import api
        from CTFd.events import events
        from CTFd.errors import page_not_found, forbidden, general_error, gateway_error

        app.register_blueprint(views)
        app.register_blueprint(teams)
        app.register_blueprint(users)
        app.register_blueprint(challenges)
        app.register_blueprint(scoreboard)
        app.register_blueprint(auth)
        app.register_blueprint(api)
        app.register_blueprint(events)

        app.register_blueprint(admin)

        app.register_error_handler(404, page_not_found)
        app.register_error_handler(403, forbidden)
        app.register_error_handler(500, general_error)
        app.register_error_handler(502, gateway_error)

        init_logs(app)
        init_plugins(app)

        return app
Example #21
0
def test_challenges_under_view_after_ctf():
    app = create_ctfd()
    with app.app_context(), freeze_time("2017-10-7"):
        set_config("start", "1507089600"
                   )  # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
        set_config(
            "end",
            "1507262400")  # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST

        register_user(app)
        client = login_as_user(app)

        gen_challenge(app.db)
        gen_flag(app.db, challenge_id=1, content="flag")

        r = client.get("/challenges")
        assert r.status_code == 403

        r = client.get("/api/v1/challenges")
        assert r.status_code == 403
        assert r.get_json().get("data") is None

        r = client.get("/api/v1/challenges/1")
        assert r.status_code == 403
        assert r.get_json().get("data") is None

        data = {"submission": "flag", "challenge_id": 1}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 403
        assert r.get_json().get("data") is None
        assert Solves.query.count() == 0

        data = {"submission": "notflag", "challenge_id": 1}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 403
        assert r.get_json().get("data") is None
        assert Fails.query.count() == 0

        set_config("view_after_ctf", True)

        r = client.get("/challenges")
        assert r.status_code == 200

        r = client.get("/api/v1/challenges")
        assert r.status_code == 200
        assert r.get_json()["data"][0]["id"] == 1

        r = client.get("/api/v1/challenges/1")
        assert r.status_code == 200
        assert r.get_json()["data"]["id"] == 1

        data = {"submission": "flag", "challenge_id": 1}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        assert r.get_json()["data"]["status"] == "correct"
        assert Solves.query.count() == 0

        data = {"submission": "notflag", "challenge_id": 1}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        assert r.get_json()["data"]["status"] == "incorrect"
        assert Fails.query.count() == 0

    destroy_ctfd(app)
Example #22
0
def setup():
    # with app.app_context():
        # admin = Teams.query.filter_by(admin=True).first()

    if not is_setup():
        if not session.get('nonce'):
            session['nonce'] = sha512(os.urandom(10))
        if request.method == 'POST':
            ctf_name = request.form['ctf_name']
            ctf_name = set_config('ctf_name', ctf_name)

            ## CSS
            css = set_config('start', '')

            ## Admin user
            name = request.form['name']
            email = request.form['email']
            password = request.form['password']
            admin = Teams(name, email, password)
            admin.admin = True
            admin.banned = True

            ## Index page
            page = Pages('index', """<div class="container main-container">
    <img class="logo" src="{0}/static/original/img/logo.png" />
    <h3 class="text-center">
        Welcome to a cool CTF framework written by <a href="https://github.com/ColdHeat">Kevin Chung</a> of <a href="https://github.com/isislab">@isislab</a>
    </h3>

    <h4 class="text-center">
        <a href="{0}/admin">Click here</a> to login and setup your CTF
    </h4>
</div>""".format(request.script_root))

            #max attempts per challenge
            max_tries = set_config("max_tries",0)

            ## Start time
            start = set_config('start', None)
            end = set_config('end', None)

            ## Challenges cannot be viewed by unregistered users
            view_challenges_unregistered = set_config('view_challenges_unregistered', None)

            ## Allow/Disallow registration
            prevent_registration = set_config('prevent_registration', None)

            ## Verify emails
            verify_emails = set_config('verify_emails', None)

            mail_server = set_config('mail_server', None)
            mail_port = set_config('mail_port', None)
            mail_tls = set_config('mail_tls', None)
            mail_ssl = set_config('mail_ssl', None)
            mail_username = set_config('mail_username', None)
            mail_password = set_config('mail_password', None)

            setup = set_config('setup', True)

            db.session.add(page)
            db.session.add(admin)
            db.session.commit()
            db.session.close()
            app.setup = False
            with app.app_context():
                cache.clear()
            return redirect(url_for('views.static_html'))
        return render_template('setup.html', nonce=session.get('nonce'))
    return redirect(url_for('views.static_html'))
Example #23
0
def test_user_can_reset_password(mock_smtp):
    """Test that a user is capable of resetting their password"""
    from email.mime.text import MIMEText
    app = create_ctfd()
    with app.app_context(), freeze_time("2012-01-14 03:21:34"):
        # Set CTFd to send emails
        set_config('mail_server', 'localhost')
        set_config('mail_port', 25)
        set_config('mail_useauth', True)
        set_config('mail_username', 'username')
        set_config('mail_password', 'password')

        # Create a user
        register_user(app, name="user1", email="*****@*****.**")

        with app.test_client() as client:
            client.get('/reset_password')

            # Build reset password data
            with client.session_transaction() as sess:
                data = {'nonce': sess.get('nonce'), 'email': '*****@*****.**'}

            # Issue the password reset request
            client.post('/reset_password', data=data)

            from_addr = get_config('mailfrom_addr') or app.config.get(
                'MAILFROM_ADDR')
            to_addr = '*****@*****.**'

            # Build the email
            msg = (
                """Did you initiate a password reset? Click the following link to reset """
                """your password:\n\nhttp://localhost/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M\n\n"""
            )
            email_msg = MIMEText(msg)
            email_msg['Subject'] = "Message from CTFd"
            email_msg['From'] = from_addr
            email_msg['To'] = to_addr

            # Make sure that the reset password email is sent
            mock_smtp.return_value.sendmail.assert_called_with(
                from_addr, [to_addr], email_msg.as_string())

            # Get user's original password
            user = Users.query.filter_by(email="*****@*****.**").first()

            # Build the POST data
            with client.session_transaction() as sess:
                data = {'nonce': sess.get('nonce'), 'password': '******'}

            # Do the password reset
            client.get(
                '/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M'
            )
            client.post(
                '/reset_password/InVzZXIxIg.TxD0vg.-gvVg-KVy0RWkiclAE6JViv1I0M',
                data=data)

            # Make sure that the user's password changed
            user = Users.query.filter_by(email="*****@*****.**").first()
            assert verify_password('passwordtwo', user.password)
    destroy_ctfd(app)
Example #24
0
def test_api_challenges_solves_score_visibility():
    """Can a user load /api/v1/challenges/<challenge_id>/solves if score_visibility is public/private/admin"""
    app = create_ctfd()
    with app.app_context(), freeze_time("2017-10-5"):
        set_config(
            "start", "1507089600"
        )  # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
        set_config(
            "end", "1507262400"
        )  # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
        set_config("challenge_visibility", "public")
        set_config("score_visibility", "public")
        gen_challenge(app.db)
        with app.test_client() as client:
            r = client.get("/api/v1/challenges/1/solves")
            assert r.status_code == 200
        set_config("challenge_visibility", "private")
        set_config("score_visibility", "private")
        register_user(app)
        private_client = login_as_user(app)
        r = private_client.get("/api/v1/challenges/1/solves")
        assert r.status_code == 200
        set_config("score_visibility", "admin")
        admin = login_as_user(app, "admin", "password")
        r = admin.get("/api/v1/challenges/1/solves")
        assert r.status_code == 200
    destroy_ctfd(app)
Example #25
0
def test_user_can_confirm_email(mock_smtp):
    """Test that a user is capable of confirming their email address"""
    app = create_ctfd()
    with app.app_context(), freeze_time("2012-01-14 03:21:34"):
        # Set CTFd to only allow confirmed users and send emails
        set_config("verify_emails", True)
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")

        register_user(app, name="user1", email="*****@*****.**")

        # Teams are not verified by default
        user = Users.query.filter_by(email="*****@*****.**").first()
        assert user.verified is False

        client = login_as_user(app, name="user1", password="******")

        r = client.get("http://localhost/confirm")
        assert "We've sent a confirmation email" in r.get_data(as_text=True)

        # smtp send message function was called
        mock_smtp.return_value.send_message.assert_called()

        with client.session_transaction() as sess:
            data = {"nonce": sess.get("nonce")}
            r = client.post("http://localhost/confirm", data=data)
            assert "Confirmation email sent to" in r.get_data(as_text=True)

            r = client.get("/challenges")
            assert (r.location == "http://localhost/confirm"
                    )  # We got redirected to /confirm

            r = client.get("http://localhost/confirm/" +
                           serialize("*****@*****.**"))
            assert r.location == "http://localhost/challenges"

            # The team is now verified
            user = Users.query.filter_by(email="*****@*****.**").first()
            assert user.verified is True

            r = client.get("http://localhost/confirm")
            assert r.location == "http://localhost/settings"
    destroy_ctfd(app)
Example #26
0
def setup():
    # with app.app_context():
    # admin = Teams.query.filter_by(admin=True).first()

    if not utils.is_setup():
        if not session.get('nonce'):
            session['nonce'] = utils.sha512(os.urandom(10))
        if request.method == 'POST':
            ctf_name = request.form['ctf_name']
            ctf_name = utils.set_config('ctf_name', ctf_name)

            # CSS
            css = utils.set_config('start', '')

            # Admin user
            name = request.form['name']
            email = request.form['email']
            password = request.form['password']
            admin = Teams(name, email, password)
            admin.admin = True
            admin.banned = True

            # Index page
            page = Pages(
                'index',
                """<div class="container main-container index" id="index">
    <img class="logo" src="static/original/img/logo.png" />
    <h3 class="text-center">
        <p>A cool CTF platform from (tom)<a href="https://ctfd.io">ctfd.io</a></p>
        <p>Follow us on social media:</p>
        <a href="https://twitter.com/ctfdio"><i class="fa fa-twitter fa-2x" aria-hidden="true"></i></a>&nbsp;
        <a href="https://facebook.com/ctfdio"><i class="fa fa-facebook-official fa-2x" aria-hidden="true"></i></a>&nbsp;
        <a href="https://github.com/ctfd"><i class="fa 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>""".format(request.script_root))

            # max attempts per challenge
            max_tries = utils.set_config('max_tries', 0)

            # Start time
            start = utils.set_config('start', None)
            end = utils.set_config('end', None)
            freeze = utils.set_config('freeze', None)

            # Challenges cannot be viewed by unregistered users
            view_challenges_unregistered = utils.set_config(
                'view_challenges_unregistered', None)

            # Allow/Disallow registration
            prevent_registration = utils.set_config('prevent_registration',
                                                    None)

            # Verify emails
            verify_emails = utils.set_config('verify_emails', None)

            mail_server = utils.set_config('mail_server', None)
            mail_port = utils.set_config('mail_port', None)
            mail_tls = utils.set_config('mail_tls', None)
            mail_ssl = utils.set_config('mail_ssl', None)
            mail_username = utils.set_config('mail_username', None)
            mail_password = utils.set_config('mail_password', None)

            setup = utils.set_config('setup', True)

            db.session.add(page)
            db.session.add(admin)
            db.session.commit()

            session['username'] = admin.name
            session['id'] = admin.id
            session['admin'] = admin.admin
            session['nonce'] = utils.sha512(os.urandom(10))

            db.session.close()
            app.setup = False
            with app.app_context():
                cache.clear()

            return redirect(url_for('views.static_html'))
        return render_template('setup.html', nonce=session.get('nonce'))
    return redirect(url_for('views.static_html'))
Example #27
0
def test_user_can_reset_password(mock_smtp):
    """Test that a user is capable of resetting their password"""
    from email.message import EmailMessage

    app = create_ctfd()
    with app.app_context(), freeze_time("2012-01-14 03:21:34"):
        # Set CTFd to send emails
        set_config("mail_server", "localhost")
        set_config("mail_port", 25)
        set_config("mail_useauth", True)
        set_config("mail_username", "username")
        set_config("mail_password", "password")

        # Create a user
        register_user(app, name="user1", email="*****@*****.**")

        with app.test_client() as client:
            client.get("/reset_password")

            # Build reset password data
            with client.session_transaction() as sess:
                data = {"nonce": sess.get("nonce"), "email": "*****@*****.**"}

            # Issue the password reset request
            client.post("/reset_password", data=data)

            ctf_name = get_config("ctf_name")
            from_addr = get_config("mailfrom_addr") or app.config.get(
                "MAILFROM_ADDR")
            from_addr = "{} <{}>".format(ctf_name, from_addr)

            to_addr = "*****@*****.**"

            # Build the email
            msg = (
                "Did you initiate a password reset on CTFd? If you didn't initiate this request you can ignore this email. "
                "\n\nClick the following link to reset your password:\n"
                "http://localhost/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U\n\n"
                "If the link is not clickable, try copying and pasting it into your browser."
            )
            ctf_name = get_config("ctf_name")

            email_msg = EmailMessage()
            email_msg.set_content(msg)

            email_msg[
                "Subject"] = "Password Reset Request from {ctf_name}".format(
                    ctf_name=ctf_name)
            email_msg["From"] = from_addr
            email_msg["To"] = to_addr

            # Make sure that the reset password email is sent
            mock_smtp.return_value.send_message.assert_called()
            assert str(mock_smtp.return_value.send_message.call_args[0]
                       [0]) == str(email_msg)

            # Get user's original password
            user = Users.query.filter_by(email="*****@*****.**").first()

            # Build the POST data
            with client.session_transaction() as sess:
                data = {"nonce": sess.get("nonce"), "password": "******"}

            # Do the password reset
            client.get(
                "/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U"
            )
            client.post(
                "/reset_password/InVzZXJAdXNlci5jb20i.TxD0vg.28dY_Gzqb1TH9nrcE_H7W8YFM-U",
                data=data,
            )

            # Make sure that the user's password changed
            user = Users.query.filter_by(email="*****@*****.**").first()
            assert verify_password("passwordtwo", user.password)
    destroy_ctfd(app)
Example #28
0
def test_user_can_reset_password(mock_smtp):
    '''Test that a user is capable of resetting their password'''
    from email.mime.text import MIMEText
    app = create_ctfd()
    with app.app_context():
        # Set CTFd to send emails
        set_config('mail_server', 'localhost')
        set_config('mail_port', 25)
        set_config('mail_username', 'username')
        set_config('mail_password', 'password')

        # Create a user
        register_user(app, name="user1", email="*****@*****.**")

        with app.test_client() as client:
            r = client.get('/reset_password')

            # Build reset password data
            with client.session_transaction() as sess:
                data = {'nonce': sess.get('nonce'), 'email': '*****@*****.**'}

            # Issue the password reset request
            r = client.post('/reset_password', data=data)

            from_addr = get_config('mailfrom_addr') or app.config.get(
                'MAILFROM_ADDR')
            to_addr = '*****@*****.**'

            # Build the email
            msg = """Did you initiate a password reset? Click the following link to reset your password:

http://localhost/reset_password/InVzZXIxIi5BZktHUGcuTVhkTmZtOWU2U2xwSXZ1MlFwTjdwa3F5V3hR

"""
            email_msg = MIMEText(msg)
            email_msg['Subject'] = "Message from CTFd"
            email_msg['From'] = from_addr
            email_msg['To'] = to_addr

            # Make sure that the reset password email is sent
            mock_smtp.return_value.sendmail.assert_called_with(
                from_addr, [to_addr], email_msg.as_string())

            # Get user's original password
            team = Teams.query.filter_by(email="*****@*****.**").first()
            team_password_saved = team.password

            # Build the POST data
            with client.session_transaction() as sess:
                data = {'nonce': sess.get('nonce'), 'password': '******'}

            # Do the password reset
            r = client.get('/reset_password')
            r = client.post(
                '/reset_password/InVzZXIxIi5BZktHUGcuTVhkTmZtOWU2U2xwSXZ1MlFwTjdwa3F5V3hR',
                data=data)

            # Make sure that the user's password changed
            team = Teams.query.filter_by(email="*****@*****.**").first()
            assert team.password != team_password_saved
    destroy_ctfd(app)
Example #29
0
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
            theme = request.form.get("ctf_theme", "core")
            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
            )

            # Index page

            index = """<div class="row">
    <div class="col-md-9 offset-md-2">
        <img class="w-100 mx-auto d-block" style="max-width: 500px;padding: 50px;padding-top: 14vh;" src="themes/core/static/img/xsutd-istd-logo-web-2021.png.pagespeed.ic.w1dqDYZAE-.webp">
        <h3 class="text-center">
            <p>A CTF platform for System Security course</p>
        </h3>
        <br>
        <h4 class="text-center">
            <a href="admin">Click here</a> to login and setup your CTF
        </h4>
    </div>
</div>"""

# """<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="themes/core/static/img/logo.png" />
#         <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>&nbsp;
#             <a href="https://facebook.com/ctfdio"><i class="fab fa-facebook fa-2x" aria-hidden="true"></i></a>&nbsp;
#             <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 = Pages(title=None, route="index", content=index, draft=False)

            # 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"))
        return render_template("setup.html", state=serialize(generate_nonce()))
    return redirect(url_for("views.static_html"))
Example #30
0
def setup():
    if not config.is_setup():
        if not session.get('nonce'):
            session['nonce'] = generate_nonce()
        if request.method == 'POST':
            ctf_name = request.form['ctf_name']
            set_config('ctf_name', ctf_name)

            # CSS
            set_config('start', '')

            # Admin user
            name = request.form['name']
            email = request.form['email']
            password = request.form['password']
            admin = Admins(name=name,
                           email=email,
                           password=password,
                           type='admin',
                           hidden=True)

            user_mode = request.form['user_mode']

            set_config('user_mode', user_mode)

            # Index page

            index = """<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="themes/core/static/img/logo.png" />
        <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>&nbsp;
            <a href="https://facebook.com/ctfdio"><i class="fab fa-facebook fa-2x" aria-hidden="true"></i></a>&nbsp;
            <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>""".format(request.script_root)

            page = Pages(title=None, route='index', content=index, draft=False)
            # Visibility
            set_config('challenge_visibility', 'private')
            set_config('registration_visibility', 'public')
            set_config('score_visibility', 'public')
            set_config('account_visibility', 'public')

            # Start time
            set_config('start', None)
            set_config('end', None)
            set_config('freeze', None)

            # 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)

            setup = 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()
            app.setup = False
            with app.app_context():
                cache.clear()

            return redirect(url_for('views.static_html'))
        return render_template('setup.html', nonce=session.get('nonce'))
    return redirect(url_for('views.static_html'))
Example #31
0
def admin_css():
    if request.method == 'POST':
        css = request.form['css']
        css = set_config('css', css)
        return "1"
    return "0"
Example #32
0
def create_app(config="CTFd.config.Config"):
    app = CTFdFlask(__name__)
    with app.app_context():
        app.config.from_object(config)

        theme_loader = ThemeLoader(os.path.join(app.root_path, "themes"),
                                   followlinks=True)
        app.jinja_loader = theme_loader

        from CTFd.models import (  # noqa: F401
            db, Teams, Solves, Challenges, Fails, Flags, Tags, Files, Tracking,
        )

        url = create_database()

        # This allows any changes to the SQLALCHEMY_DATABASE_URI to get pushed back in
        # This is mostly so we can force MySQL's charset
        app.config["SQLALCHEMY_DATABASE_URI"] = str(url)

        # Register database
        db.init_app(app)

        # Register Flask-Migrate
        migrations.init_app(app, db)

        # Alembic sqlite support is lacking so we should just create_all anyway
        if url.drivername.startswith("sqlite"):
            db.create_all()
            stamp_latest_revision()
        else:
            # This creates tables instead of db.create_all()
            # Allows migrations to happen properly
            upgrade()

        from CTFd.models import ma

        ma.init_app(app)

        app.db = db
        app.VERSION = __version__

        from CTFd.cache import cache

        cache.init_app(app)
        app.cache = cache

        reverse_proxy = app.config.get("REVERSE_PROXY")
        if reverse_proxy:
            if type(reverse_proxy) is str and "," in reverse_proxy:
                proxyfix_args = [int(i) for i in reverse_proxy.split(",")]
                app.wsgi_app = ProxyFix(app.wsgi_app, None, *proxyfix_args)
            else:
                app.wsgi_app = ProxyFix(
                    app.wsgi_app,
                    num_proxies=None,
                    x_for=1,
                    x_proto=1,
                    x_host=1,
                    x_port=1,
                    x_prefix=1,
                )

        version = utils.get_config("ctf_version")

        # Upgrading from an older version of CTFd
        if version and (StrictVersion(version) < StrictVersion(__version__)):
            if confirm_upgrade():
                run_upgrade()
            else:
                exit()

        if not version:
            utils.set_config("ctf_version", __version__)

        if not utils.get_config("ctf_theme"):
            utils.set_config("ctf_theme", "core")

        update_check(force=True)

        init_request_processors(app)
        init_template_filters(app)
        init_template_globals(app)

        # Importing here allows tests to use sensible names (e.g. api instead of api_bp)
        from CTFd.views import views
        from CTFd.teams import teams
        from CTFd.users import users
        from CTFd.challenges import challenges
        from CTFd.scoreboard import scoreboard
        from CTFd.auth import auth
        from CTFd.admin import admin
        from CTFd.api import api
        from CTFd.events import events
        from CTFd.errors import page_not_found, forbidden, general_error, gateway_error

        app.register_blueprint(views)
        app.register_blueprint(teams)
        app.register_blueprint(users)
        app.register_blueprint(challenges)
        app.register_blueprint(scoreboard)
        app.register_blueprint(auth)
        app.register_blueprint(api)
        app.register_blueprint(events)

        app.register_blueprint(admin)

        app.register_error_handler(404, page_not_found)
        app.register_error_handler(403, forbidden)
        app.register_error_handler(500, general_error)
        app.register_error_handler(502, gateway_error)

        init_logs(app)
        init_events(app)
        init_plugins(app)

        return app
Example #33
0
def setup():
    # with app.app_context():
        # admin = Teams.query.filter_by(admin=True).first()

    if not is_setup():
        if not session.get('nonce'):
            session['nonce'] = sha512(os.urandom(10))
        if request.method == 'POST':
            ctf_name = request.form['ctf_name']
            ctf_name = set_config('ctf_name', ctf_name)

            ## CSS
            css = set_config('start', '')

            ## Admin user
            name = request.form['name']
            email = request.form['email']
            password = request.form['password']
            admin = Teams(name, email, password)
            admin.admin = True
            admin.banned = True

            ## Index page
            page = Pages('index', """<div class="container main-container">
    <img class="logo" src="/static/img/logo.png" />
    <h3 class="text-center">
        Welcome to a cool CTF framework written by <a href="https://github.com/ColdHeat">Kevin Chung</a> of <a href="https://github.com/isislab">@isislab</a>
    </h3>

    <h4 class="text-center">
        <a href="/admin">Click here</a> to login and setup your CTF
    </h4>
</div>""")

            #max attempts per challenge
            max_tries = set_config("max_tries",0)

            ## Start time
            start = set_config('start', None)
            end = set_config('end', None)

            ## Challenges cannot be viewed by unregistered users
            view_challenges_unregistered = set_config('view_challenges_unregistered', None)

            ## Allow/Disallow registration
            prevent_registration = set_config('prevent_registration', None)

            ## Verify emails
            verify_emails = set_config('verify_emails', None)

            mail_server = set_config('mail_server', None)
            mail_port = set_config('mail_port', None)
            mail_tls = set_config('mail_tls', None)
            mail_ssl = set_config('mail_ssl', None)
            mail_username = set_config('mail_username', None)
            mail_password = set_config('mail_password', None)

            setup = set_config('setup', True)

            db.session.add(page)
            db.session.add(admin)
            db.session.commit()
            app.setup = False
            return redirect('/')
        return render_template('setup.html', nonce=session.get('nonce'))
    return redirect('/')
Example #34
0
def run_upgrade():
    upgrade()
    utils.set_config('ctf_version', __version__)
Example #35
0
def admin_config():
    if request.method == "POST":
        try:
            start = int(request.form['start'])
            end = int(request.form['end'])
        except (ValueError, TypeError):
            start = None
            end = None

        try:
            view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
            prevent_registration = bool(request.form.get('prevent_registration', None))
            prevent_name_change = bool(request.form.get('prevent_name_change', None))
            view_after_ctf = bool(request.form.get('view_after_ctf', None))
        except (ValueError, TypeError):
            view_challenges_unregistered = None
            prevent_registration = None
            prevent_name_change = None
            view_after_ctf = None
        finally:
            view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
            prevent_registration = set_config('prevent_registration', prevent_registration)
            prevent_name_change = set_config('prevent_name_change', prevent_name_change)
            view_after_ctf = set_config('view_after_ctf', view_after_ctf)

        ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
        mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
        max_tries = set_config("max_tries", request.form.get('max_tries', None))


        db_start = Config.query.filter_by(key='start').first()
        db_start.value = start

        db_end = Config.query.filter_by(key='end').first()
        db_end.value = end

        db.session.add(db_start)
        db.session.add(db_end)

        db.session.commit()
        return redirect('/admin/config')

    ctf_name = get_config('ctf_name')
    if not ctf_name:
        set_config('ctf_name', None)

    mg_api_key = get_config('mg_api_key')
    if not mg_api_key:
        set_config('mg_api_key', None)

    max_tries = get_config('max_tries')
    if not max_tries:
        set_config('max_tries', 0)
        max_tries = 0

    view_after_ctf = get_config('view_after_ctf') == '1'
    if not view_after_ctf:
        set_config('view_after_ctf', 0)
        view_after_ctf = 0

    start = get_config('start')
    if not start:
        set_config('start', None)

    end = get_config('end')
    if not end:
        set_config('end', None)

    view_challenges_unregistered = get_config('view_challenges_unregistered') == '1'
    if not view_challenges_unregistered:
        set_config('view_challenges_unregistered', None)

    prevent_registration = get_config('prevent_registration') == '1'
    if not prevent_registration:
        set_config('prevent_registration', None)

    prevent_name_change = get_config('prevent_name_change') == '1'
    if not prevent_name_change:
        set_config('prevent_name_change', None)

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

    return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end,
                           max_tries=max_tries,
                           view_challenges_unregistered=view_challenges_unregistered,
                           prevent_registration=prevent_registration, mg_api_key=mg_api_key,
                           prevent_name_change=prevent_name_change,
                           view_after_ctf=view_after_ctf)
Example #36
0
def admin_config():
    if request.method == "POST":
        start = None
        end = None
        if request.form.get('start'):
            start = int(request.form['start'])
        if request.form.get('end'):
            end = int(request.form['end'])
            if end < unix_time(datetime.datetime.now()):
                end = None

        try:
            view_challenges_unregistered = bool(
                request.form.get('view_challenges_unregistered', None))
            view_scoreboard_if_authed = bool(
                request.form.get('view_scoreboard_if_authed', None))
            prevent_registration = bool(
                request.form.get('prevent_registration', None))
            prevent_name_change = bool(
                request.form.get('prevent_name_change', None))
            view_after_ctf = bool(request.form.get('view_after_ctf', None))
            verify_emails = bool(request.form.get('verify_emails', None))
            mail_tls = bool(request.form.get('mail_tls', None))
            mail_ssl = bool(request.form.get('mail_ssl', None))
        except (ValueError, TypeError):
            view_challenges_unregistered = None
            view_scoreboard_if_authed = None
            prevent_registration = None
            prevent_name_change = None
            view_after_ctf = None
            verify_emails = None
            mail_tls = None
            mail_ssl = None
        finally:
            view_challenges_unregistered = set_config(
                'view_challenges_unregistered', view_challenges_unregistered)
            view_scoreboard_if_authed = set_config('view_scoreboard_if_authed',
                                                   view_scoreboard_if_authed)
            prevent_registration = set_config('prevent_registration',
                                              prevent_registration)
            prevent_name_change = set_config('prevent_name_change',
                                             prevent_name_change)
            view_after_ctf = set_config('view_after_ctf', view_after_ctf)
            verify_emails = set_config('verify_emails', verify_emails)
            mail_tls = set_config('mail_tls', mail_tls)
            mail_ssl = set_config('mail_ssl', mail_ssl)

        mail_server = set_config("mail_server",
                                 request.form.get('mail_server', None))
        mail_port = set_config("mail_port",
                               request.form.get('mail_port', None))

        mail_username = set_config("mail_username",
                                   request.form.get('mail_username', None))
        mail_password = set_config("mail_password",
                                   request.form.get('mail_password', None))

        ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
        ctf_theme = set_config("ctf_theme",
                               request.form.get('ctf_theme', None))

        mg_base_url = set_config("mg_base_url",
                                 request.form.get('mg_base_url', None))
        mg_api_key = set_config("mg_api_key",
                                request.form.get('mg_api_key', None))

        max_tries = set_config("max_tries",
                               request.form.get('max_tries', None))

        db_start = Config.query.filter_by(key='start').first()
        db_start.value = start

        db_end = Config.query.filter_by(key='end').first()
        db_end.value = end

        db.session.add(db_start)
        db.session.add(db_end)

        db.session.commit()
        db.session.close()
        return redirect(url_for('admin.admin_config', _external=True))

    ctf_name = get_config('ctf_name')
    ctf_theme = get_config('ctf_theme')
    max_tries = get_config('max_tries')

    mail_server = get_config('mail_server')
    mail_port = get_config('mail_port')
    mail_username = get_config('mail_username')
    mail_password = get_config('mail_password')

    mg_api_key = get_config('mg_api_key')
    mg_base_url = get_config('mg_base_url')
    if not max_tries:
        set_config('max_tries', 0)
        max_tries = 0

    view_after_ctf = get_config('view_after_ctf')
    start = get_config('start')
    end = get_config('end')

    mail_tls = get_config('mail_tls')
    mail_ssl = get_config('mail_ssl')

    view_challenges_unregistered = get_config('view_challenges_unregistered')
    view_scoreboard_if_authed = get_config('view_scoreboard_if_authed')
    prevent_registration = get_config('prevent_registration')
    prevent_name_change = get_config('prevent_name_change')
    verify_emails = get_config('verify_emails')

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

    months = [
        'January', 'February', 'March', 'April', 'May', 'June', 'July',
        'August', 'September', 'October', 'November', 'December'
    ]

    curr_year = datetime.date.today().year
    start_days = 0
    end_days = 0

    if start:
        start = datetime.datetime.fromtimestamp(float(start))
        start_days = calendar.monthrange(start.year, start.month)[1]
    if end:
        end = datetime.datetime.fromtimestamp(float(end))
        end_days = calendar.monthrange(end.year, end.month)[1]

    themes = get_themes()
    themes.remove(ctf_theme)

    return render_template(
        'admin/config.html',
        ctf_name=ctf_name,
        ctf_theme_config=ctf_theme,
        start=start,
        end=end,
        max_tries=max_tries,
        mail_server=mail_server,
        mail_port=mail_port,
        mail_username=mail_username,
        mail_password=mail_password,
        mail_tls=mail_tls,
        mail_ssl=mail_ssl,
        view_challenges_unregistered=view_challenges_unregistered,
        view_scoreboard_if_authed=view_scoreboard_if_authed,
        prevent_registration=prevent_registration,
        mg_base_url=mg_base_url,
        mg_api_key=mg_api_key,
        prevent_name_change=prevent_name_change,
        verify_emails=verify_emails,
        view_after_ctf=view_after_ctf,
        months=months,
        curr_year=curr_year,
        start_days=start_days,
        end_days=end_days,
        themes=themes)
Example #37
0
def create_app(config='CTFd.config.Config'):
    app = Flask(__name__)
    with app.app_context():
        app.config.from_object(config)
        app.jinja_loader = ThemeLoader(os.path.join(app.root_path, 'themes'), followlinks=True)

        from CTFd.models import db, Teams, Solves, Challenges, WrongKeys, Keys, Tags, Files, Tracking

        url = make_url(app.config['SQLALCHEMY_DATABASE_URI'])
        if url.drivername == 'postgres':
            url.drivername = 'postgresql'

        if url.drivername.startswith('mysql'):
            url.query['charset'] = 'utf8mb4'

        # Creates database if the database database does not exist
        if not database_exists(url):
            if url.drivername.startswith('mysql'):
                create_database(url, encoding='utf8mb4')
            else:
                create_database(url)

        # This allows any changes to the SQLALCHEMY_DATABASE_URI to get pushed back in
        # This is mostly so we can force MySQL's charset
        app.config['SQLALCHEMY_DATABASE_URI'] = str(url)

        # Register database
        db.init_app(app)

        # Register Flask-Migrate
        migrate.init_app(app, db)

        # This creates tables instead of db.create_all()
        # Allows migrations to happen properly
        migrate_upgrade()

        # Alembic sqlite support is lacking so we should just create_all anyway
        if url.drivername.startswith('sqlite'):
            db.create_all()

        app.db = db

        cache.init_app(app)
        app.cache = cache

        version = utils.get_config('ctf_version')

        if not version:  # Upgrading from an unversioned CTFd
            utils.set_config('ctf_version', __version__)

        if version and (StrictVersion(version) < StrictVersion(__version__)):  # Upgrading from an older version of CTFd
            print("/*\\ CTFd has updated and must update the database! /*\\")
            print("/*\\ Please backup your database before proceeding! /*\\")
            print("/*\\ CTFd maintainers are not responsible for any data loss! /*\\")
            if input('Run database migrations (Y/N)').lower().strip() == 'y':
                migrate_stamp()
                migrate_upgrade()
                utils.set_config('ctf_version', __version__)
            else:
                print('/*\\ Ignored database migrations... /*\\')
                exit()

        if not utils.get_config('ctf_theme'):
            utils.set_config('ctf_theme', 'original')

        from CTFd.views import views
        from CTFd.challenges import challenges
        from CTFd.scoreboard import scoreboard
        from CTFd.auth import auth
        from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_containers, admin_keys, admin_teams
        from CTFd.utils import init_utils, init_errors, init_logs

        init_utils(app)
        init_errors(app)
        init_logs(app)

        app.register_blueprint(views)
        app.register_blueprint(challenges)
        app.register_blueprint(scoreboard)
        app.register_blueprint(auth)

        app.register_blueprint(admin)
        app.register_blueprint(admin_statistics)
        app.register_blueprint(admin_challenges)
        app.register_blueprint(admin_teams)
        app.register_blueprint(admin_scoreboard)
        app.register_blueprint(admin_keys)
        app.register_blueprint(admin_containers)
        app.register_blueprint(admin_pages)

        from CTFd.plugins import init_plugins

        init_plugins(app)

        return app
Example #38
0
def test_user_can_access_files():
    app = create_ctfd()
    with app.app_context():
        from CTFd.utils.uploads import rmdir

        chal = gen_challenge(app.db)
        chal_id = chal.id
        path = app.config.get("UPLOAD_FOLDER")

        location = os.path.join(path, "test_file_path", "test.txt")
        directory = os.path.dirname(location)
        model_path = os.path.join("test_file_path", "test.txt")

        try:
            os.makedirs(directory)
            with open(location, "wb") as obj:
                obj.write("testing file load".encode())
            gen_file(app.db, location=model_path, challenge_id=chal_id)
            url = url_for("views.files", path=model_path)

            # Unauthed user should be able to see challenges if challenges are public
            set_config("challenge_visibility", "public")
            with app.test_client() as client:
                r = client.get(url)

                assert r.status_code == 200
                assert r.get_data(as_text=True) == "testing file load"

            # Unauthed user should not be able to see challenges if challenges are private
            set_config("challenge_visibility", "private")
            with app.test_client() as client:
                r = client.get(url)

                assert r.status_code == 403
                assert r.get_data(as_text=True) != "testing file load"

            # Authed user should be able to see files if challenges are private
            register_user(app)
            client = login_as_user(app)
            r = client.get(url)
            assert r.status_code == 200
            assert r.get_data(as_text=True) == "testing file load"

            with freeze_time("2017-10-5"):
                # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
                set_config("start", "1507262400")
                for v in ("public", "private"):
                    set_config("challenge_visibility", v)

                    # Unauthed users shouldn't be able to see files if the CTF hasn't started
                    client = app.test_client()
                    r = client.get(url)
                    assert r.status_code == 403
                    assert r.get_data(as_text=True) != "testing file load"

                    # Authed users shouldn't be able to see files if the CTF hasn't started
                    client = login_as_user(app)
                    r = client.get(url)
                    assert r.status_code == 403
                    assert r.get_data(as_text=True) != "testing file load"

                    # Admins should be able to see files if the CTF hasn't started
                    admin = login_as_user(app, "admin")
                    r = admin.get(url)
                    assert r.status_code == 200
                    assert r.get_data(as_text=True) == "testing file load"

            with freeze_time("2017-10-7"):
                # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
                set_config("end", "1507262400")
                for v in ("public", "private"):
                    set_config("challenge_visibility", v)

                    # Unauthed users shouldn't be able to see files if the CTF has ended
                    client = app.test_client()
                    r = client.get(url)
                    assert r.status_code == 403
                    assert r.get_data(as_text=True) != "testing file load"

                    # Authed users shouldn't be able to see files if the CTF has ended
                    client = login_as_user(app)
                    r = client.get(url)
                    assert r.status_code == 403
                    assert r.get_data(as_text=True) != "testing file load"

                    # Admins should be able to see files if the CTF has ended
                    admin = login_as_user(app, "admin")
                    r = admin.get(url)
                    assert r.status_code == 200
                    assert r.get_data(as_text=True) == "testing file load"
        finally:
            rmdir(directory)
    destroy_ctfd(app)
Example #39
0
def test_user_can_access_files_with_auth_token():
    app = create_ctfd()
    with app.app_context():
        from CTFd.utils.uploads import rmdir

        chal = gen_challenge(app.db)
        chal_id = chal.id
        path = app.config.get("UPLOAD_FOLDER")

        md5hash = hexencode(os.urandom(16))

        location = os.path.join(path, md5hash, "test.txt")
        directory = os.path.dirname(location)
        model_path = os.path.join(md5hash, "test.txt")

        try:
            os.makedirs(directory)
            with open(location, "wb") as obj:
                obj.write("testing file load".encode())
            gen_file(app.db, location=model_path, challenge_id=chal_id)
            url = url_for("views.files", path=model_path)

            register_user(app)
            with login_as_user(app) as client:
                req = client.get("/api/v1/challenges/1")
                data = req.get_json()
                file_url = data["data"]["files"][0]

            with app.test_client() as client:
                r = client.get(url)
                assert r.status_code == 403
                assert r.get_data(as_text=True) != "testing file load"

                r = client.get(
                    url_for(
                        "views.files",
                        path=model_path,
                        token="random_token_that_shouldnt_work",
                    ))
                assert r.status_code == 403
                assert r.get_data(as_text=True) != "testing file load"

                r = client.get(file_url)
                assert r.status_code == 200
                assert r.get_data(as_text=True) == "testing file load"

                # Unauthed users shouldn't be able to see files if the CTF is admins only
                set_config("challenge_visibility", "admins")
                r = client.get(file_url)
                assert r.status_code == 403
                assert r.get_data(as_text=True) != "testing file load"
                set_config("challenge_visibility", "private")

                with freeze_time("2017-10-5"):
                    # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
                    set_config("start", "1507262400")

                    # Unauthed users shouldn't be able to see files if the CTF hasn't started
                    r = client.get(file_url)
                    assert r.status_code == 403
                    assert r.get_data(as_text=True) != "testing file load"

                with freeze_time("2017-10-5"):
                    # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
                    set_config("start", "1507262400")
                    for v in ("public", "private"):
                        set_config("challenge_visibility", v)

                        # Unauthed users shouldn't be able to see files if the CTF hasn't started
                        client = app.test_client()
                        r = client.get(file_url)
                        assert r.status_code == 403
                        assert r.get_data(as_text=True) != "testing file load"

                        # Authed users shouldn't be able to see files if the CTF hasn't started
                        client = login_as_user(app)
                        r = client.get(file_url)
                        assert r.status_code == 403
                        assert r.get_data(as_text=True) != "testing file load"

                        # Admins should be able to see files if the CTF hasn't started
                        admin = login_as_user(app, "admin")
                        r = admin.get(file_url)
                        assert r.status_code == 200
                        assert r.get_data(as_text=True) == "testing file load"

                with freeze_time("2017-10-7"):
                    # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST
                    set_config("end", "1507262400")
                    for v in ("public", "private"):
                        set_config("challenge_visibility", v)

                        # Unauthed users shouldn't be able to see files if the CTF has ended
                        client = app.test_client()
                        r = client.get(file_url)
                        assert r.status_code == 403
                        assert r.get_data(as_text=True) != "testing file load"

                        # Authed users shouldn't be able to see files if the CTF has ended
                        client = login_as_user(app)
                        r = client.get(file_url)
                        assert r.status_code == 403
                        assert r.get_data(as_text=True) != "testing file load"

                        # Admins should be able to see files if the CTF has ended
                        admin = login_as_user(app, "admin")
                        r = admin.get(file_url)
                        assert r.status_code == 200
                        assert r.get_data(as_text=True) == "testing file load"
        finally:
            rmdir(directory)
    destroy_ctfd(app)
Example #40
0
    def admin_config():
        if request.method == "POST":
            try:
                start = int(request.form['start'])
                end = int(request.form['end'])
            except (ValueError, TypeError):
                start = None
                end = None

            try:
                view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
                prevent_registration = bool(request.form.get('prevent_registration', None))
            except (ValueError, TypeError):
                view_challenges_unregistered = None
                prevent_registration = None

            ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))
            mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))
            do_api_key = set_config("do_api_key", request.form.get('do_api_key', None))

            db_start = Config.query.filter_by(key='start').first()
            db_start.value = start

            db_end = Config.query.filter_by(key='end').first()
            db_end.value = end

            db_view_challenges_unregistered = Config.query.filter_by(key='view_challenges_unregistered').first()
            db_view_challenges_unregistered.value = view_challenges_unregistered

            db_prevent_registration = Config.query.filter_by(key='prevent_registration').first()
            db_prevent_registration.value = prevent_registration

            db.session.add(db_start)
            db.session.add(db_end)
            db.session.add(db_view_challenges_unregistered)
            db.session.add(db_prevent_registration)

            db.session.commit()
            return redirect('/admin/config')

        ctf_name = get_config('ctf_name')
        if not ctf_name:
            set_config('do_api_key', None)

        mg_api_key = get_config('do_api_key')
        if not mg_api_key:
            set_config('do_api_key', None)

        do_api_key = get_config('do_api_key')
        if not do_api_key:
            set_config('do_api_key', None)

        start = get_config('start')
        if not start:
            set_config('start', None)

        end = get_config('end')
        if not end:
            set_config('end', None)

        view_challenges_unregistered = get_config('view_challenges_unregistered') == '1'
        if not view_challenges_unregistered:
            set_config('view_challenges_unregistered', None)

        prevent_registration = get_config('prevent_registration') == '1'
        if not prevent_registration:
            set_config('prevent_registration', None)

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

        return render_template('admin/config.html', ctf_name=ctf_name, start=start, end=end,
                               view_challenges_unregistered=view_challenges_unregistered,
                               prevent_registration=prevent_registration, do_api_key=do_api_key, mg_api_key=mg_api_key)
def test_challenges_under_view_after_ctf():
    app = create_ctfd()
    with app.app_context(), freeze_time("2017-10-7"):
        set_config('start', '1507089600'
                   )  # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
        set_config(
            'end',
            '1507262400')  # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST

        register_user(app)
        client = login_as_user(app)

        gen_challenge(app.db)
        gen_flag(app.db, challenge_id=1, content='flag')

        r = client.get('/challenges')
        assert r.status_code == 403

        r = client.get('/api/v1/challenges')
        assert r.status_code == 403
        assert r.get_json().get('data') is None

        r = client.get('/api/v1/challenges/1')
        assert r.status_code == 403
        assert r.get_json().get('data') is None

        data = {"submission": 'flag', "challenge_id": 1}
        r = client.post('/api/v1/challenges/attempt', json=data)
        assert r.status_code == 403
        assert r.get_json().get('data') is None
        assert Solves.query.count() == 0

        data = {"submission": 'notflag', "challenge_id": 1}
        r = client.post('/api/v1/challenges/attempt', json=data)
        assert r.status_code == 403
        assert r.get_json().get('data') is None
        assert Fails.query.count() == 0

        set_config('view_after_ctf', True)

        r = client.get('/challenges')
        assert r.status_code == 200

        r = client.get('/api/v1/challenges')
        assert r.status_code == 200
        assert r.get_json()['data'][0]['id'] == 1

        r = client.get('/api/v1/challenges/1')
        assert r.status_code == 200
        assert r.get_json()['data']['id'] == 1

        data = {"submission": 'flag', "challenge_id": 1}
        r = client.post('/api/v1/challenges/attempt', json=data)
        assert r.status_code == 200
        assert r.get_json()['data']['status'] == "correct"
        assert Solves.query.count() == 0

        data = {"submission": 'notflag', "challenge_id": 1}
        r = client.post('/api/v1/challenges/attempt', json=data)
        assert r.status_code == 200
        assert r.get_json()['data']['status'] == "incorrect"
        assert Fails.query.count() == 0

    destroy_ctfd(app)
Example #42
0
def admin_config():
    if request.method == "POST":
        start = None
        end = None
        if request.form.get('start'):
            start = int(request.form['start'])
        if request.form.get('end'):
            end = int(request.form['end'])
            if end < unix_time(datetime.datetime.now()):
                end = None

        try:
            view_challenges_unregistered = bool(request.form.get('view_challenges_unregistered', None))
            view_scoreboard_if_authed = bool(request.form.get('view_scoreboard_if_authed', None))
            prevent_registration = bool(request.form.get('prevent_registration', None))
            prevent_name_change = bool(request.form.get('prevent_name_change', None))
            view_after_ctf = bool(request.form.get('view_after_ctf', None))
            verify_emails = bool(request.form.get('verify_emails', None))
            mail_tls = bool(request.form.get('mail_tls', None))
            mail_ssl = bool(request.form.get('mail_ssl', None))
        except (ValueError, TypeError):
            view_challenges_unregistered = None
            view_scoreboard_if_authed = None
            prevent_registration = None
            prevent_name_change = None
            view_after_ctf = None
            verify_emails = None
            mail_tls = None
            mail_ssl = None
        finally:
            view_challenges_unregistered = set_config('view_challenges_unregistered', view_challenges_unregistered)
            view_scoreboard_if_authed = set_config('view_scoreboard_if_authed', view_scoreboard_if_authed)
            prevent_registration = set_config('prevent_registration', prevent_registration)
            prevent_name_change = set_config('prevent_name_change', prevent_name_change)
            view_after_ctf = set_config('view_after_ctf', view_after_ctf)
            verify_emails = set_config('verify_emails', verify_emails)
            mail_tls = set_config('mail_tls', mail_tls)
            mail_ssl = set_config('mail_ssl', mail_ssl)

        mail_server = set_config("mail_server", request.form.get('mail_server', None))
        mail_port = set_config("mail_port", request.form.get('mail_port', None))

        mail_username = set_config("mail_username", request.form.get('mail_username', None))
        mail_password = set_config("mail_password", request.form.get('mail_password', None))

        ctf_name = set_config("ctf_name", request.form.get('ctf_name', None))

        mg_base_url = set_config("mg_base_url", request.form.get('mg_base_url', None))
        mg_api_key = set_config("mg_api_key", request.form.get('mg_api_key', None))

        max_tries = set_config("max_tries", request.form.get('max_tries', None))

        db_start = Config.query.filter_by(key='start').first()
        db_start.value = start

        db_end = Config.query.filter_by(key='end').first()
        db_end.value = end

        db.session.add(db_start)
        db.session.add(db_end)

        db.session.commit()
        return redirect(url_for('admin.admin_config'))

    ctf_name = get_config('ctf_name')
    max_tries = get_config('max_tries')

    mail_server = get_config('mail_server')
    mail_port = get_config('mail_port')
    mail_username = get_config('mail_username')
    mail_password = get_config('mail_password')

    mg_api_key = get_config('mg_api_key')
    mg_base_url = get_config('mg_base_url')
    if not max_tries:
        set_config('max_tries', 0)
        max_tries = 0

    view_after_ctf = get_config('view_after_ctf')
    start = get_config('start')
    end = get_config('end')

    mail_tls = get_config('mail_tls')
    mail_ssl = get_config('mail_ssl')

    view_challenges_unregistered = get_config('view_challenges_unregistered')
    view_scoreboard_if_authed = get_config('view_scoreboard_if_authed')
    prevent_registration = get_config('prevent_registration')
    prevent_name_change = get_config('prevent_name_change')
    verify_emails = get_config('verify_emails')

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

    months = [
        'January', 'February', 'March', 'April',
        'May', 'June', 'July', 'August',
        'September', 'October', 'November', 'December'
    ]

    curr_year = datetime.date.today().year
    start_days = 0
    end_days = 0

    if start:
        start = datetime.datetime.fromtimestamp(float(start))
        start_days = calendar.monthrange(start.year, start.month)[1]
    if end:
        end = datetime.datetime.fromtimestamp(float(end))
        end_days = calendar.monthrange(end.year, end.month)[1]

    return render_template('admin/config.html',
                           ctf_name=ctf_name,
                           start=start,
                           end=end,
                           max_tries=max_tries,
                           mail_server=mail_server,
                           mail_port=mail_port,
                           mail_username=mail_username,
                           mail_password=mail_password,
                           mail_tls=mail_tls,
                           mail_ssl=mail_ssl,
                           view_challenges_unregistered=view_challenges_unregistered,
                           view_scoreboard_if_authed=view_scoreboard_if_authed,
                           prevent_registration=prevent_registration,
                           mg_base_url=mg_base_url,
                           mg_api_key=mg_api_key,
                           prevent_name_change=prevent_name_change,
                           verify_emails=verify_emails,
                           view_after_ctf=view_after_ctf,
                           months=months,
                           curr_year=curr_year,
                           start_days=start_days,
                           end_days=end_days)
Example #43
0
def setup():
    # with app.app_context():
        # admin = Teams.query.filter_by(admin=True).first()

    if not utils.is_setup():
        if not session.get('nonce'):
            session['nonce'] = utils.sha512(os.urandom(10))
        if request.method == 'POST':
            ctf_name = request.form['ctf_name']
            ctf_name = utils.set_config('ctf_name', ctf_name)

            # CSS
            css = utils.set_config('start', '')

            # Admin user
            name = request.form['name']
            email = request.form['email']
            password = request.form['password']
            admin = Teams(name, email, password)
            admin.admin = True
            admin.banned = True

            # Index page
            page = Pages('index', """<div class="container main-container">
    <img class="logo" src="themes/original/static/img/logo.png" />
    <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="fa fa-twitter fa-2x" aria-hidden="true"></i></a>&nbsp;
        <a href="https://facebook.com/ctfdio"><i class="fa fa-facebook-official fa-2x" aria-hidden="true"></i></a>&nbsp;
        <a href="https://github.com/ctfd"><i class="fa 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>""".format(request.script_root))

            # max attempts per challenge
            max_tries = utils.set_config('max_tries', 0)

            # Start time
            start = utils.set_config('start', None)
            end = utils.set_config('end', None)
            freeze = utils.set_config('freeze', None)

            # Challenges cannot be viewed by unregistered users
            view_challenges_unregistered = utils.set_config('view_challenges_unregistered', None)

            # Allow/Disallow registration
            prevent_registration = utils.set_config('prevent_registration', None)

            # Verify emails
            verify_emails = utils.set_config('verify_emails', None)

            mail_server = utils.set_config('mail_server', None)
            mail_port = utils.set_config('mail_port', None)
            mail_tls = utils.set_config('mail_tls', None)
            mail_ssl = utils.set_config('mail_ssl', None)
            mail_username = utils.set_config('mail_username', None)
            mail_password = utils.set_config('mail_password', None)

            setup = utils.set_config('setup', True)

            db.session.add(page)
            db.session.add(admin)
            db.session.commit()

            session['username'] = admin.name
            session['id'] = admin.id
            session['admin'] = admin.admin
            session['nonce'] = utils.sha512(os.urandom(10))

            db.session.close()
            app.setup = False
            with app.app_context():
                cache.clear()

            return redirect(url_for('views.static_html'))
        return render_template('setup.html', nonce=session.get('nonce'))
    return redirect(url_for('views.static_html'))