vishalbhat07/ctfd
0
1#!/usr/bin/env python2# -*- coding: utf-8 -*-3from datetime import datetime, timedelta4 5from freezegun import freeze_time6 7from CTFd.models import Challenges, Fails, Ratelimiteds, Solves8from CTFd.utils import set_config, text_type9from tests.helpers import (10 create_ctfd,11 destroy_ctfd,12 gen_challenge,13 gen_fail,14 gen_flag,15 gen_hint,16 login_as_user,17 register_user,18)19 20 21def test_user_get_challenges():22 """23 Can a registered user load /challenges24 """25 app = create_ctfd()26 with app.app_context():27 register_user(app)28 client = login_as_user(app)29 r = client.get("/challenges")30 assert r.status_code == 20031 destroy_ctfd(app)32 33 34def test_user_get_chals():35 """36 Can a registered user load /chals37 """38 app = create_ctfd()39 with app.app_context():40 register_user(app)41 client = login_as_user(app)42 r = client.get("/api/v1/challenges")43 assert r.status_code == 20044 destroy_ctfd(app)45 46 47def test_viewing_challenges():48 """49 Test that users can see added challenges50 """51 app = create_ctfd()52 with app.app_context():53 register_user(app)54 client = login_as_user(app)55 gen_challenge(app.db)56 r = client.get("/api/v1/challenges")57 chals = r.get_json()["data"]58 assert len(chals) == 159 destroy_ctfd(app)60 61 62def test_viewing_challenge():63 """Test that users can see individual challenges"""64 app = create_ctfd()65 with app.app_context():66 register_user(app)67 client = login_as_user(app)68 gen_challenge(app.db)69 r = client.get("/api/v1/challenges/1")70 assert r.get_json()71 destroy_ctfd(app)72 73 74# def test_chals_solves():75# """Test that the /chals/solves endpoint works properly"""76# app = create_ctfd()77# with app.app_context():78# # Generate 5 users79# for c in range(1, 6):80# name = "user{}".format(c)81# email = "user{}@examplectf.com".format(c)82# register_user(app, name=name, email=email, password="password")83#84# # Generate 5 challenges85# for c in range(6):86# chal1 = gen_challenge(app.db, value=100)87#88# user_ids = list(range(2, 7))89# chal_ids = list(range(1, 6))90# for u in user_ids:91# for c in chal_ids:92# gen_solve(app.db, teamid=u, chalid=c)93# chal_ids.pop()94#95# client = login_as_user(app, name="user1")96#97# with client.session_transaction() as sess:98# r = client.get('/chals/solves')99# output = r.get_data(as_text=True)100# saved = json.loads('''{101# "1": 5,102# "2": 4,103# "3": 3,104# "4": 2,105# "5": 1,106# "6": 0107# }108# ''')109# received = json.loads(output)110# assert saved == received111# set_config('hide_scores', True)112# with client.session_transaction():113# r = client.get('/chals/solves')114# output = r.get_data(as_text=True)115# saved = json.loads('''{116# "1": -1,117# "2": -1,118# "3": -1,119# "4": -1,120# "5": -1,121# "6": -1122# }123# ''')124# received = json.loads(output)125# assert saved == received126# destroy_ctfd(app)127 128 129def test_submitting_correct_flag():130 """Test that correct flags are correct"""131 app = create_ctfd()132 with app.app_context():133 register_user(app)134 client = login_as_user(app)135 chal = gen_challenge(app.db)136 gen_flag(app.db, challenge_id=chal.id, content="flag")137 data = {"submission": "flag", "challenge_id": chal.id}138 r = client.post("/api/v1/challenges/attempt", json=data)139 assert r.status_code == 200140 resp = r.get_json()["data"]141 assert resp.get("status") == "correct"142 assert resp.get("message") == "Correct"143 destroy_ctfd(app)144 145 146def test_submitting_correct_static_case_insensitive_flag():147 """Test that correct static flags are correct if the static flag is marked case_insensitive"""148 app = create_ctfd()149 with app.app_context():150 register_user(app)151 client = login_as_user(app)152 chal = gen_challenge(app.db)153 gen_flag(app.db, challenge_id=chal.id, content="flag", data="case_insensitive")154 data = {"submission": "FLAG", "challenge_id": chal.id}155 r = client.post("/api/v1/challenges/attempt", json=data)156 assert r.status_code == 200157 resp = r.get_json()["data"]158 assert resp.get("status") == "correct"159 assert resp.get("message") == "Correct"160 destroy_ctfd(app)161 162 163def test_submitting_correct_regex_case_insensitive_flag():164 """Test that correct regex flags are correct if the regex flag is marked case_insensitive"""165 app = create_ctfd()166 with app.app_context():167 register_user(app)168 client = login_as_user(app)169 chal = gen_challenge(app.db)170 gen_flag(171 app.db,172 challenge_id=chal.id,173 type="regex",174 content="flag",175 data="case_insensitive",176 )177 data = {"submission": "FLAG", "challenge_id": chal.id}178 r = client.post("/api/v1/challenges/attempt", json=data)179 assert r.status_code == 200180 resp = r.get_json()["data"]181 assert resp.get("status") == "correct"182 assert resp.get("message") == "Correct"183 destroy_ctfd(app)184 185 186def test_submitting_invalid_regex_flag():187 """Test that invalid regex flags are errored out to the user"""188 app = create_ctfd()189 with app.app_context():190 register_user(app)191 client = login_as_user(app)192 chal = gen_challenge(app.db)193 gen_flag(194 app.db,195 challenge_id=chal.id,196 type="regex",197 content="**",198 data="case_insensitive",199 )200 data = {"submission": "FLAG", "challenge_id": chal.id}201 r = client.post("/api/v1/challenges/attempt", json=data)202 assert r.status_code == 200203 resp = r.get_json()["data"]204 assert resp.get("status") == "incorrect"205 assert resp.get("message") == "Regex parse error occured"206 destroy_ctfd(app)207 208 209def test_submitting_incorrect_flag():210 """Test that incorrect flags are incorrect"""211 app = create_ctfd()212 with app.app_context():213 register_user(app)214 client = login_as_user(app)215 chal = gen_challenge(app.db)216 gen_flag(app.db, challenge_id=chal.id, content="flag")217 data = {"submission": "notflag", "challenge_id": chal.id}218 r = client.post("/api/v1/challenges/attempt", json=data)219 assert r.status_code == 200220 resp = r.get_json()["data"]221 assert resp.get("status") == "incorrect"222 assert resp.get("message") == "Incorrect"223 destroy_ctfd(app)224 225 226def test_submitting_unicode_flag():227 """Test that users can submit a unicode flag"""228 app = create_ctfd()229 with app.app_context():230 register_user(app)231 client = login_as_user(app)232 chal = gen_challenge(app.db)233 gen_flag(app.db, challenge_id=chal.id, content="你好")234 with client.session_transaction():235 data = {"submission": "你好", "challenge_id": chal.id}236 r = client.post("/api/v1/challenges/attempt", json=data)237 assert r.status_code == 200238 resp = r.get_json()["data"]239 assert resp.get("status") == "correct"240 assert resp.get("message") == "Correct"241 destroy_ctfd(app)242 243 244def test_challenges_with_max_attempts():245 """Test that users are locked out of a challenge after they reach max_attempts"""246 app = create_ctfd()247 with app.app_context():248 register_user(app)249 client = login_as_user(app)250 chal = gen_challenge(app.db)251 chal = Challenges.query.filter_by(id=chal.id).first()252 chal_id = chal.id253 chal.max_attempts = 3254 app.db.session.commit()255 256 gen_flag(app.db, challenge_id=chal.id, content="flag")257 for _ in range(3):258 data = {"submission": "notflag", "challenge_id": chal_id}259 r = client.post("/api/v1/challenges/attempt", json=data)260 261 wrong_keys = Fails.query.count()262 assert wrong_keys == 3263 264 data = {"submission": "flag", "challenge_id": chal_id}265 r = client.post("/api/v1/challenges/attempt", json=data)266 assert r.status_code == 403267 268 resp = r.get_json()["data"]269 assert resp.get("status") == "ratelimited"270 assert resp.get("message") == "Not accepted. You have 0 tries remaining"271 272 solves = Solves.query.count()273 assert solves == 0274 destroy_ctfd(app)275 276 277def test_challenges_with_max_attempts_timeout_behavior():278 """Test that users are temporarily locked out of a challenge after reaching max_attempts with timeout behavior"""279 280 app = create_ctfd()281 with app.app_context():282 set_config("max_attempts_behavior", "timeout")283 set_config("max_attempts_timeout", 300) # 300 seconds timeout for test284 285 register_user(app)286 client = login_as_user(app)287 chal = gen_challenge(app.db)288 chal = Challenges.query.filter_by(id=chal.id).first()289 chal_id = chal.id290 chal.max_attempts = 2291 app.db.session.commit()292 293 gen_flag(app.db, challenge_id=chal.id, content="flag")294 for _ in range(2):295 data = {"submission": "notflag", "challenge_id": chal_id}296 r = client.post("/api/v1/challenges/attempt", json=data)297 assert r.status_code == 200298 299 # Should be locked out now300 with freeze_time(timedelta(seconds=0)):301 data = {"submission": "flag", "challenge_id": chal_id}302 r = client.post("/api/v1/challenges/attempt", json=data)303 assert r.status_code == 429304 resp = r.get_json()["data"]305 assert resp.get("status") == "ratelimited"306 assert "Not accepted. Try again in 300 seconds" in resp.get("message")307 308 # Use freeze_time to advance time by 290 seconds309 with freeze_time(timedelta(seconds=290)):310 data = {"submission": "flag", "challenge_id": chal_id}311 r = client.post("/api/v1/challenges/attempt", json=data)312 assert r.status_code == 429313 resp = r.get_json()["data"]314 assert resp.get("status") == "ratelimited"315 assert "Not accepted. Try again in 10 seconds" in resp.get("message")316 317 # Use freeze_time to advance time by 301 seconds318 with freeze_time(timedelta(seconds=301)):319 data = {"submission": "flag", "challenge_id": chal_id}320 r = client.post("/api/v1/challenges/attempt", json=data)321 assert r.status_code == 200322 resp = r.get_json()["data"]323 # Should be correct now324 assert resp.get("status") == "correct"325 assert resp.get("message") == "Correct"326 destroy_ctfd(app)327 328 329def test_challenges_with_max_attempts_timeout_ratelimit():330 """Test that max_attempts timeout ratelimit and global ratelimit work together correctly"""331 app = create_ctfd()332 with app.app_context():333 set_config("max_attempts_behavior", "timeout")334 set_config("max_attempts_timeout", 30) # 30 seconds timeout for test335 336 register_user(app)337 client = login_as_user(app)338 339 # Challenge 1 with max_attempts = 5340 chal1 = gen_challenge(app.db)341 chal1_obj = Challenges.query.filter_by(id=chal1.id).first()342 chal1_obj.max_attempts = 5343 app.db.session.commit()344 gen_flag(app.db, challenge_id=chal1.id, content="flag1")345 346 # Challenge 2 with no max_attempts347 chal2 = gen_challenge(app.db)348 gen_flag(app.db, challenge_id=chal2.id, content="flag2")349 350 base_time = datetime.utcnow()351 352 # Submit 5 wrong attempts to challenge 1 (triggers max_attempts ratelimit)353 with freeze_time(base_time):354 for _ in range(5):355 data = {"submission": "wrong", "challenge_id": chal1.id}356 r = client.post("/api/v1/challenges/attempt", json=data)357 assert r.status_code == 200358 359 # 6th attempt should be blocked by max_attempts timeout360 data = {"submission": "flag1", "challenge_id": chal1.id}361 r = client.post("/api/v1/challenges/attempt", json=data)362 assert r.status_code == 429363 resp = r.get_json()["data"]364 assert resp.get("status") == "ratelimited"365 assert "Try again in 30 seconds" in resp.get("message")366 367 # Now submit 5 more wrong attempts to challenge 2 (total 10 fails, triggers global ratelimit)368 for i in range(6):369 data = {"submission": "wrong", "challenge_id": chal2.id}370 r = client.post("/api/v1/challenges/attempt", json=data)371 if i < 5:372 assert r.status_code == 200373 else:374 # 11th attempt should be blocked by global ratelimit (60 seconds)375 assert r.status_code == 429376 resp = r.get_json()["data"]377 assert resp.get("status") == "ratelimited"378 assert "You're submitting flags too fast" in resp.get("message")379 380 # Check counts381 wrong_keys = Fails.query.count()382 ratelimiteds = Ratelimiteds.query.count()383 assert wrong_keys == 10384 assert (385 ratelimiteds == 2386 ) # One max_attempts ratelimit + one global ratelimit387 388 # After 30 seconds, max_attempts timeout should release but global ratelimit (60s) still active389 with freeze_time(base_time + timedelta(seconds=31)):390 # Try challenge 1 - should still be blocked by global ratelimit391 data = {"submission": "flag1", "challenge_id": chal1.id}392 r = client.post("/api/v1/challenges/attempt", json=data)393 assert r.status_code == 429394 resp = r.get_json()["data"]395 assert resp.get("status") == "ratelimited"396 assert "Try again in 30 seconds" in resp.get("message")397 398 ratelimiteds = Ratelimiteds.query.count()399 assert ratelimiteds == 3 # Another ratelimit entry400 401 # After 60 seconds, both ratelimits should be released402 with freeze_time(base_time + timedelta(seconds=61)):403 # Should be able to solve challenge 1 now404 data = {"submission": "flag1", "challenge_id": chal1.id}405 r = client.post("/api/v1/challenges/attempt", json=data)406 assert r.status_code == 200407 resp = r.get_json()["data"]408 assert resp.get("status") == "correct"409 assert resp.get("message") == "Correct"410 411 # Verify solve was recorded412 solves = Solves.query.count()413 assert solves == 1414 destroy_ctfd(app)415 416 417def test_challenges_max_attempts_timeout_config_change():418 """Test that changing max_attempts_timeout resets attempt count because cache key changes"""419 app = create_ctfd()420 with app.app_context():421 set_config("max_attempts_behavior", "timeout")422 set_config("max_attempts_timeout", 300) # Start with 300 seconds423 424 register_user(app)425 client = login_as_user(app)426 chal = gen_challenge(app.db)427 chal = Challenges.query.filter_by(id=chal.id).first()428 chal_id = chal.id429 chal.max_attempts = 3430 app.db.session.commit()431 432 gen_flag(app.db, challenge_id=chal.id, content="flag")433 434 # Make 3 wrong attempts (hit the limit)435 for _ in range(3):436 data = {"submission": "notflag", "challenge_id": chal_id}437 r = client.post("/api/v1/challenges/attempt", json=data)438 assert r.status_code == 200439 440 # Verify we're locked out441 data = {"submission": "flag", "challenge_id": chal_id}442 r = client.post("/api/v1/challenges/attempt", json=data)443 assert r.status_code == 429444 resp = r.get_json()["data"]445 assert resp.get("status") == "ratelimited"446 assert "300 seconds" in resp.get("message")447 448 # Change the max_attempts_timeout config (this changes the cache key)449 set_config("max_attempts_timeout", 30) # Change to 30 seconds450 451 # Jump forward in time to ensure we're past the new 30 second rate limit452 with freeze_time(timedelta(seconds=35)):453 # Now we should be able to submit again because the cache key changed454 # Old submissions have also fallen off the 30 second window455 data = {"submission": "flag", "challenge_id": chal_id}456 r = client.post("/api/v1/challenges/attempt", json=data)457 assert r.status_code == 200458 resp = r.get_json()["data"]459 assert resp.get("status") == "correct"460 assert resp.get("message") == "Correct"461 462 # Verify solve was recorded463 solves = Solves.query.count()464 assert solves == 1465 466 # Verify the fail count is still accurate (3 fails from before)467 fails = Fails.query.count()468 assert fails == 3469 470 ratelimiteds = Ratelimiteds.query.count()471 assert ratelimiteds == 1472 destroy_ctfd(app)473 474 475def test_challenge_kpm_limit_no_freeze():476 """Test that users are properly ratelimited when submitting flags"""477 app = create_ctfd()478 with app.app_context():479 register_user(app)480 client = login_as_user(app)481 chal = gen_challenge(app.db)482 chal_id = chal.id483 484 gen_flag(app.db, challenge_id=chal.id, content="flag")485 for _ in range(11):486 with client.session_transaction():487 data = {"submission": "notflag", "challenge_id": chal_id}488 client.post("/api/v1/challenges/attempt", json=data)489 490 wrong_keys = Fails.query.count()491 ratelimiteds = Ratelimiteds.query.count()492 assert wrong_keys == 10493 assert ratelimiteds == 1494 495 # We just want a consistent flag response countdown496 with freeze_time(timedelta(seconds=0)):497 data = {"submission": "flag", "challenge_id": chal_id}498 r = client.post("/api/v1/challenges/attempt", json=data)499 assert r.status_code == 429500 501 wrong_keys = Fails.query.count()502 ratelimiteds = Ratelimiteds.query.count()503 assert wrong_keys == 10504 assert ratelimiteds == 2505 506 resp = r.get_json()["data"]507 assert resp.get("status") == "ratelimited"508 assert (509 resp.get("message")510 == "You're submitting flags too fast. Try again in 60 seconds."511 )512 513 solves = Solves.query.count()514 assert solves == 0515 destroy_ctfd(app)516 517 518def test_challenge_kpm_limit_freeze_time():519 """Test that users are properly ratelimited when submitting flags"""520 app = create_ctfd()521 with app.app_context():522 register_user(app)523 client = login_as_user(app)524 chal = gen_challenge(app.db)525 chal_id = chal.id526 527 gen_flag(app.db, challenge_id=chal.id, content="flag")528 base_time = datetime.utcnow()529 530 # First section: Use API to generate 10 fails + 1 ratelimit531 with freeze_time(base_time):532 for _ in range(11):533 with client.session_transaction():534 data = {"submission": "notflag", "challenge_id": chal_id}535 r = client.post("/api/v1/challenges/attempt", json=data)536 537 wrong_keys = Fails.query.count()538 ratelimiteds = Ratelimiteds.query.count()539 assert wrong_keys == 10540 assert ratelimiteds == 1541 542 # Within the 1 min time frame we should still be ratelimited543 with freeze_time(base_time + timedelta(seconds=11)):544 data = {"submission": "flag", "challenge_id": chal_id}545 r = client.post("/api/v1/challenges/attempt", json=data)546 assert r.status_code == 429547 548 wrong_keys = Fails.query.count()549 ratelimiteds = Ratelimiteds.query.count()550 assert wrong_keys == 10551 assert ratelimiteds == 2552 553 resp = r.get_json()["data"]554 assert resp.get("status") == "ratelimited"555 assert (556 resp.get("message")557 == "You're submitting flags too fast. Try again in 50 seconds."558 )559 560 # Generate 10 more fails at +60 seconds using gen_fail because freezegun cannot patch to sqlalchemy's default561 for _ in range(10):562 fail = gen_fail(app.db, user_id=2, challenge_id=chal_id, provided="notflag")563 fail.date = base_time + timedelta(seconds=60)564 app.db.session.commit()565 566 # The 11th attempt via API should trigger another ratelimit567 with freeze_time(base_time + timedelta(seconds=60)):568 data = {"submission": "notflag", "challenge_id": chal_id}569 client.post("/api/v1/challenges/attempt", json=data)570 571 wrong_keys = Fails.query.count()572 ratelimiteds = Ratelimiteds.query.count()573 assert wrong_keys == 20574 assert ratelimiteds == 3575 destroy_ctfd(app)576 577 578def test_that_view_challenges_unregistered_works():579 """Test that view_challenges_unregistered works"""580 app = create_ctfd()581 with app.app_context():582 chal = gen_challenge(app.db, name=text_type("🐺"))583 chal_id = chal.id584 gen_hint(app.db, chal_id)585 586 client = app.test_client()587 r = client.get("/api/v1/challenges", json="")588 assert r.status_code == 403589 r = client.get("/api/v1/challenges")590 assert r.status_code == 302591 592 set_config("challenge_visibility", "public")593 594 client = app.test_client()595 r = client.get("/api/v1/challenges")596 assert r.get_json()["data"]597 598 r = client.get("/api/v1/challenges/1/solves")599 assert r.get_json().get("data") is not None600 601 data = {"submission": "not_flag", "challenge_id": chal_id}602 r = client.post("/api/v1/challenges/attempt", json=data)603 assert r.status_code == 403604 assert r.get_json().get("data").get("status") == "authentication_required"605 assert r.get_json().get("data").get("message") is None606 destroy_ctfd(app)607 608 609def test_hidden_challenge_is_unreachable():610 """Test that hidden challenges return 404 and do not insert a solve or wrong key"""611 app = create_ctfd()612 with app.app_context():613 register_user(app)614 client = login_as_user(app)615 chal = gen_challenge(app.db, state="hidden")616 gen_flag(app.db, challenge_id=chal.id, content="flag")617 chal_id = chal.id618 619 assert Challenges.query.count() == 1620 621 r = client.get("/api/v1/challenges", json="")622 data = r.get_json().get("data")623 assert data == []624 625 r = client.get("/api/v1/challenges/1", json="")626 assert r.status_code == 404627 data = r.get_json().get("data")628 assert data is None629 630 data = {"submission": "flag", "challenge_id": chal_id}631 632 r = client.post("/api/v1/challenges/attempt", json=data)633 assert r.status_code == 404634 635 r = client.post("/api/v1/challenges/attempt?preview=true", json=data)636 assert r.status_code == 404637 assert r.get_json().get("data") is None638 639 solves = Solves.query.count()640 assert solves == 0641 642 wrong_keys = Fails.query.count()643 assert wrong_keys == 0644 destroy_ctfd(app)645 646 647def test_hidden_challenge_is_unsolveable():648 """Test that hidden challenges return 404 and do not insert a solve or wrong key"""649 app = create_ctfd()650 with app.app_context():651 register_user(app)652 client = login_as_user(app)653 chal = gen_challenge(app.db, state="hidden")654 gen_flag(app.db, challenge_id=chal.id, content="flag")655 656 data = {"submission": "flag", "challenge_id": chal.id}657 658 r = client.post("/api/v1/challenges/attempt", json=data)659 assert r.status_code == 404660 661 solves = Solves.query.count()662 assert solves == 0663 664 wrong_keys = Fails.query.count()665 assert wrong_keys == 0666 destroy_ctfd(app)667 668 669def test_invalid_requirements_are_rejected():670 """Test that invalid requirements JSON blobs are rejected by the API"""671 app = create_ctfd()672 with app.app_context():673 gen_challenge(app.db)674 gen_challenge(app.db)675 with login_as_user(app, "admin") as client:676 # Test None/null values677 r = client.patch(678 "/api/v1/challenges/1", json={"requirements": {"prerequisites": [None]}}679 )680 assert r.status_code == 400681 assert r.get_json() == {682 "success": False,683 "errors": {684 "requirements": [685 "Challenge requirements cannot have a null prerequisite"686 ]687 },688 }689 # Test empty strings690 r = client.patch(691 "/api/v1/challenges/1", json={"requirements": {"prerequisites": [""]}}692 )693 assert r.status_code == 400694 assert r.get_json() == {695 "success": False,696 "errors": {697 "requirements": [698 "Challenge requirements cannot have a null prerequisite"699 ]700 },701 }702 # Test a valid integer703 r = client.patch(704 "/api/v1/challenges/1", json={"requirements": {"prerequisites": [2]}}705 )706 assert r.status_code == 200707 destroy_ctfd(app)708 709 710def test_challenge_with_requirements_is_unsolveable():711 """Test that a challenge with a requirement is unsolveable without first solving the requirement"""712 app = create_ctfd()713 with app.app_context():714 register_user(app)715 client = login_as_user(app)716 chal1 = gen_challenge(app.db)717 gen_flag(app.db, challenge_id=chal1.id, content="flag")718 719 requirements = {"prerequisites": [1]}720 chal2 = gen_challenge(app.db, requirements=requirements)721 app.db.session.commit()722 723 gen_flag(app.db, challenge_id=chal2.id, content="flag")724 725 r = client.get("/api/v1/challenges")726 challenges = r.get_json()["data"]727 assert len(challenges) == 1728 assert challenges[0]["id"] == 1729 730 r = client.get("/api/v1/challenges/2")731 assert r.status_code == 403732 assert r.get_json().get("data") is None733 734 # Attempt to solve hidden Challenge 2735 data = {"submission": "flag", "challenge_id": 2}736 r = client.post("/api/v1/challenges/attempt", json=data)737 assert r.status_code == 403738 assert r.get_json().get("data") is None739 740 # Solve Challenge 1741 data = {"submission": "flag", "challenge_id": 1}742 r = client.post("/api/v1/challenges/attempt", json=data)743 resp = r.get_json()["data"]744 assert resp["status"] == "correct"745 746 # Challenge 2 should now be visible747 r = client.get("/api/v1/challenges")748 challenges = r.get_json()["data"]749 assert len(challenges) == 2750 751 r = client.get("/api/v1/challenges/2")752 assert r.status_code == 200753 assert r.get_json().get("data")["id"] == 2754 755 # Attempt to solve the now-visible Challenge 2756 data = {"submission": "flag", "challenge_id": 2}757 r = client.post("/api/v1/challenges/attempt", json=data)758 assert r.status_code == 200759 assert resp["status"] == "correct"760 761 destroy_ctfd(app)762 763 764def test_challenges_cannot_be_solved_while_paused():765 """Test that challenges cannot be solved when the CTF is paused"""766 app = create_ctfd()767 with app.app_context():768 set_config("paused", True)769 770 register_user(app)771 client = login_as_user(app)772 773 r = client.get("/challenges")774 assert r.status_code == 200775 776 # Assert that there is a paused message777 data = r.get_data(as_text=True)778 assert "paused" in data779 780 chal = gen_challenge(app.db)781 gen_flag(app.db, challenge_id=chal.id, content="flag")782 783 data = {"submission": "flag", "challenge_id": chal.id}784 r = client.post("/api/v1/challenges/attempt", json=data)785 786 # Assert that the JSON message is correct787 resp = r.get_json()["data"]788 assert r.status_code == 403789 assert resp["status"] == "paused"790 assert resp["message"] == "CTFd is paused"791 792 # There are no solves saved793 solves = Solves.query.count()794 assert solves == 0795 796 # There are no wrong keys saved797 wrong_keys = Fails.query.count()798 assert wrong_keys == 0799 destroy_ctfd(app)800 801 802def test_challenge_board_under_view_after_ctf():803 """Test that the challenge board does not show an error under view_after_ctf"""804 app = create_ctfd()805 with app.app_context():806 set_config("view_after_ctf", True)807 set_config(808 "start", "1507089600"809 ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST810 set_config(811 "end", "1507262400"812 ) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST813 814 register_user(app)815 client = login_as_user(app)816 817 gen_challenge(app.db)818 gen_flag(app.db, challenge_id=1, content="flag")819 820 gen_challenge(app.db)821 gen_flag(app.db, challenge_id=2, content="flag")822 823 # CTF hasn't started yet. There should be an error message.824 with freeze_time("2017-10-3"):825 r = client.get("/challenges")826 assert r.status_code == 403827 assert "has not started yet" in r.get_data(as_text=True)828 829 data = {"submission": "flag", "challenge_id": 2}830 r = client.post("/api/v1/challenges/attempt", json=data)831 assert r.status_code == 403832 assert Solves.query.count() == 0833 834 # CTF is ongoing. Normal operation.835 with freeze_time("2017-10-5"):836 r = client.get("/challenges")837 assert r.status_code == 200838 assert "has ended" not in r.get_data(as_text=True)839 840 data = {"submission": "flag", "challenge_id": 1}841 r = client.post("/api/v1/challenges/attempt", json=data)842 assert r.status_code == 200843 assert r.get_json()["data"]["status"] == "correct"844 assert Solves.query.count() == 1845 846 # CTF is now over. There should be a message and challenges should show submission status but not store solves847 with freeze_time("2017-10-7"):848 r = client.get("/challenges")849 assert r.status_code == 200850 assert "has ended" in r.get_data(as_text=True)851 852 data = {"submission": "flag", "challenge_id": 2}853 r = client.post("/api/v1/challenges/attempt", json=data)854 assert r.status_code == 200855 assert r.get_json()["data"]["status"] == "correct"856 assert Solves.query.count() == 1857 destroy_ctfd(app)858 859 860def test_challenges_under_view_after_ctf():861 app = create_ctfd()862 with app.app_context(), freeze_time("2017-10-7"):863 set_config(864 "start", "1507089600"865 ) # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST866 set_config(867 "end", "1507262400"868 ) # Friday, October 6, 2017 12:00:00 AM GMT-04:00 DST869 870 register_user(app)871 client = login_as_user(app)872 873 gen_challenge(app.db)874 gen_flag(app.db, challenge_id=1, content="flag")875 876 r = client.get("/challenges")877 assert r.status_code == 403878 879 r = client.get("/api/v1/challenges")880 assert r.status_code == 403881 assert r.get_json().get("data") is None882 883 r = client.get("/api/v1/challenges/1")884 assert r.status_code == 403885 assert r.get_json().get("data") is None886 887 data = {"submission": "flag", "challenge_id": 1}888 r = client.post("/api/v1/challenges/attempt", json=data)889 assert r.status_code == 403890 assert r.get_json().get("data") is None891 assert Solves.query.count() == 0892 893 data = {"submission": "notflag", "challenge_id": 1}894 r = client.post("/api/v1/challenges/attempt", json=data)895 assert r.status_code == 403896 assert r.get_json().get("data") is None897 assert Fails.query.count() == 0898 899 set_config("view_after_ctf", True)900 901 r = client.get("/challenges")902 assert r.status_code == 200903 904 r = client.get("/api/v1/challenges")905 assert r.status_code == 200906 assert r.get_json()["data"][0]["id"] == 1907 908 r = client.get("/api/v1/challenges/1")909 assert r.status_code == 200910 assert r.get_json()["data"]["id"] == 1911 912 data = {"submission": "flag", "challenge_id": 1}913 r = client.post("/api/v1/challenges/attempt", json=data)914 assert r.status_code == 200915 assert r.get_json()["data"]["status"] == "correct"916 assert Solves.query.count() == 0917 918 data = {"submission": "notflag", "challenge_id": 1}919 r = client.post("/api/v1/challenges/attempt", json=data)920 assert r.status_code == 200921 assert r.get_json()["data"]["status"] == "incorrect"922 assert Fails.query.count() == 0923 924 destroy_ctfd(app)925 926 927def test_challenges_admin_only_as_user():928 app = create_ctfd()929 with app.app_context():930 set_config("challenge_visibility", "admins")931 932 register_user(app)933 admin = login_as_user(app, name="admin")934 935 gen_challenge(app.db)936 gen_flag(app.db, challenge_id=1, content="flag")937 938 r = admin.get("/challenges")939 assert r.status_code == 200940 941 r = admin.get("/api/v1/challenges", json="")942 assert r.status_code == 200943 944 r = admin.get("/api/v1/challenges/1", json="")945 assert r.status_code == 200946 947 data = {"submission": "flag", "challenge_id": 1}948 r = admin.post("/api/v1/challenges/attempt", json=data)949 assert r.status_code == 200950 destroy_ctfd(app)951 