diff --git a/ctfcli/core/challenge.py b/ctfcli/core/challenge.py index 49901ac..969426b 100644 --- a/ctfcli/core/challenge.py +++ b/ctfcli/core/challenge.py @@ -45,7 +45,7 @@ class Challenge(dict): # fmt: off "name", "author", "category", "description", "attribution", "value", "type", "extra", "image", "protocol", "host", - "connection_info", "healthcheck", "attempts", "flags", + "connection_info", "healthcheck", "attempts", "logic", "flags", "files", "topics", "tags", "files", "hints", "requirements", "next", "state", "version", # fmt: on @@ -291,6 +291,10 @@ def _get_initial_challenge_payload(self, ignore: Tuple[str] = ()) -> Dict: if "connection_info" not in ignore: challenge_payload["connection_info"] = challenge.get("connection_info", None) + if "logic" not in ignore: + if challenge.get("logic"): + challenge_payload["logic"] = challenge.get("logic") or "any" + if "extra" not in ignore: challenge_payload = {**challenge_payload, **challenge.get("extra", {})} @@ -552,13 +556,16 @@ def _normalize_challenge(self, challenge_data: Dict[str, Any]): "type", "state", "connection_info", + "logic", ] for key in copy_keys: if key in challenge_data: challenge[key] = challenge_data[key] challenge["description"] = challenge_data["description"].strip().replace("\r\n", "\n").replace("\t", "") - challenge["attribution"] = challenge_data.get("attribution", "").strip().replace("\r\n", "\n").replace("\t", "") + challenge["attribution"] = challenge_data.get("attribution", "") + if challenge["attribution"]: + challenge["attribution"] = challenge["attribution"].strip().replace("\r\n", "\n").replace("\t", "") challenge["attempts"] = challenge_data["max_attempts"] for key in ["initial", "decay", "minimum"]: @@ -685,6 +692,8 @@ def sync(self, ignore: Tuple[str] = ()) -> None: # Update simple properties r = self.api.patch(f"/api/v1/challenges/{self.challenge_id}", json=challenge_payload) + if r.ok is False: + click.secho(f"Failed to sync challenge: ({r.status_code}) {r.text}", fg="red") r.raise_for_status() # Update flags @@ -809,6 +818,8 @@ def create(self, ignore: Tuple[str] = ()) -> None: challenge_payload[p] = "" r = self.api.post("/api/v1/challenges", json=challenge_payload) + if r.ok is False: + click.secho(f"Failed to create challenge: ({r.status_code}) {r.text}", fg="red") r.raise_for_status() self.challenge_id = r.json()["data"]["id"] diff --git a/ctfcli/spec/challenge-example.yml b/ctfcli/spec/challenge-example.yml index f8fe07c..ecee287 100644 --- a/ctfcli/spec/challenge-example.yml +++ b/ctfcli/spec/challenge-example.yml @@ -51,6 +51,12 @@ healthcheck: writeup/exploit.sh # Can be removed if unused attempts: 5 +# Specifies flag processing behavior of challenge +# Accept any flag (default): any +# Require all flags to be provided: all +# Require all team members to submit any flag: team +logic: any + # Flags specify answers that your challenge use. You should generally provide at least one. # Can be removed if unused # Accepts strings or dictionaries of CTFd API data