Exemple #1
0
def test_challenge_kpm_limit():
    """Test that users are properly ratelimited when submitting flags"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        chal_id = chal.id

        gen_flag(app.db, challenge_id=chal.id, content=u"flag")
        for x in range(11):
            with client.session_transaction():
                data = {"submission": "notflag", "challenge_id": chal_id}
            r = client.post("/api/v1/challenges/attempt", json=data)

        wrong_keys = Fails.query.count()
        assert wrong_keys == 11

        data = {"submission": "flag", "challenge_id": chal_id}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 429

        wrong_keys = Fails.query.count()
        assert wrong_keys == 12

        resp = r.get_json()["data"]
        assert resp.get("status") == "ratelimited"
        assert resp.get(
            "message") == "You're submitting flags too fast. Slow down."

        solves = Solves.query.count()
        assert solves == 0
    destroy_ctfd(app)
Exemple #2
0
def test_dynamic_challenge_doesnt_lose_value_on_update():
    """Dynamic challenge updates without changing any values or solves shouldn't change the current value. See #1043"""
    app = create_ctfd(enable_plugins=True)
    with app.app_context():
        challenge_data = {
            "name": "name",
            "category": "category",
            "description": "description",
            "value": 10000,
            "decay": 4,
            "minimum": 10,
            "state": "visible",
            "type": "dynamic",
        }
        req = FakeRequest(form=challenge_data)
        challenge = DynamicValueChallenge.create(req)
        challenge_id = challenge.id
        gen_flag(app.db, challenge_id=challenge.id, content="flag")
        register_user(app)
        with login_as_user(app) as client:
            data = {"submission": "flag", "challenge_id": challenge_id}
            r = client.post("/api/v1/challenges/attempt", json=data)
            assert r.status_code == 200
            assert r.get_json()["data"]["status"] == "correct"
        chal = Challenges.query.filter_by(id=challenge_id).first()
        prev_chal_value = chal.value
        chal = DynamicValueChallenge.update(chal, req)
        assert prev_chal_value == chal.value
    destroy_ctfd(app)
Exemple #3
0
def test_challenges_with_max_attempts():
    """Test that users are locked out of a challenge after they reach max_attempts"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        chal = Challenges.query.filter_by(id=chal.id).first()
        chal_id = chal.id
        chal.max_attempts = 3
        app.db.session.commit()

        gen_flag(app.db, challenge_id=chal.id, content=u"flag")
        for x in range(3):
            data = {"submission": "notflag", "challenge_id": chal_id}
            r = client.post("/api/v1/challenges/attempt", json=data)

        wrong_keys = Fails.query.count()
        assert wrong_keys == 3

        data = {"submission": "flag", "challenge_id": chal_id}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 403

        resp = r.get_json()["data"]
        assert resp.get("status") == "incorrect"
        assert resp.get("message") == "You have 0 tries remaining"

        solves = Solves.query.count()
        assert solves == 0
    destroy_ctfd(app)
Exemple #4
0
def test_scoring_logic():
    """Test that scoring logic is correct"""
    app = create_ctfd()
    with app.app_context():
        admin = login_as_user(app, name="admin", password="******")

        register_user(app,
                      name="user1",
                      email="*****@*****.**",
                      password="******")
        client1 = login_as_user(app, name="user1", password="******")
        register_user(app,
                      name="user2",
                      email="*****@*****.**",
                      password="******")
        client2 = login_as_user(app, name="user2", password="******")

        chal1 = gen_challenge(app.db)
        gen_flag(app.db, challenge_id=chal1.id, content="flag")
        chal1_id = chal1.id

        chal2 = gen_challenge(app.db)
        gen_flag(app.db, challenge_id=chal2.id, content="flag")
        chal2_id = chal2.id

        # user1 solves chal1
        with freeze_time("2017-10-3 03:21:34"):
            with client1.session_transaction():
                data = {"submission": "flag", "challenge_id": chal1_id}
                client1.post("/api/v1/challenges/attempt", json=data)

        # user1 is now on top
        scores = get_scores(admin)
        assert scores[0]["name"] == "user1"

        # user2 solves chal1 and chal2
        with freeze_time("2017-10-4 03:30:34"):
            with client2.session_transaction():
                # solve chal1
                data = {"submission": "flag", "challenge_id": chal1_id}
                client2.post("/api/v1/challenges/attempt", json=data)
                # solve chal2
                data = {"submission": "flag", "challenge_id": chal2_id}
                client2.post("/api/v1/challenges/attempt", json=data)

        # user2 is now on top
        scores = get_scores(admin)
        assert scores[0]["name"] == "user2"

        # user1 solves chal2
        with freeze_time("2017-10-5 03:50:34"):
            with client1.session_transaction():
                data = {"submission": "flag", "challenge_id": chal2_id}
                client1.post("/api/v1/challenges/attempt", json=data)

        # user2 should still be on top because they solved chal2 first
        scores = get_scores(admin)
        assert scores[0]["name"] == "user2"
    destroy_ctfd(app)
Exemple #5
0
def test_dynamic_challenge_loses_value_properly():
    app = create_ctfd(enable_plugins=True)
    with app.app_context():
        register_user(app)
        client = login_as_user(app, name="admin", password="******")

        challenge_data = {
            "name": "name",
            "category": "category",
            "description": "description",
            "value": 500,
            "initial": 500,
            "slope": 1.5,
            "decrease": 2.079,
            "state": "visible",
            "type": "dynamic",
        }

        r = client.post("/api/v1/challenges", json=challenge_data)
        assert r.get_json().get("data")["id"] == 1

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

        for i, team_id in enumerate(range(2, 26)):
            name = "user{}".format(team_id)
            email = "user{}@ctfd.io".format(team_id)
            # We need to bypass rate-limiting so gen_user instead of register_user
            user = gen_user(app.db, name=name, email=email)
            user_id = user.id

            with app.test_client() as client:
                # We need to bypass rate-limiting so creating a fake user instead of logging in
                with client.session_transaction() as sess:
                    sess["id"] = user_id
                    sess["nonce"] = "fake-nonce"
                    sess["hash"] = hmac(user.password)

                data = {"submission": "flag", "challenge_id": 1}

                r = client.post("/api/v1/challenges/attempt", json=data)
                resp = r.get_json()["data"]
                assert resp["status"] == "correct"

                chal = DynamicChallenge.query.filter_by(id=1).first()
                assert chal.initial >= chal.value

                if i == 0:
                    # The first solver should get the maximum points
                    assert chal.initial == chal.value
                elif i == 10:
                    # The value should be around half of the maximum by 10 solvers
                    assert chal.value > 260
                    assert chal.value < 270
                elif i == 250:
                    assert chal.value > 95
                    assert chal.value < 105

    destroy_ctfd(app)
Exemple #6
0
def test_dynamic_challenge_value_isnt_affected_by_hidden_users():
    app = create_ctfd(enable_plugins=True)
    with app.app_context():
        register_user(app)
        client = login_as_user(app, name="admin", password="******")

        challenge_data = {
            "name": "name",
            "category": "category",
            "description": "description",
            "value": 100,
            "decay": 20,
            "minimum": 1,
            "state": "visible",
            "type": "dynamic",
        }

        r = client.post("/api/v1/challenges", json=challenge_data)
        assert r.get_json().get("data")["id"] == 1

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

        # Make a solve as a regular user. This should not affect the value.
        data = {"submission": "flag", "challenge_id": 1}

        r = client.post("/api/v1/challenges/attempt", json=data)
        resp = r.get_json()["data"]
        assert resp["status"] == "correct"

        # Make solves as hidden users. Also should not affect value
        for i, team_id in enumerate(range(2, 26)):
            name = "user{}".format(team_id)
            email = "user{}@ctfd.io".format(team_id)
            # We need to bypass rate-limiting so gen_user instead of register_user
            user = gen_user(app.db, name=name, email=email)
            user.hidden = True
            app.db.session.commit()

            with app.test_client() as client:
                # We need to bypass rate-limiting so creating a fake user instead of logging in
                with client.session_transaction() as sess:
                    sess["id"] = team_id
                    sess["name"] = name
                    sess["type"] = "user"
                    sess["email"] = email
                    sess["nonce"] = "fake-nonce"

                data = {"submission": "flag", "challenge_id": 1}

                r = client.post("/api/v1/challenges/attempt", json=data)
                resp = r.get_json()["data"]
                assert resp["status"] == "correct"

                chal = DynamicChallenge.query.filter_by(id=1).first()
                assert chal.value == chal.initial
    destroy_ctfd(app)
Exemple #7
0
def test_api_flag_get_admin():
    """Can a user get /api/v1/flags/<flag_id> if admin"""
    app = create_kmactf()
    with app.app_context():
        gen_challenge(app.db)
        gen_flag(app.db, 1)
        with login_as_user(app, "admin") as client:
            r = client.get("/api/v1/flags/1", json="")
            assert r.status_code == 200
    destroy_kmactf(app)
def test_api_flag_delete_admin():
    """Can a user patch /api/v1/flags/<flag_id> if admin"""
    app = create_ctfd()
    with app.app_context():
        gen_challenge(app.db)
        gen_flag(app.db, 1)
        with login_as_user(app, "admin") as client:
            r = client.delete("/api/v1/flags/1", json="")
            assert r.status_code == 200
            assert r.get_json().get("data") is None
    destroy_ctfd(app)
Exemple #9
0
def test_dynamic_challenge_loses_value_properly():
    app = create_ctfd(enable_plugins=True)
    with app.app_context():
        register_user(app)
        client = login_as_user(app, name="admin", password="******")

        challenge_data = {
            "name": "name",
            "category": "category",
            "description": "description",
            "value": 100,
            "decay": 20,
            "minimum": 1,
            "state": "visible",
            "type": "dynamic"
        }

        r = client.post('/api/v1/challenges', json=challenge_data)
        assert r.get_json().get('data')['id'] == 1

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

        for i, team_id in enumerate(range(2, 26)):
            name = "user{}".format(team_id)
            email = "user{}@ctfd.io".format(team_id)
            # We need to bypass rate-limiting so gen_user instead of register_user
            gen_user(app.db, name=name, email=email)

            with app.test_client() as client:
                # We need to bypass rate-limiting so creating a fake user instead of logging in
                with client.session_transaction() as sess:
                    sess['id'] = team_id
                    sess['name'] = name
                    sess['type'] = 'user'
                    sess['email'] = email
                    sess['nonce'] = 'fake-nonce'

                data = {
                    "submission": 'flag',
                    "challenge_id": 1
                }

                r = client.post('/api/v1/challenges/attempt', json=data)
                resp = r.get_json()['data']
                assert resp['status'] == 'correct'

                chal = DynamicChallenge.query.filter_by(id=1).first()
                if i >= 20:
                    assert chal.value == chal.minimum
                else:
                    assert chal.initial >= chal.value
                    assert chal.value > chal.minimum
    destroy_ctfd(app)
def test_challenge_with_requirements_is_unsolveable():
    """Test that a challenge with a requirement is unsolveable without first solving the requirement"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal1 = gen_challenge(app.db)
        gen_flag(app.db, challenge_id=chal1.id, content="flag")

        requirements = {"prerequisites": [1]}
        chal2 = gen_challenge(app.db, requirements=requirements)
        app.db.session.commit()

        gen_flag(app.db, challenge_id=chal2.id, content="flag")

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

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

        # Attempt to solve hidden Challenge 2
        data = {"submission": "flag", "challenge_id": 2}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 403
        assert r.get_json().get("data") is None

        # Solve Challenge 1
        data = {"submission": "flag", "challenge_id": 1}
        r = client.post("/api/v1/challenges/attempt", json=data)
        resp = r.get_json()["data"]
        assert resp["status"] == "correct"

        # Challenge 2 should now be visible
        r = client.get("/api/v1/challenges")
        challenges = r.get_json()["data"]
        assert len(challenges) == 2

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

        # Attempt to solve the now-visible Challenge 2
        data = {"submission": "flag", "challenge_id": 2}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        assert resp["status"] == "correct"

    destroy_ctfd(app)
Exemple #11
0
def test_import_ctf():
    """Test that CTFd can import a CTF"""
    app = create_ctfd()
    if not app.config.get("SQLALCHEMY_DATABASE_URI").startswith("sqlite"):
        with app.app_context():
            base_user = "******"
            for x in range(10):
                user = base_user + str(x)
                user_email = user + "@ctfd.io"
                gen_user(app.db, name=user, email=user_email)

            base_team = "team"
            for x in range(5):
                team = base_team + str(x)
                team_email = team + "@ctfd.io"
                gen_team(app.db, name=team, email=team_email)

            for x in range(9):
                chal = gen_challenge(app.db, name="chal_name{}".format(x))
                gen_flag(app.db, challenge_id=chal.id, content="flag")

            chal = gen_challenge(app.db,
                                 name="chal_name10",
                                 requirements={"prerequisites": [1]})
            gen_flag(app.db, challenge_id=chal.id, content="flag")

            app.db.session.commit()

            backup = export_ctf()

            with open("export.test_import_ctf.zip", "wb") as f:
                f.write(backup.read())
    destroy_ctfd(app)

    app = create_ctfd()
    # TODO: These databases should work but they don't...
    if not app.config.get("SQLALCHEMY_DATABASE_URI").startswith("sqlite"):
        with app.app_context():
            import_ctf("export.test_import_ctf.zip")

            if not app.config.get("SQLALCHEMY_DATABASE_URI").startswith(
                    "postgres"):
                # TODO: Dig deeper into why Postgres fails here
                assert Users.query.count() == 31
                assert Teams.query.count() == 5
                assert Challenges.query.count() == 10
                assert Flags.query.count() == 10

                chal = Challenges.query.filter_by(name="chal_name10").first()
                assert chal.requirements == {"prerequisites": [1]}
    destroy_ctfd(app)
Exemple #12
0
def test_api_flag_patch_admin():
    """Can a user patch /api/v1/flags/<flag_id> if admin"""
    app = create_kmactf()
    with app.app_context():
        gen_challenge(app.db)
        gen_flag(app.db, 1)
        with login_as_user(app, "admin") as client:
            r = client.patch(
                "/api/v1/flags/1",
                json={"content": "flag_edit", "data": "", "type": "static", "id": "1"},
            )
            assert r.status_code == 200
            assert r.get_json()["data"]["content"] == "flag_edit"
    destroy_kmactf(app)
Exemple #13
0
def test_dynamic_challenge_loses_value_properly():
    app = create_ctfd(enable_plugins=True)
    with app.app_context():
        register_user(app)
        client = login_as_user(app, name="admin", password="******")

        challenge_data = {
            "name": "name",
            "category": "category",
            "description": "description",
            "value": 100,
            "decay": 20,
            "minimum": 1,
            "state": "visible",
            "type": "dynamic",
        }

        r = client.post("/api/v1/challenges", json=challenge_data)
        assert r.get_json().get("data")["id"] == 1

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

        for i, team_id in enumerate(range(2, 26)):
            name = "user{}".format(team_id)
            email = "user{}@examplectf.com".format(team_id)
            # We need to bypass rate-limiting so gen_user instead of register_user
            user = gen_user(app.db, name=name, email=email)
            user_id = user.id

            with app.test_client() as client:
                # We need to bypass rate-limiting so creating a fake user instead of logging in
                with client.session_transaction() as sess:
                    sess["id"] = user_id
                    sess["nonce"] = "fake-nonce"
                    sess["hash"] = hmac(user.password)

                data = {"submission": "flag", "challenge_id": 1}

                r = client.post("/api/v1/challenges/attempt", json=data)
                resp = r.get_json()["data"]
                assert resp["status"] == "correct"

                chal = DynamicChallenge.query.filter_by(id=1).first()
                if i >= 20:
                    assert chal.value == chal.minimum
                else:
                    assert chal.initial >= chal.value
                    assert chal.value > chal.minimum
    destroy_ctfd(app)
def test_submitting_incorrect_flag():
    """Test that incorrect flags are incorrect"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        gen_flag(app.db, challenge_id=chal.id, content="flag")
        data = {"submission": "notflag", "challenge_id": chal.id}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        resp = r.get_json()["data"]
        assert resp.get("status") == "incorrect"
        assert resp.get("message") == "Incorrect"
    destroy_ctfd(app)
Exemple #15
0
def test_api_flag_patch_admin():
    """Can a user patch /api/v1/flags/<flag_id> if admin"""
    app = create_ctfd()
    with app.app_context():
        gen_challenge(app.db)
        gen_flag(app.db, 1)
        with login_as_user(app, 'admin') as client:
            r = client.patch('/api/v1/flags/1', json={
                "content": "flag_edit",
                "data": "",
                "type": "static",
                "id": "1"})
            assert r.status_code == 200
            assert r.get_json()['data']['content'] == "flag_edit"
    destroy_ctfd(app)
Exemple #16
0
def test_reset_team_mode():
    app = create_ctfd(user_mode="teams")
    with app.app_context():
        base_user = '******'
        base_team = 'team'

        for x in range(10):
            chal = gen_challenge(app.db, name='chal_name{}'.format(x))
            gen_flag(app.db, challenge_id=chal.id, content='flag')

        for x in range(10):
            user = base_user + str(x)
            user_email = user + "@ctfd.io"
            user_obj = gen_user(app.db, name=user, email=user_email)
            team_obj = gen_team(app.db,
                                name=base_team + str(x),
                                email=base_team + str(x) + '@ctfd.io')
            team_obj.members.append(user_obj)
            team_obj.captain_id = user_obj.id
            app.db.session.commit()
            gen_award(app.db, user_id=user_obj.id)
            gen_solve(app.db,
                      user_id=user_obj.id,
                      challenge_id=random.randint(1, 10))
            gen_fail(app.db,
                     user_id=user_obj.id,
                     challenge_id=random.randint(1, 10))
            gen_tracking(app.db, user_id=user_obj.id)

        assert Teams.query.count() == 10
        assert Users.query.count(
        ) == 51  # 10 random users, 40 users (10 teams * 4), 1 admin user
        assert Challenges.query.count() == 10

        register_user(app)
        client = login_as_user(app, name="admin", password="******")

        with client.session_transaction() as sess:
            data = {"nonce": sess.get('nonce')}
            client.post('/admin/reset', data=data)

        assert Teams.query.count() == 0
        assert Users.query.count() == 0
        assert Challenges.query.count() == 10
        assert Solves.query.count() == 0
        assert Fails.query.count() == 0
        assert Tracking.query.count() == 0
    destroy_ctfd(app)
def test_submitting_unicode_flag():
    """Test that users can submit a unicode flag"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        gen_flag(app.db, challenge_id=chal.id, content=u"ф╜ахе╜")
        with client.session_transaction():
            data = {"submission": "ф╜ахе╜", "challenge_id": chal.id}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        resp = r.get_json()["data"]
        assert resp.get("status") == "correct"
        assert resp.get("message") == "Correct"
    destroy_ctfd(app)
def test_challenge_board_under_view_after_ctf():
    """Test that the challenge board does not show an error under view_after_ctf"""
    app = create_ctfd()
    with app.app_context():
        set_config('view_after_ctf', True)
        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')

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

        # CTF is ongoing. Normal operation.
        with freeze_time("2017-10-5"):
            r = client.get('/challenges')
            assert r.status_code == 200
            assert "has ended" not in r.get_data(as_text=True)

            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() == 1

        # CTF is now over. There should be a message and challenges should show submission status but not store solves
        with freeze_time("2017-10-7"):
            r = client.get('/challenges')
            assert r.status_code == 200
            assert "has ended" in r.get_data(as_text=True)

            data = {
                "submission": 'flag',
                "challenge_id": 2
            }
            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() == 1
    destroy_ctfd(app)
Exemple #19
0
def test_api_challenge_attempt_post_admin():
    """Can an admin user post /api/v1/challenges/attempt"""
    app = create_ctfd()
    with app.app_context():
        gen_challenge(app.db)
        gen_flag(app.db, 1)
        with login_as_user(app, 'admin') as client:
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": 1, "submission": "wrong_flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'incorrect'
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": 1, "submission": "flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'correct'
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": 1, "submission": "flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'already_solved'
    destroy_ctfd(app)
Exemple #20
0
def test_api_challenge_attempt_post_private():
    """Can an private user post /api/v1/challenges/attempt"""
    app = create_ctfd()
    with app.app_context():
        challenge_id = gen_challenge(app.db).id
        gen_flag(app.db, challenge_id)
        register_user(app)
        with login_as_user(app) as client:
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "wrong_flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'incorrect'
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'correct'
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'already_solved'
        challenge_id = gen_challenge(app.db).id
        gen_flag(app.db, challenge_id)
        with login_as_user(app) as client:
            for i in range(10):
                gen_fail(app.db, user_id=2, challenge_id=challenge_id)
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "flag"})
            assert r.status_code == 429
            assert r.get_json()['data']['status'] == 'ratelimited'
    destroy_ctfd(app)

    app = create_ctfd(user_mode="teams")
    with app.app_context():
        challenge_id = gen_challenge(app.db).id
        gen_flag(app.db, challenge_id)
        register_user(app)
        team_id = gen_team(app.db).id
        user = Users.query.filter_by(id=2).first()
        user.team_id = team_id
        app.db.session.commit()
        with login_as_user(app) as client:
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "wrong_flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'incorrect'
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'correct'
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "flag"})
            assert r.status_code == 200
            assert r.get_json()['data']['status'] == 'already_solved'
        challenge_id = gen_challenge(app.db).id
        gen_flag(app.db, challenge_id)
        with login_as_user(app) as client:
            for i in range(10):
                gen_fail(app.db, user_id=2, team_id=team_id, challenge_id=challenge_id)
            r = client.post('/api/v1/challenges/attempt', json={"challenge_id": challenge_id, "submission": "flag"})
            assert r.status_code == 429
            assert r.get_json()['data']['status'] == 'ratelimited'
    destroy_ctfd(app)
def test_submitting_correct_static_case_insensitive_flag():
    """Test that correct static flags are correct if the static flag is marked case_insensitive"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        gen_flag(app.db,
                 challenge_id=chal.id,
                 content="flag",
                 data="case_insensitive")
        data = {"submission": "FLAG", "challenge_id": chal.id}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        resp = r.get_json()["data"]
        assert resp.get("status") == "correct"
        assert resp.get("message") == "Correct"
    destroy_ctfd(app)
Exemple #22
0
def test_scoreboard_team_score():
    """Is a user's submitted flag reflected on the team's score on /scoreboard"""
    app = create_kmactf(user_mode="teams")
    with app.app_context():
        user = gen_user(app.db, name="user")
        team = gen_team(app.db)
        user.team_id = team.id
        team.members.append(user)
        gen_challenge(app.db)
        gen_flag(app.db, 1)
        app.db.session.commit()
        with login_as_user(app) as client:
            flag = {"challenge_id": 1, "submission": "flag"}
            client.post("/api/v1/challenges/attempt", json=flag)
        standings = get_standings()
        assert standings[0][2] == "team_name"
        assert standings[0][3] == 100
    destroy_kmactf(app)
def test_submitting_correct_regex_case_insensitive_flag():
    """Test that correct regex flags are correct if the regex flag is marked case_insensitive"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        gen_flag(app.db, challenge_id=chal.id, type='regex', content='flag', data="case_insensitive")
        data = {
            "submission": 'FLAG',
            "challenge_id": chal.id,
        }
        r = client.post('/api/v1/challenges/attempt', json=data)
        assert r.status_code == 200
        resp = r.get_json()['data']
        assert resp.get('status') == "correct"
        assert resp.get('message') == "Correct"
    destroy_ctfd(app)
Exemple #24
0
def test_import_ctf():
    """Test that CTFd can import a CTF"""
    app = create_ctfd()
    if not app.config.get('SQLALCHEMY_DATABASE_URI').startswith('sqlite'):
        with app.app_context():
            base_user = '******'
            for x in range(10):
                user = base_user + str(x)
                user_email = user + "@ctfd.io"
                gen_user(app.db, name=user, email=user_email)

            for x in range(9):
                chal = gen_challenge(app.db, name='chal_name{}'.format(x))
                gen_flag(app.db, challenge_id=chal.id, content='flag')

            chal = gen_challenge(app.db,
                                 name='chal_name10',
                                 requirements={"prerequisites": [1]})
            gen_flag(app.db, challenge_id=chal.id, content='flag')

            app.db.session.commit()

            backup = export_ctf()

            with open('export.test_import_ctf.zip', 'wb') as f:
                f.write(backup.read())
    destroy_ctfd(app)

    app = create_ctfd()
    # TODO: These databases should work but they don't...
    if not app.config.get('SQLALCHEMY_DATABASE_URI').startswith('sqlite'):
        with app.app_context():
            import_ctf('export.test_import_ctf.zip')

            if not app.config.get('SQLALCHEMY_DATABASE_URI').startswith(
                    'postgres'):
                # TODO: Dig deeper into why Postgres fails here
                assert Users.query.count() == 11
                assert Challenges.query.count() == 10
                assert Flags.query.count() == 10

                chal = Challenges.query.filter_by(name='chal_name10').first()
                assert chal.requirements == {"prerequisites": [1]}
    destroy_ctfd(app)
Exemple #25
0
def test_scoreboard_is_cached():
    """Test that /api/v1/scoreboard is properly cached and cleared"""
    app = create_ctfd()
    with app.app_context():
        # create user1
        register_user(app, name="user1", email="*****@*****.**")

        # create challenge
        chal = gen_challenge(app.db, value=100)
        gen_flag(app.db, challenge_id=chal.id, content="flag")
        chal_id = chal.id

        # create a solve for the challenge for user1. (the id is 2 because of the admin)
        gen_solve(app.db, user_id=2, challenge_id=chal_id)

        with login_as_user(app, "user1") as client:
            # No cached data
            assert app.cache.get("view/api.scoreboard_scoreboard_list") is None
            assert app.cache.get("view/api.scoreboard_scoreboard_detail") is None

            # Load and check cached data
            client.get("/api/v1/scoreboard")
            assert app.cache.get("view/api.scoreboard_scoreboard_list")
            client.get("/api/v1/scoreboard/top/10")
            assert app.cache.get("view/api.scoreboard_scoreboard_detail")

            # Check scoreboard page
            assert (
                app.cache.get(make_template_fragment_key("public_scoreboard_table"))
                is None
            )
            client.get("/scoreboard")
            assert app.cache.get(make_template_fragment_key("public_scoreboard_table"))

            # Empty standings and check that the cached data is gone
            clear_standings()
            assert app.cache.get("view/api.scoreboard_scoreboard_list") is None
            assert app.cache.get("view/api.scoreboard_scoreboard_detail") is None
            assert (
                app.cache.get(make_template_fragment_key("public_scoreboard_table"))
                is None
            )
    destroy_ctfd(app)
Exemple #26
0
def test_api_team_captain_disbanding_only_inactive_teams():
    """Test that only teams that haven't conducted any actions can be disbanded"""
    app = create_ctfd(user_mode="teams")
    with app.app_context():
        user = gen_user(app.db, name="user")
        team = gen_team(app.db)
        team.members.append(user)
        user.team_id = team.id
        team.captain_id = 2
        user2 = gen_user(app.db, name="user2", email="*****@*****.**")
        team.members.append(user2)
        app.db.session.commit()

        gen_challenge(app.db)
        gen_flag(app.db, 1)
        gen_solve(app.db, user_id=3, team_id=1, challenge_id=1)

        with login_as_user(app) as client:
            r = client.delete("/api/v1/teams/me", json="")
            assert r.status_code == 403
            assert r.get_json() == {
                "success": False,
                "errors": {
                    "": [
                        "You cannot disband your team as it has participated in the event. "
                        "Please contact an admin to disband your team or remove a member."
                    ]
                },
            }

        user = gen_user(app.db, name="user3", email="*****@*****.**")
        team = gen_team(app.db, name="team2", email="*****@*****.**")
        print(user.id)
        team.members.append(user)
        user.team_id = team.id
        team.captain_id = user.id
        app.db.session.commit()
        with login_as_user(app, name="user3") as client:
            r = client.delete("/api/v1/teams/me", json="")
            print(r.get_json())
            assert r.status_code == 200
            assert r.get_json() == {"success": True}
    destroy_ctfd(app)
def test_hidden_challenge_is_unsolveable():
    """Test that hidden challenges return 404 and do not insert a solve or wrong key"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db, state="hidden")
        gen_flag(app.db, challenge_id=chal.id, content="flag")

        data = {"submission": "flag", "challenge_id": chal.id}

        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 404

        solves = Solves.query.count()
        assert solves == 0

        wrong_keys = Fails.query.count()
        assert wrong_keys == 0
    destroy_ctfd(app)
def test_submitting_invalid_regex_flag():
    """Test that invalid regex flags are errored out to the user"""
    app = create_ctfd()
    with app.app_context():
        register_user(app)
        client = login_as_user(app)
        chal = gen_challenge(app.db)
        gen_flag(
            app.db,
            challenge_id=chal.id,
            type="regex",
            content="**",
            data="case_insensitive",
        )
        data = {"submission": "FLAG", "challenge_id": chal.id}
        r = client.post("/api/v1/challenges/attempt", json=data)
        assert r.status_code == 200
        resp = r.get_json()["data"]
        assert resp.get("status") == "incorrect"
        assert resp.get("message") == "Regex parse error occured"
    destroy_ctfd(app)
Exemple #29
0
def test_user_can_unlock_hint():
    """Test that a user can unlock a hint if they have enough points"""
    app = create_kmactf()
    with app.app_context():
        with app.test_client():
            register_user(app, name="user1", email="*****@*****.**")

            chal = gen_challenge(app.db, value=100)
            chal_id = chal.id

            gen_flag(app.db, challenge_id=chal.id, content="flag")

            hint = gen_hint(app.db, chal_id, cost=10)
            hint_id = hint.id

            gen_award(app.db, user_id=2, value=15)

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

            user = Users.query.filter_by(name="user1").first()
            assert user.score == 15

            with client.session_transaction():
                r = client.get("/api/v1/hints/{}".format(hint_id))
                resp = r.get_json()
                assert resp["data"].get("content") is None

                params = {"target": hint_id, "type": "hints"}

                r = client.post("/api/v1/unlocks", json=params)
                resp = r.get_json()
                assert resp["success"] is True

                r = client.get("/api/v1/hints/{}".format(hint_id))
                resp = r.get_json()
                assert resp["data"].get("content") == "This is a hint"

                user = Users.query.filter_by(name="user1").first()
                assert user.score == 5
    destroy_kmactf(app)
def test_challenges_cannot_be_solved_while_paused():
    """Test that challenges cannot be solved when the CTF is paused"""
    app = create_ctfd()
    with app.app_context():
        set_config('paused', True)

        register_user(app)
        client = login_as_user(app)

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

        # Assert that there is a paused message
        data = r.get_data(as_text=True)
        assert 'paused' in data

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

        data = {
            "submission": 'flag',
            "challenge_id": chal.id
        }
        r = client.post('/api/v1/challenges/attempt', json=data)

        # Assert that the JSON message is correct
        resp = r.get_json()['data']
        assert r.status_code == 403
        assert resp['status'] == 'paused'
        assert resp['message'] == 'CTFd is paused'

        # There are no solves saved
        solves = Solves.query.count()
        assert solves == 0

        # There are no wrong keys saved
        wrong_keys = Fails.query.count()
        assert wrong_keys == 0
    destroy_ctfd(app)