feat: challenge registry-driven platform + XVI/XVII imports + admin toggle + domain rename

- Rename repo/domain: attack-defense-platform / attackdefense.imrnes.team (all refs replaced)
- challenge_registry.json: single source of truth (28 challs across gemastik18/xvi/xvii)
- teams.py: registry-driven CHALLENGES, set_challenge_enabled, sync_challenge_runtime
  (apply enable/disable to live teams: build/up or stop/remove + receiver restart)
- compose_gen.py: render per-team compose from canonical per-challenge templates
  (image reuse, per-team ports 30xxx, flag mounts, passwords)
- gen_canonical_composes.py: canonical docker-compose.yml for all services
- import_new_challenges.py: import XVI/XVII services + EOL base image fixes
  (debian:buster→bookworm, node:14→20, python:3.7-slim→3.11)
- receiver: xvi package (10 checkers) + xvii package (12 generic checkers),
  Challenge base reads PASSWORD_<team_port> from env; gen_receiver_main.py
  generates per-team main.py from registry
- main.py: /api/challenges returns full registry; PATCH /api/challenges/<name>
  toggles enabled + applies to live teams
- index.html: 🏗️ Challenge Manager tab (toggle per challenge, grouped by set)
- SLA bonus now dynamic (all enabled challenges, not hardcoded 6)
This commit is contained in:
MythEclipse committed 2026-09-25 14:04:33 +08:00
1 parent c35b23a37f
commit c6fd9ec268
1317 files changed
+306586 -128

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
FROM python:3.11-slim-bookworm
ARG PASSWORD
ENV DEBIAN_FRONTEND noninteractive
RUN echo root:${PASSWORD} | chpasswd
COPY apt-insecure.conf /etc/apt/apt.conf.d/99gemastik-insecure
RUN apt-get -o Acquire::AllowInsecureRepositories=true update && apt-get install -y openssh-server curl socat nano
RUN echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
RUN service ssh start
WORKDIR /ctf/gift-card
COPY src/ src/
COPY start.sh .
RUN chmod +x start.sh
CMD ./start.sh
+3
View File
@@ -0,0 +1,3 @@
Acquire::AllowInsecureRepositories "true";
Acquire::AllowDowngradeToInsecureRepositories "true";
Apt::Get::AllowUnauthenticated "true";
+18
View File
@@ -0,0 +1,18 @@
services:
gift-card:
container_name: gift-card_container
hostname: gift-card
restart: always
build:
context: .
args:
- PASSWORD=$PASSWORD_21000
volumes:
- ../receiver/flags/gift-card.txt:/ctf/gift-card/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "21000:5000"
- "21022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
+1
View File
@@ -0,0 +1 @@
PLACEHOLDER
+126
View File
@@ -0,0 +1,126 @@
import random
import signal
FLAG = open("../flag.txt").read()
class GiftShop:
# TODO: design better giftcard generator and validator
def __init__(self, name: str) -> None:
self.mod = lambda x: x % 256
self.Hm = self.mod(sum(name.encode()))
def generate_giftcard(self) -> str:
r = random.randbytes(15)
s = int.to_bytes(self.mod(self.Hm - self.mod(sum(r))))
signature = r + s
return signature.hex()
def validate_giftcard(self, giftcard: str) -> bool:
signature = bytes.fromhex(giftcard)
return len(signature) == 16 and self.mod(sum(signature)) == self.Hm
class Challenge:
def __init__(self, name):
self.name = name
self.balance = 100
self.items = {
"bread": {"price": 33, "callable": self.__get_bread},
"giftcard": {"price": 50, "callable": self.__get_giftcard},
"flag": {"price": 420, "callable": self.__get_flag},
}
self.redeemed_giftcard = set()
self.gift_shop = GiftShop(self.name)
def greet(self):
msg = ""
msg += f"\nWelcome, {self.name}!"
msg += f"\nOption:"
msg += f"\n [1] Check available items"
msg += f"\n [2] Buy item"
msg += f"\n [3] Redeem giftcard"
msg += f"\n [9] Input admin code"
print(msg)
def __get_bread(self):
return chr(0x1F35E)
def __get_giftcard(self):
return self.gift_shop.generate_giftcard()
def __get_flag(self):
return FLAG
def check_available(self):
msg = ""
msg += f"\nAvailable items:"
for item_name, item_info in self.items.items():
item_price = item_info["price"]
msg += f"\n [+] {item_name} (${item_price})"
return msg
def buy_item(self, item):
if item not in self.items.keys():
return f"Item {item} is not available"
if self.balance < self.items[item]["price"]:
return f"Insufficient balance"
self.balance -= self.items[item]["price"]
return self.items[item]["callable"]()
def redeem_giftcard(self, giftcard):
if giftcard in self.redeemed_giftcard:
return "Your giftcard is already used"
if not self.gift_shop.validate_giftcard(giftcard):
return "Your giftcard is invalid"
value = random.randint(30, 60)
self.balance += value
self.redeemed_giftcard.add(giftcard)
return f"You got ${value} from the giftcard"
def admin_code(self, code):
if code != FLAG:
return "Incorrect code"
self.balance += self.items["flag"]["price"]
return "Correct code"
def user_input(s):
inp = input(s).strip()
assert len(inp) < 256
return inp
def main():
name = user_input("Name: ")
challenge = Challenge(name)
challenge.greet()
while True:
print(f"\nCurrent balance: ${challenge.balance}")
match int(user_input("> ")):
case 1:
print(challenge.check_available())
case 2:
item = user_input("Item: ")
print(challenge.buy_item(item))
case 3:
giftcard = user_input("Giftcard: ")
print(challenge.redeem_giftcard(giftcard))
case 9:
code = user_input("Code: ")
print(challenge.admin_code(code))
case _:
print("Invalid option")
break
if __name__ == "__main__":
signal.alarm(60)
main()
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
/usr/sbin/sshd -D &
cd src
socat TCP-LISTEN:5000,reuseaddr,fork EXEC:"python3 -u main.py"