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
2026-09-25 14:04:33 +08:00
parent c35b23a37f
commit c6fd9ec268
1317 changed files with 306586 additions and 128 deletions
+25
View File
@@ -0,0 +1,25 @@
FROM public.ecr.aws/docker/library/ubuntu:24.04
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 gcc libgmp3-dev python3 python3-pip
RUN echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
RUN service ssh start
WORKDIR /ctf/ticketer
COPY src/ src/
COPY start.sh .
RUN pip install fastecdsa --break-system-packages
RUN touch flag.txt
RUN chmod +x start.sh
CMD ./start.sh
# docker build --build-arg "PASSWORD=123" -t ticketer .
+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:
ticketer:
container_name: ticketer_container
hostname: ticketer
restart: always
build:
context: .
args:
- PASSWORD=$PASSWORD_14000
volumes:
- ../receiver/flags/ticketer.txt:/flag.txt:ro
- ../utils/bashrc:/root/.bashrc:ro
- ../utils/preexec.sh:/root/.preexec.sh:ro
ports:
- "14000:5000"
- "14022:22"
extra_hosts:
- "host.docker.internal:host-gateway"
+1
View File
@@ -0,0 +1 @@
PLACEHOLDER
+105
View File
@@ -0,0 +1,105 @@
import random
import time
import pok
FLAG = open("../flag.txt", "r").read()
TICKET_PRICE = {
"normal": 2,
"premium": 5,
"VIP": 10,
"FLAG": 100,
}
TICKET_VALUE = {
"normal": 1,
"premium": 3,
"VIP": 5,
}
class Ticketer:
def __init__(self, username: str):
pk, vk = pok.keygen(username.encode())
self.username = username
self.proving_key = pk
self.verifying_key = vk
self.balance = 10
self.used_tickets = []
self.issued_tickets = {}
def buy_ticket(self, ticket_type):
price = TICKET_PRICE.get(ticket_type)
if self.balance >= price:
self.balance -= price
new_ticket_id = int.to_bytes(random.getrandbits(128), 16, "big").hex()
self.issued_tickets[new_ticket_id] = ticket_type
ticket = pok.prove(new_ticket_id.encode(), self.proving_key)
if ticket_type == "FLAG":
print(FLAG)
else:
print(f"Your ticket: {new_ticket_id+ticket.hex()}")
else:
print("[ERROR] Insufficient balance")
def refund_ticket(self):
try:
ticket = input("Input ticket: ")
ticket_id, proof = ticket[:32], bytes.fromhex(ticket[32:])
if pok.verify(ticket_id.encode(), self.verifying_key, proof):
if ticket_id in self.issued_tickets and ticket not in self.used_tickets:
ticket_type = self.issued_tickets.get(ticket_id)
value = TICKET_VALUE.get(ticket_type, 0)
self.balance += value
self.used_tickets.append(ticket)
print("Ticket succesfully refunded!")
else:
print("[ERROR] Invalid ticket")
else:
print("[ERROR] Invalid ticket")
except Exception:
print("[ERROR] Invalid ticket")
def checker_menu(self):
self.balance = 150
def menu(self):
print("[1] Buy normal ticket ($2)")
print("[2] Buy premium ticket ($5)")
print("[3] Buy VIP ticket ($10)")
print("[4] Buy FLAG ticket ($100)")
print("[5] Refund ticket")
print("[6] Exit")
print("")
print(f"Your balance: ${self.balance}")
def main():
username = input("Username: ")
ticketer = Ticketer(username)
while True:
time.sleep(0.5)
ticketer.menu()
option = input("$> ")
if option == "1":
ticketer.buy_ticket("normal")
elif option == "2":
ticketer.buy_ticket("premium")
elif option == "3":
ticketer.buy_ticket("VIP")
elif option == "4":
ticketer.buy_ticket("FLAG")
elif option == "5":
ticketer.refund_ticket()
elif option == FLAG:
ticketer.checker_menu()
else:
break
if __name__ == "__main__":
main()
+28
View File
@@ -0,0 +1,28 @@
import hashlib
from fastecdsa import curve, ecdsa
def H(m):
return decode(hashlib.shake_256(m).digest(128))
def encode(x):
return int.to_bytes(x, 32, 'big')
def decode(b):
return int.from_bytes(b, 'big') % curve.secp256k1.q
def keygen(seed):
pk = H(seed)
vk = pk * curve.secp256k1.G
return pk, vk
def prove(m, pk):
u, z = ecdsa.sign(m, pk, curve.secp256k1)
return encode(u)+encode(z)
def verify(m, vk, proof):
if len(proof) != 64:
return False
u, z = decode(proof[:32]), decode(proof[32:])
return ecdsa.verify((u, z), m, vk, curve.secp256k1)
+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"