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