vishalbhat07/ctfd
0
1#!/usr/bin/env python2# -*- coding: utf-8 -*-3 4from CTFd.models import Challenges5from CTFd.plugins.dynamic_challenges import DynamicChallenge, DynamicValueChallenge6from CTFd.utils.security.signing import hmac7from tests.helpers import (8 FakeRequest,9 create_ctfd,10 destroy_ctfd,11 gen_flag,12 gen_user,13 login_as_user,14 register_user,15)16 17 18def test_can_create_dynamic_challenge():19 """Test that dynamic challenges can be made from the API/admin panel"""20 app = create_ctfd(enable_plugins=True)21 with app.app_context():22 register_user(app)23 client = login_as_user(app, name="admin", password="password")24 25 challenge_data = {26 "name": "name",27 "category": "category",28 "description": "description",29 "initial": 100,30 "decay": 20,31 "minimum": 1,32 "state": "hidden",33 "type": "dynamic",34 }35 36 r = client.post("/api/v1/challenges", json=challenge_data)37 assert r.get_json().get("data")["id"] == 138 39 challenges = DynamicChallenge.query.all()40 assert len(challenges) == 141 42 challenge = challenges[0]43 assert challenge.value == 10044 assert challenge.initial == 10045 assert challenge.decay == 2046 assert challenge.minimum == 147 destroy_ctfd(app)48 49 50def test_can_update_dynamic_challenge():51 app = create_ctfd(enable_plugins=True)52 with app.app_context():53 challenge_data = {54 "name": "name",55 "category": "category",56 "description": "description",57 "initial": 100,58 "decay": 20,59 "minimum": 1,60 "state": "hidden",61 "type": "dynamic",62 }63 req = FakeRequest(form=challenge_data)64 challenge = DynamicValueChallenge.create(req)65 66 assert challenge.value == 10067 assert challenge.initial == 10068 assert challenge.decay == 2069 assert challenge.minimum == 170 71 challenge_data = {72 "name": "new_name",73 "category": "category",74 "description": "new_description",75 "value": "200",76 "initial": "200",77 "decay": "40",78 "minimum": "5",79 "max_attempts": "0",80 "state": "visible",81 }82 83 req = FakeRequest(form=challenge_data)84 challenge = DynamicValueChallenge.update(challenge, req)85 86 assert challenge.name == "new_name"87 assert challenge.description == "new_description"88 assert challenge.value == 20089 assert challenge.initial == 20090 assert challenge.decay == 4091 assert challenge.minimum == 592 assert challenge.state == "visible"93 94 destroy_ctfd(app)95 96 97def test_can_add_requirement_dynamic_challenge():98 """Test that requirements can be added to dynamic challenges"""99 app = create_ctfd(enable_plugins=True)100 with app.app_context():101 challenge_data = {102 "name": "name",103 "category": "category",104 "description": "description",105 "initial": 100,106 "decay": 20,107 "minimum": 1,108 "state": "hidden",109 "type": "dynamic",110 }111 req = FakeRequest(form=challenge_data)112 challenge = DynamicValueChallenge.create(req)113 114 assert challenge.value == 100115 assert challenge.initial == 100116 assert challenge.decay == 20117 assert challenge.minimum == 1118 119 challenge_data = {120 "name": "second_name",121 "category": "category",122 "description": "new_description",123 "value": "200",124 "initial": "200",125 "decay": "40",126 "minimum": "5",127 "max_attempts": "0",128 "state": "visible",129 }130 131 req = FakeRequest(form=challenge_data)132 challenge = DynamicValueChallenge.create(req)133 134 assert challenge.name == "second_name"135 assert challenge.description == "new_description"136 assert challenge.value == 200137 assert challenge.initial == 200138 assert challenge.decay == 40139 assert challenge.minimum == 5140 assert challenge.state == "visible"141 142 challenge_data = {"requirements": [1]}143 144 req = FakeRequest(form=challenge_data)145 challenge = DynamicValueChallenge.update(challenge, req)146 147 assert challenge.requirements == [1]148 149 destroy_ctfd(app)150 151 152def test_can_delete_dynamic_challenge():153 """Test that dynamic challenges can be deleted"""154 app = create_ctfd(enable_plugins=True)155 with app.app_context():156 register_user(app)157 client = login_as_user(app, name="admin", password="password")158 159 challenge_data = {160 "name": "name",161 "category": "category",162 "description": "description",163 "initial": 100,164 "decay": 20,165 "minimum": 1,166 "state": "hidden",167 "type": "dynamic",168 }169 170 r = client.post("/api/v1/challenges", json=challenge_data)171 assert r.get_json().get("data")["id"] == 1172 173 challenges = DynamicChallenge.query.all()174 assert len(challenges) == 1175 176 challenge = challenges[0]177 DynamicValueChallenge.delete(challenge)178 179 challenges = DynamicChallenge.query.all()180 assert len(challenges) == 0181 destroy_ctfd(app)182 183 184def test_dynamic_challenge_loses_value_properly():185 app = create_ctfd(enable_plugins=True)186 with app.app_context():187 register_user(app)188 client = login_as_user(app, name="admin", password="password")189 190 challenge_data = {191 "name": "name",192 "category": "category",193 "description": "description",194 "initial": 100,195 "decay": 20,196 "minimum": 1,197 "state": "visible",198 "type": "dynamic",199 }200 201 r = client.post("/api/v1/challenges", json=challenge_data)202 assert r.get_json().get("data")["id"] == 1203 204 gen_flag(app.db, challenge_id=1, content="flag")205 206 for i, team_id in enumerate(range(2, 26)):207 name = "user{}".format(team_id)208 email = "user{}@examplectf.com".format(team_id)209 # We need to bypass rate-limiting so gen_user instead of register_user210 user = gen_user(app.db, name=name, email=email)211 user_id = user.id212 213 with app.test_client() as client:214 # We need to bypass rate-limiting so creating a fake user instead of logging in215 with client.session_transaction() as sess:216 sess["id"] = user_id217 sess["nonce"] = "fake-nonce"218 sess["hash"] = hmac(user.password)219 220 data = {"submission": "flag", "challenge_id": 1}221 222 r = client.post("/api/v1/challenges/attempt", json=data)223 resp = r.get_json()["data"]224 assert resp["status"] == "correct"225 226 chal = DynamicChallenge.query.filter_by(id=1).first()227 if i >= 20:228 assert chal.value == chal.minimum229 else:230 assert chal.initial >= chal.value231 assert chal.value > chal.minimum232 destroy_ctfd(app)233 234 235def test_dynamic_challenge_doesnt_lose_value_on_update():236 """Dynamic challenge updates without changing any values or solves shouldn't change the current value. See #1043"""237 app = create_ctfd(enable_plugins=True)238 with app.app_context():239 challenge_data = {240 "name": "name",241 "category": "category",242 "description": "description",243 "initial": 10000,244 "decay": 4,245 "minimum": 10,246 "state": "visible",247 "type": "dynamic",248 }249 req = FakeRequest(form=challenge_data)250 challenge = DynamicValueChallenge.create(req)251 challenge_id = challenge.id252 gen_flag(app.db, challenge_id=challenge.id, content="flag")253 register_user(app)254 with login_as_user(app) as client:255 data = {"submission": "flag", "challenge_id": challenge_id}256 r = client.post("/api/v1/challenges/attempt", json=data)257 assert r.status_code == 200258 assert r.get_json()["data"]["status"] == "correct"259 chal = Challenges.query.filter_by(id=challenge_id).first()260 prev_chal_value = chal.value261 chal = DynamicValueChallenge.update(chal, req)262 assert prev_chal_value == chal.value263 destroy_ctfd(app)264 265 266def test_dynamic_challenge_value_isnt_affected_by_hidden_users():267 app = create_ctfd(enable_plugins=True)268 with app.app_context():269 register_user(app)270 client = login_as_user(app, name="admin", password="password")271 272 challenge_data = {273 "name": "name",274 "category": "category",275 "description": "description",276 "initial": 100,277 "decay": 20,278 "minimum": 1,279 "state": "visible",280 "type": "dynamic",281 }282 283 r = client.post("/api/v1/challenges", json=challenge_data)284 assert r.get_json().get("data")["id"] == 1285 286 gen_flag(app.db, challenge_id=1, content="flag")287 288 # Make a solve as a regular user. This should not affect the value.289 data = {"submission": "flag", "challenge_id": 1}290 291 r = client.post("/api/v1/challenges/attempt", json=data)292 resp = r.get_json()["data"]293 assert resp["status"] == "correct"294 295 # Make solves as hidden users. Also should not affect value296 for _, team_id in enumerate(range(2, 26)):297 name = "user{}".format(team_id)298 email = "user{}@examplectf.com".format(team_id)299 # We need to bypass rate-limiting so gen_user instead of register_user300 user = gen_user(app.db, name=name, email=email)301 user.hidden = True302 app.db.session.commit()303 user_id = user.id304 305 with app.test_client() as client:306 # We need to bypass rate-limiting so creating a fake user instead of logging in307 with client.session_transaction() as sess:308 sess["id"] = user_id309 sess["nonce"] = "fake-nonce"310 sess["hash"] = hmac(user.password)311 312 data = {"submission": "flag", "challenge_id": 1}313 314 r = client.post("/api/v1/challenges/attempt", json=data)315 assert r.status_code == 200316 resp = r.get_json()["data"]317 assert resp["status"] == "correct"318 319 chal = DynamicChallenge.query.filter_by(id=1).first()320 assert chal.value == chal.initial321 destroy_ctfd(app)322 323 324def test_dynamic_challenges_reset():325 app = create_ctfd(enable_plugins=True)326 with app.app_context():327 client = login_as_user(app, name="admin", password="password")328 329 challenge_data = {330 "name": "name",331 "category": "category",332 "description": "description",333 "initial": 100,334 "decay": 20,335 "minimum": 1,336 "state": "hidden",337 "type": "dynamic",338 }339 340 r = client.post("/api/v1/challenges", json=challenge_data)341 assert Challenges.query.count() == 1342 assert DynamicChallenge.query.count() == 1343 344 with client.session_transaction() as sess:345 data = {"nonce": sess.get("nonce"), "challenges": "on"}346 r = client.post("/admin/reset", data=data)347 assert r.location.endswith("/admin/statistics")348 assert Challenges.query.count() == 0349 assert DynamicChallenge.query.count() == 0350 351 destroy_ctfd(app)352 353 354def test_dynamic_challenge_linear_loses_value_properly():355 app = create_ctfd(enable_plugins=True)356 with app.app_context():357 register_user(app)358 client = login_as_user(app, name="admin", password="password")359 360 challenge_data = {361 "name": "name",362 "category": "category",363 "description": "description",364 "function": "linear",365 "initial": 100,366 "decay": 5,367 "minimum": 1,368 "state": "visible",369 "type": "dynamic",370 }371 372 r = client.post("/api/v1/challenges", json=challenge_data)373 assert r.get_json().get("data")["id"] == 1374 375 gen_flag(app.db, challenge_id=1, content="flag")376 377 for i, team_id in enumerate(range(2, 26)):378 name = "user{}".format(team_id)379 email = "user{}@examplectf.com".format(team_id)380 # We need to bypass rate-limiting so gen_user instead of register_user381 user = gen_user(app.db, name=name, email=email)382 user_id = user.id383 384 with app.test_client() as client:385 # We need to bypass rate-limiting so creating a fake user instead of logging in386 with client.session_transaction() as sess:387 sess["id"] = user_id388 sess["nonce"] = "fake-nonce"389 sess["hash"] = hmac(user.password)390 391 data = {"submission": "flag", "challenge_id": 1}392 r = client.post("/api/v1/challenges/attempt", json=data)393 resp = r.get_json()["data"]394 assert resp["status"] == "correct"395 396 chal = DynamicChallenge.query.filter_by(id=1).first()397 398 if i >= 20:399 assert chal.value == chal.minimum400 else:401 assert chal.value == (chal.initial - (i * 5))402 destroy_ctfd(app)403 