try checker

This commit is contained in:
rafliher
2025-10-25 14:48:20 +07:00
parent 41a170543b
commit 0d216d77eb
10 changed files with 457 additions and 480 deletions
+250
View File
@@ -0,0 +1,250 @@
import io
import re
import time
import json
import base64
import random
import string
import logging
import subprocess
from typing import Optional
import requests
from PIL import Image # pillow for tiny PNG generation
from .Challenge import Challenge
class Blogspot(Challenge):
"""
SLA checker for the provided Flask challenge app.
Assumptions / Defaults (override as needed):
- Service listens on self.port (inherited)
- Container name is 'chal_app' (override via self.container_name)
- Flag lives in '/app/flag.txt' inside container
- Login flow at /login, register at /register
- Create post at /create, view post at /post/<id>, uploads at /uploads/<file>
"""
flag_location = 'flags/blogspot.txt' # Host copy (used by your orchestrator)
history_location = 'history/blogspot.txt'
container_flag_path = '/flag.txt'
container_name = 'blogpost_container' # <-- set to your actual container name
# Heuristics to recognize ExifTool output
_exif_markers = (
'ExifTool Version Number',
'File Name',
'File Size',
'MIME Type',
'File Type',
)
def _make_logger(self):
if not hasattr(self, 'logger') or self.logger is None:
self.logger = logging.getLogger(self.__class__.__name__)
if not self.logger.handlers:
h = logging.StreamHandler()
h.setFormatter(logging.Formatter('[%(levelname)s] %(message)s'))
self.logger.addHandler(h)
self.logger.setLevel(logging.INFO)
# --- Flag distribution hook (optional, mirrors your example) ---
def distribute(self, flag: str) -> bool:
"""
Writes/records the current flag on the host. Your infra may separately
mount/copy it into the container; this class *also* verifies existence
inside the container during .check().
"""
self._make_logger()
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f"Flag '{flag}' written to {self.flag_location}")
return True
except Exception as e:
self.logger.error(f"Failed writing host flag: {e}")
return False
# --- Helpers ---
def _gen_username(self) -> str:
return "user_" + ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
def _gen_password(self) -> str:
return "Pw!" + ''.join(random.choices(string.ascii_letters + string.digits, k=10))
def _tiny_png_bytes(self) -> bytes:
"""
Generate a minimal valid PNG in-memory to trigger exiftool.
"""
img = Image.new("RGB", (2, 2), (123, 200, 50))
buf = io.BytesIO()
img.save(buf, format="PNG")
return buf.getvalue()
def _docker_exec(self, args: list[str], timeout: int = 10) -> subprocess.CompletedProcess:
"""
Run `docker exec` into the challenge container.
"""
return subprocess.run(
["docker", "exec", self.container_name] + args,
capture_output=True,
text=True,
timeout=timeout
)
# --- SLA core ---
def check(self) -> bool:
self._make_logger()
base_url = f"http://localhost:{self.port}"
s = requests.Session()
# 0) Liveness: login page should be reachable (no auth needed)
login_url = base_url + "/login"
self.logger.info(f"[1/7] Checking liveness at {login_url} ...")
try:
r = s.get(login_url, timeout=10)
assert r.status_code == 200, f"Login page HTTP {r.status_code}"
self.logger.info(" ✓ Login page reachable")
except Exception as e:
self.logger.error(f" ✗ Liveness check failed: {e}")
return False
# 1) Register a fresh user
self.logger.info("[2/7] Registering a fresh user ...")
username = self._gen_username()
password = self._gen_password()
try:
r = s.post(
base_url + "/register",
data={"username": username, "password": password},
allow_redirects=False,
timeout=10,
)
# Flask typically redirects to /login on success (302)
assert r.status_code in (200, 302, 303), f"Register HTTP {r.status_code}"
self.logger.info(f" ✓ Registered as {username}")
except Exception as e:
self.logger.error(f" ✗ Registration failed: {e}")
return False
# 2) Log in
self.logger.info("[3/7] Logging in ...")
try:
r = s.post(
base_url + "/login",
data={"username": username, "password": password},
allow_redirects=True,
timeout=10,
)
# Successful login should redirect or render index; ensure not bounced back to /login with "Invalid credentials"
assert r.status_code == 200, f"Login HTTP {r.status_code}"
assert "/login" not in r.url or "Invalid credentials" not in r.text
self.logger.info(" ✓ Logged in")
except Exception as e:
self.logger.error(f" ✗ Login failed: {e}")
return False
# 3) Create post with image upload (PNG) to trigger exiftool + DB write
self.logger.info("[4/7] Creating a post with image upload ...")
title = "SLA Post " + ''.join(random.choices(string.ascii_letters, k=6))
content = "hello_from_sla_checker"
png_bytes = self._tiny_png_bytes()
files = {
"image": ("probe.png", png_bytes, "image/png")
}
data = {"title": title, "content": content}
try:
r = s.post(base_url + "/create", files=files, data=data, allow_redirects=True, timeout=20)
assert r.status_code == 200, f"Create returned HTTP {r.status_code}"
self.logger.info(" ✓ Post created")
except Exception as e:
self.logger.error(f" ✗ Create post failed: {e}")
return False
# 4) Discover the newly created post id by scraping links like /post/<id> from index
self.logger.info("[5/7] Resolving new post id from index ...")
try:
r = s.get(base_url + "/", timeout=10)
assert r.status_code == 200, f"Index HTTP {r.status_code}"
# Find the most recent /post/<id> that likely corresponds to our post title
# First try to find blocks containing our title then pull an id:
post_id: Optional[int] = None
# Prefer links sitting near our title
title_blocks = [m.start() for m in re.finditer(re.escape(title), r.text)]
if title_blocks:
# Search backward/forward around the title occurrences for a /post/<num> link
for pos in title_blocks:
window = r.text[max(0, pos-1000):pos+1000]
m = re.search(r'/post/(\d+)', window)
if m:
post_id = int(m.group(1))
break
# Fallback: pick the first /post/<id> in the page
if post_id is None:
m = re.search(r'/post/(\d+)', r.text)
assert m, "No /post/<id> link found on index"
post_id = int(m.group(1))
self.logger.info(f" ✓ Resolved post id = {post_id}")
except Exception as e:
self.logger.error(f" ✗ Could not resolve post id: {e}")
return False
# 5) Visit the post page, confirm it renders and contains our title/content.
self.logger.info("[6/7] Visiting the post page ...")
try:
vp = s.get(base_url + f"/post/{post_id}", timeout=10)
assert vp.status_code == 200, f"Post page HTTP {vp.status_code}"
assert title in vp.text, "Post title not present on view page"
assert content in vp.text, "Post content not present on view page"
self.logger.info(" ✓ Post page renders with our content")
except Exception as e:
self.logger.error(f" ✗ View post failed: {e}")
return False
# 6) Locate the uploaded file link on the post page, then fetch the .meta file via /uploads/<file>.meta
self.logger.info("[7/7] Verifying ExifTool metadata persisted & accessible ...")
try:
# Find something like /uploads/<sha256>.png (or .jpg/.jpeg/.bmp)
m = re.search(r'/uploads/([A-Za-z0-9_.-]+\.(?:png|jpg|jpeg|bmp))', vp.text, flags=re.IGNORECASE)
assert m, "No uploaded image link found on post page"
image_name = m.group(1)
# The server stores metadata in a sibling file with .meta suffix; it is served by the same /uploads route.
meta_url = base_url + f"/uploads/{image_name}.meta"
mr = s.get(meta_url, timeout=10)
assert mr.status_code == 200, f"Meta file HTTP {mr.status_code}"
meta_text = mr.text.strip()
# Heuristic: expect at least one ExifTool-like marker
assert any(tag in meta_text for tag in self._exif_markers), "Meta file does not look like ExifTool output"
self.logger.info(" ✓ Exif metadata present and readable")
except Exception as e:
self.logger.error(f" ✗ Metadata verification failed: {e}")
return False
# 7) Flag existence in container (do not fail SLA if only host copy exists but container is missing—treat as warning or policy-driven)
try:
proc = self._docker_exec(["/bin/sh", "-lc", f"test -f {self.container_flag_path} && cat {self.container_flag_path} || echo __MISSING__"])
out = (proc.stdout or "").strip()
if "__MISSING__" in out or proc.returncode not in (0,):
self.logger.warning("⚠ Flag file missing inside container")
else:
self.logger.info(" ✓ Container flag present")
# Optional: compare with host flag if present
try:
with open(self.flag_location, "r") as f:
host_flag = f.read().strip()
if host_flag and host_flag == out:
self.logger.info(" ✓ Host and container flags match")
else:
self.logger.warning("⚠ Host/container flag mismatch (may be expected if rotated separately)")
except FileNotFoundError:
self.logger.warning("⚠ Host flag not found; skipping comparison")
except Exception as e:
# Non-fatal: you can tune this to fail the round if flag is mandatory.
self.logger.warning(f"Flag existence check encountered an issue: {e}")
self.logger.info("SLA check passed ✅")
return True
+224
View File
@@ -0,0 +1,224 @@
import io
import re
import random
import string
import logging
import subprocess
from typing import Optional
import requests
from PIL import Image
from .Challenge import Challenge
class CDN(Challenge):
"""
SLA checker for the Flask app.
Enforced rules (HARD FAIL if violated):
1) /flag.txt exists in container AND equals host copy at flags/webapp.txt
2) Image metadata generation works via ExifTool and is retrievable from /post/<id>?meta=1
3) exiftool is installed in the container and is the provider of that metadata
"""
# Paths & infra knobs
flag_location = 'flags/cdn.txt' # host copy (written by your orchestrator)
history_location = 'history/cdn.txt'
container_flag_path = '/flag.txt'
container_name = 'chal_app' # adjust if your container name differs
# Canonical ExifTool markers (NO app fallbacks allowed)
_exif_must_have = (
'File Name',
'Date Created',
)
# --- Flag distribution hook (optional, mirrors your example) ---
def distribute(self, flag: str) -> bool:
"""
Writes/records the current flag on the host. Your infra may separately
mount/copy it into the container; this class *also* verifies existence
inside the container during .check().
"""
self._make_logger()
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f"Flag '{flag}' written to {self.flag_location}")
return True
except Exception as e:
self.logger.error(f"Failed writing host flag: {e}")
return False
def _make_logger(self):
if not hasattr(self, 'logger') or self.logger is None:
self.logger = logging.getLogger(self.__class__.__name__)
if not self.logger.handlers:
h = logging.StreamHandler()
h.setFormatter(logging.Formatter('[%(levelname)s] %(message)s'))
self.logger.addHandler(h)
self.logger.setLevel(logging.INFO)
# --- Helpers ---
def _gen_username(self) -> str:
return "user_" + ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
def _gen_password(self) -> str:
return "Pw!" + ''.join(random.choices(string.ascii_letters + string.digits, k=10))
def _tiny_png_bytes(self) -> bytes:
img = Image.new("RGB", (2, 2), (123, 200, 50))
buf = io.BytesIO()
img.save(buf, format="PNG")
return buf.getvalue()
def _docker_exec(self, args: list[str], timeout: int = 10) -> subprocess.CompletedProcess:
return subprocess.run(
["docker", "exec", self.container_name] + args,
capture_output=True, text=True, timeout=timeout
)
# --- SLA core ---
def check(self) -> bool:
self._make_logger()
base_url = f"http://localhost:{self.port}"
s = requests.Session()
# [1/9] Liveness
self.logger.info("[1/9] Checking liveness (/login) ...")
try:
r = s.get(base_url + "/login", timeout=10)
assert r.status_code == 200, f"/login HTTP {r.status_code}"
self.logger.info(" ✓ Login page reachable")
except Exception as e:
self.logger.error(f" ✗ Liveness failed: {e}")
return False
# [2/9] exiftool MUST exist in container
self.logger.info("[2/9] Verifying exiftool installed in container ...")
try:
proc = self._docker_exec(["/bin/sh", "-lc", "command -v exiftool && exiftool -ver"])
out = (proc.stdout or "").strip()
assert proc.returncode == 0 and out, "exiftool not found or not runnable"
self.logger.info(f" ✓ exiftool OK (version: {out.splitlines()[-1]})")
except Exception as e:
self.logger.error(f" ✗ exiftool check failed: {e}")
return False
# [3/9] flag.txt MUST exist and MUST equal host copy
self.logger.info("[3/9] Verifying /flag.txt exists and matches host copy ...")
try:
# read container flag
proc = self._docker_exec(["/bin/sh", "-lc", f"test -f {self.container_flag_path} && cat {self.container_flag_path}"])
assert proc.returncode == 0, "flag.txt missing in container"
container_flag = (proc.stdout or "").strip()
assert container_flag, "flag.txt empty"
# read host copy
with open(self.flag_location, "r", encoding="utf-8") as fh:
host_flag = fh.read().strip()
# exact match
assert container_flag == host_flag, "flag.txt content changed/mismatch"
self.logger.info(" ✓ flag.txt present and unchanged")
except Exception as e:
self.logger.error(f" ✗ Flag verification failed: {e}")
return False
# [4/9] Register
self.logger.info("[4/9] Registering a fresh user ...")
username = self._gen_username()
password = self._gen_password()
try:
r = s.post(base_url + "/register",
data={"username": username, "password": password},
allow_redirects=True, timeout=10)
assert 200 <= r.status_code < 400, f"Register HTTP {r.status_code}"
self.logger.info(f" ✓ Registered as {username}")
except Exception as e:
self.logger.error(f" ✗ Registration failed: {e}")
return False
# [5/9] Login
self.logger.info("[5/9] Logging in ...")
try:
r = s.post(base_url + "/login",
data={"username": username, "password": password},
allow_redirects=True, timeout=10)
assert r.status_code == 200, f"Login HTTP {r.status_code}"
assert "Invalid credentials" not in r.text, "Invalid credentials"
self.logger.info(" ✓ Logged in")
except Exception as e:
self.logger.error(f" ✗ Login failed: {e}")
return False
# [6/9] Upload tiny PNG (triggers exiftool path in app)
self.logger.info("[6/9] Uploading PNG to generate metadata ...")
title = "SLA Post " + ''.join(random.choices(string.ascii_letters, k=6))
files = {"image": ("probe.png", self._tiny_png_bytes(), "image/png")}
data = {"title": title}
try:
r = s.post(base_url + "/upload", files=files, data=data,
allow_redirects=True, timeout=20)
assert 200 <= r.status_code < 400, f"Upload HTTP {r.status_code}"
self.logger.info(" ✓ Upload OK")
except Exception as e:
self.logger.error(f" ✗ Upload failed: {e}")
return False
# [7/9] Find newest post id (prefer near our title)
self.logger.info("[7/9] Resolving new post id from /gallery ...")
try:
r = s.get(base_url + "/gallery", timeout=10)
if r.status_code != 200:
r = s.get(base_url + "/", timeout=10)
assert r.status_code == 200, f"Index HTTP {r.status_code}"
post_id: Optional[int] = None
for m in re.finditer(re.escape(title), r.text):
window = r.text[max(0, m.start()-1000): m.start()+1000]
mm = re.search(r'/post/(\d+)', window)
if mm:
post_id = int(mm.group(1)); break
if post_id is None:
ids = re.findall(r'/post/(\d+)', r.text)
assert ids, "No /post/<id> links found"
post_id = max(map(int, ids))
self.logger.info(f" ✓ Post id = {post_id}")
except Exception as e:
self.logger.error(f" ✗ Post discovery failed: {e}")
return False
# [8/9] Post renders
self.logger.info("[8/9] Verifying post page renders ...")
try:
vp = s.get(base_url + f"/post/{post_id}", timeout=10)
assert vp.status_code == 200, f"Post HTTP {vp.status_code}"
assert title in vp.text, "Post title missing"
self.logger.info(" ✓ Post page OK")
except Exception as e:
self.logger.error(f" ✗ View post failed: {e}")
return False
# [9/9] Metadata must be real ExifTool output (no fallbacks)
self.logger.info("[9/9] Verifying metadata via /post/<id> (ExifTool) ...")
try:
mr = s.get(base_url + f"/post/{post_id}", timeout=10)
assert mr.status_code == 200, f"Meta HTTP {mr.status_code}"
meta = (mr.text or "").strip()
assert meta, "Empty metadata"
# refuse app fallbacks
assert "no-metadata" not in meta and "exif_err:" not in meta, "App fallback metadata detected"
# must contain canonical ExifTool keys
assert all(k in meta for k in self._exif_must_have), "Metadata is not ExifTool output"
self.logger.info(" ✓ Metadata present and produced by ExifTool")
except Exception as e:
self.logger.error(f" ✗ Metadata verification failed: {e}")
return False
self.logger.info("SLA check passed ✅")
return True
-1
View File
@@ -4,7 +4,6 @@ import string
from config import get_settings
class Challenge(object):
name = __name__
settings = get_settings()
@@ -1,182 +1,182 @@
from .Challenge import Challenge
import subprocess
import time
import re
import os
class PailierBingo(Challenge):
flag_location = 'flags/phew.txt'
history_location = 'history/phew.txt'
_CONTAINER = "pailier_container" # <-- set to your container name
_SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"]
_HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
def _read_container_flag(self) -> str:
out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"],
capture_output=True, text=True)
if out.returncode != 0 or not out.stdout.strip():
raise FileNotFoundError("Flag not found in container (/flag.txt)")
return out.stdout.strip()
def _spawn(self):
return subprocess.Popen(
self._SERVICE_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=0,
)
def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000):
start = time.time()
buf = []
r = proc.stdout.read
while True:
if time.time() - start > timeout:
tail = ''.join(buf)[-500:]
raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}")
ch = r(1)
if ch == "" and proc.poll() is not None:
raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}")
buf.append(ch)
if len(buf) > max_bytes:
raise RuntimeError("Exceeded max read size")
if token in "".join(buf):
return "".join(buf)
def _send_line(self, proc, s: str):
proc.stdin.write(s + "\n")
proc.stdin.flush()
def _expect_hex_field(self, text: str, label: str) -> str:
"""
Extracts 'ct : <hex>' or 'pt : <hex>' (label = 'ct' or 'pt')
The service prints with spaces around the colon: 'ct : <hex>'
"""
m = re.search(rf"{re.escape(label)}\s*:\s*([0-9a-fA-F]+)", text)
assert m, f"Missing '{label}' in output. Tail:\n{text[-400:]}"
hx = m.group(1)
assert self._HEX_RE.match(hx), f"{label} is not hex"
return hx
def distribute(self, flag):
try:
os.makedirs(os.path.dirname(self.flag_location), exist_ok=True)
with open(self.flag_location, 'w') as f:
f.write(flag)
os.makedirs(os.path.dirname(self.history_location), exist_ok=True)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = self._read_container_flag()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('[ok] flag parity (phew)')
proc = self._spawn()
def menu():
self._read_until(proc, "> ", timeout=5.0)
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
pt_hex = "414243444546" # "ABCDEF"
self._send_line(proc, pt_hex)
out = self._read_until(proc, "\n", timeout=3.0)
ct_hex = self._expect_hex_field(out, "ct")
self.logger.info("[ok] encrypt(1) produced hex CT")
menu()
self._send_line(proc, "3")
self._read_until(proc, "ct (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, ct_hex)
out_dec = self._read_until(proc, "\n", timeout=3.0)
pt_back_hex = self._expect_hex_field(out_dec, "pt")
# Normalize leading zeros for comparison
def norm(h): return (h.lstrip("0") or "0").lower()
assert norm(pt_back_hex) == norm(pt_hex), f"decrypt(encrypt(pt)) mismatch: {pt_back_hex} != {pt_hex}"
self.logger.info("[ok] decrypt(3) round-trip")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
same_pt = "01" * 8
self._send_line(proc, same_pt)
out_a = self._read_until(proc, "\n", timeout=3.0)
ct_a = self._expect_hex_field(out_a, "ct")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, same_pt)
out_b = self._read_until(proc, "\n", timeout=3.0)
ct_b = self._expect_hex_field(out_b, "ct")
assert ct_a.lower() != ct_b.lower(), "Encryption appears deterministic for same plaintext"
self.logger.info("[ok] encrypt(1) randomness")
menu()
self._send_line(proc, "2")
out2a = self._read_until(proc, "\n", timeout=3.0)
bingo1 = self._expect_hex_field(out2a, "ct")
menu()
self._send_line(proc, "2")
out2b = self._read_until(proc, "\n", timeout=3.0)
bingo2 = self._expect_hex_field(out2b, "ct")
assert bingo1.lower() != bingo2.lower(), "Bingo ciphertexts reused randomness"
self.logger.info("[ok] bingo(2) randomness")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
pt_hex2 = "deadbeef"
self._send_line(proc, pt_hex2)
out_enc2 = self._read_until(proc, "\n", timeout=3.0)
ct2 = self._expect_hex_field(out_enc2, "ct")
menu()
self._send_line(proc, "3")
self._read_until(proc, "ct (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, ct2)
out_dec2 = self._read_until(proc, "\n", timeout=3.0)
pt2_back = self._expect_hex_field(out_dec2, "pt")
assert norm(pt2_back) == norm(pt_hex2), f"decrypt(encrypt(pt2)) mismatch: {pt2_back} != {pt_hex2}"
self.logger.info("[ok] decrypt(3) round-trip (case 2)")
menu()
self._send_line(proc, "4")
try:
proc.wait(timeout=2.0)
except subprocess.TimeoutExpired:
proc.kill()
raise AssertionError("Program did not exit after option 4")
self.logger.info("[ok] service exit on 4")
self.logger.info('Check passed for phew')
return True
except Exception as e:
self.logger.error(f'Could not check phew: {e}')
return False
from .Challenge import Challenge
import subprocess
import time
import re
import os
class Phew(Challenge):
flag_location = 'flags/phew.txt'
history_location = 'history/phew.txt'
_CONTAINER = "pailier_container" # <-- set to your container name
_SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"]
_HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
def _read_container_flag(self) -> str:
out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"],
capture_output=True, text=True)
if out.returncode != 0 or not out.stdout.strip():
raise FileNotFoundError("Flag not found in container (/flag.txt)")
return out.stdout.strip()
def _spawn(self):
return subprocess.Popen(
self._SERVICE_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=0,
)
def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000):
start = time.time()
buf = []
r = proc.stdout.read
while True:
if time.time() - start > timeout:
tail = ''.join(buf)[-500:]
raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}")
ch = r(1)
if ch == "" and proc.poll() is not None:
raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}")
buf.append(ch)
if len(buf) > max_bytes:
raise RuntimeError("Exceeded max read size")
if token in "".join(buf):
return "".join(buf)
def _send_line(self, proc, s: str):
proc.stdin.write(s + "\n")
proc.stdin.flush()
def _expect_hex_field(self, text: str, label: str) -> str:
"""
Extracts 'ct : <hex>' or 'pt : <hex>' (label = 'ct' or 'pt')
The service prints with spaces around the colon: 'ct : <hex>'
"""
m = re.search(rf"{re.escape(label)}\s*:\s*([0-9a-fA-F]+)", text)
assert m, f"Missing '{label}' in output. Tail:\n{text[-400:]}"
hx = m.group(1)
assert self._HEX_RE.match(hx), f"{label} is not hex"
return hx
def distribute(self, flag):
try:
os.makedirs(os.path.dirname(self.flag_location), exist_ok=True)
with open(self.flag_location, 'w') as f:
f.write(flag)
os.makedirs(os.path.dirname(self.history_location), exist_ok=True)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = self._read_container_flag()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('[ok] flag parity (phew)')
proc = self._spawn()
def menu():
self._read_until(proc, "> ", timeout=5.0)
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
pt_hex = "414243444546" # "ABCDEF"
self._send_line(proc, pt_hex)
out = self._read_until(proc, "\n", timeout=3.0)
ct_hex = self._expect_hex_field(out, "ct")
self.logger.info("[ok] encrypt(1) produced hex CT")
menu()
self._send_line(proc, "3")
self._read_until(proc, "ct (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, ct_hex)
out_dec = self._read_until(proc, "\n", timeout=3.0)
pt_back_hex = self._expect_hex_field(out_dec, "pt")
# Normalize leading zeros for comparison
def norm(h): return (h.lstrip("0") or "0").lower()
assert norm(pt_back_hex) == norm(pt_hex), f"decrypt(encrypt(pt)) mismatch: {pt_back_hex} != {pt_hex}"
self.logger.info("[ok] decrypt(3) round-trip")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
same_pt = "01" * 8
self._send_line(proc, same_pt)
out_a = self._read_until(proc, "\n", timeout=3.0)
ct_a = self._expect_hex_field(out_a, "ct")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, same_pt)
out_b = self._read_until(proc, "\n", timeout=3.0)
ct_b = self._expect_hex_field(out_b, "ct")
assert ct_a.lower() != ct_b.lower(), "Encryption appears deterministic for same plaintext"
self.logger.info("[ok] encrypt(1) randomness")
menu()
self._send_line(proc, "2")
out2a = self._read_until(proc, "\n", timeout=3.0)
bingo1 = self._expect_hex_field(out2a, "ct")
menu()
self._send_line(proc, "2")
out2b = self._read_until(proc, "\n", timeout=3.0)
bingo2 = self._expect_hex_field(out2b, "ct")
assert bingo1.lower() != bingo2.lower(), "Bingo ciphertexts reused randomness"
self.logger.info("[ok] bingo(2) randomness")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
pt_hex2 = "deadbeef"
self._send_line(proc, pt_hex2)
out_enc2 = self._read_until(proc, "\n", timeout=3.0)
ct2 = self._expect_hex_field(out_enc2, "ct")
menu()
self._send_line(proc, "3")
self._read_until(proc, "ct (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, ct2)
out_dec2 = self._read_until(proc, "\n", timeout=3.0)
pt2_back = self._expect_hex_field(out_dec2, "pt")
assert norm(pt2_back) == norm(pt_hex2), f"decrypt(encrypt(pt2)) mismatch: {pt2_back} != {pt_hex2}"
self.logger.info("[ok] decrypt(3) round-trip (case 2)")
menu()
self._send_line(proc, "4")
try:
proc.wait(timeout=2.0)
except subprocess.TimeoutExpired:
proc.kill()
raise AssertionError("Program did not exit after option 4")
self.logger.info("[ok] service exit on 4")
self.logger.info('Check passed for phew')
return True
except Exception as e:
self.logger.error(f'Could not check phew: {e}')
return False
@@ -1,191 +1,191 @@
from .Challenge import Challenge
import subprocess
import time
import re
import os
class Sheesh(Challenge):
flag_location = 'flags/sheesh.txt'
history_location = 'history/sheesh.txt'
_CONTAINER = "sheesh_container"
_SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"]
_HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
def _read_container_flag(self) -> str:
out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"],
capture_output=True, text=True)
if out.returncode != 0 or not out.stdout.strip():
raise FileNotFoundError("Flag not found in container (/flag.txt)")
return out.stdout.strip()
def _spawn(self):
return subprocess.Popen(
self._SERVICE_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=0,
)
def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000):
start = time.time()
buf = []
r = proc.stdout.read
while True:
if time.time() - start > timeout:
tail = ''.join(buf)[-500:]
raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}")
ch = r(1)
if ch == "" and proc.poll() is not None:
raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}")
buf.append(ch)
if len(buf) > max_bytes:
raise RuntimeError("Exceeded max read size")
if token in "".join(buf):
return "".join(buf)
def _send_line(self, proc, s: str):
proc.stdin.write(s + "\n")
proc.stdin.flush()
def _expect_hex_field(self, text: str, label: str) -> str:
m = re.search(rf"{re.escape(label)}\s*:\s*([0-9a-fA-F]+)", text)
assert m, f"Missing '{label}' in output. Tail:\n{text[-400:]}"
hx = m.group(1)
assert self._HEX_RE.match(hx), f"{label} is not hex"
return hx
def distribute(self, flag):
try:
os.makedirs(os.path.dirname(self.flag_location), exist_ok=True)
with open(self.flag_location, 'w') as f:
f.write(flag)
os.makedirs(os.path.dirname(self.history_location), exist_ok=True)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = self._read_container_flag()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('[ok] flag parity (sheesh)')
proc = self._spawn()
def menu():
self._read_until(proc, "> ", timeout=5.0)
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt: ", timeout=3.0)
pt_hex = "414243444546" # "ABCDEF"
self._send_line(proc, pt_hex)
out = self._read_until(proc, "\n\n", timeout=3.0)
ct_hex = self._expect_hex_field(out, "ct")
ct = bytes.fromhex(ct_hex)
assert len(ct) == 16 + len(bytes.fromhex(pt_hex)), "CFB ct length mismatch (IV+PT)"
iv_a = ct[:16]
self.logger.info("[ok] encrypt(1) basic")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt: ", timeout=3.0)
pt_hex2 = "00" * 8
self._send_line(proc, pt_hex2)
out2 = self._read_until(proc, "\n\n", timeout=3.0)
ct2_hex = self._expect_hex_field(out2, "ct")
ct2 = bytes.fromhex(ct2_hex)
assert len(ct2) == 16 + len(bytes.fromhex(pt_hex2)), "Second CFB ct length mismatch"
iv_b = ct2[:16]
assert iv_a != iv_b, "CFB IV appears reused"
self.logger.info("[ok] encrypt(1) IV rotates")
menu()
self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "00" * 15)
out3a = self._read_until(proc, "\n", timeout=3.0)
assert "hmmm" in out3a.lower(), "Option 3 must reject non-16-byte input (15B)"
menu()
self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "00" * 17)
out3b = self._read_until(proc, "\n", timeout=3.0)
assert "hmmm" in out3b.lower(), "Option 3 must reject non-16-byte input (17B)"
menu()
self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "11" * 16)
out3 = self._read_until(proc, "\n\n", timeout=3.0)
ct3_hex = self._expect_hex_field(out3, "ct")
ct3 = bytes.fromhex(ct3_hex)
assert len(ct3) in (16, 32), f"Unexpected CT length for option 3: {len(ct3)} (expected 16 or 32)"
if len(ct3) == 16:
self.logger.info("[ok] get third(3): vuln flavor (16-byte CT)")
else:
self.logger.info("[ok] get third(3): patched flavor (32-byte CT)")
menu()
self._send_line(proc, "3")
out4 = self._read_until(proc, "\n", timeout=3.0)
assert "sheesh" in out4.lower(), "Second call to option 3 should be rejected with 'sheesh'"
self.logger.info("[ok] get third(3) lockout")
menu()
self._send_line(proc, "2")
out5 = self._read_until(proc, "\n\n", timeout=3.0)
iv1_hex = self._expect_hex_field(out5, "iv1")
iv2_hex = self._expect_hex_field(out5, "iv2")
ct5_hex = self._expect_hex_field(out5, "ct")
assert len(bytes.fromhex(iv1_hex)) == 16, "iv1 length invalid"
assert len(bytes.fromhex(iv2_hex)) == 16, "iv2 length invalid"
assert len(bytes.fromhex(ct5_hex)) >= 16 and len(bytes.fromhex(ct5_hex)) % 16 == 0, "CBC ct invalid length"
self.logger.info("[ok] profit(2) first call")
menu()
self._send_line(proc, "2")
out6 = self._read_until(proc, "\n\n", timeout=3.0)
iv1_hex_2 = self._expect_hex_field(out6, "iv1")
iv2_hex_2 = self._expect_hex_field(out6, "iv2")
assert iv1_hex_2 != iv1_hex or iv2_hex_2 != iv2_hex, "IVs did not change between consecutive '2' calls"
self.logger.info("[ok] profit(2) fresh IVs")
menu()
self._send_line(proc, "4")
try:
proc.wait(timeout=2.0)
except subprocess.TimeoutExpired:
proc.kill()
raise AssertionError("Program did not exit after option 4")
self.logger.info("[ok] service exit on 4")
proc_alarm = self._spawn()
self._read_until(proc_alarm, "zzz", timeout=190.0) # 180s + slack
try:
proc_alarm.wait(timeout=5.0)
except subprocess.TimeoutExpired:
proc_alarm.kill()
raise AssertionError("Alarm fired but process did not exit")
self.logger.info("[ok] alarm fired ('zzz') and process self-terminated")
self.logger.info('Check passed for sheesh')
return True
except Exception as e:
self.logger.error(f'Could not check sheesh: {e}')
return False
from .Challenge import Challenge
import subprocess
import time
import re
import os
class Sheesh(Challenge):
flag_location = 'flags/sheesh.txt'
history_location = 'history/sheesh.txt'
_CONTAINER = "sheesh_container"
_SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"]
_HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
def _read_container_flag(self) -> str:
out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"],
capture_output=True, text=True)
if out.returncode != 0 or not out.stdout.strip():
raise FileNotFoundError("Flag not found in container (/flag.txt)")
return out.stdout.strip()
def _spawn(self):
return subprocess.Popen(
self._SERVICE_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=0,
)
def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000):
start = time.time()
buf = []
r = proc.stdout.read
while True:
if time.time() - start > timeout:
tail = ''.join(buf)[-500:]
raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}")
ch = r(1)
if ch == "" and proc.poll() is not None:
raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}")
buf.append(ch)
if len(buf) > max_bytes:
raise RuntimeError("Exceeded max read size")
if token in "".join(buf):
return "".join(buf)
def _send_line(self, proc, s: str):
proc.stdin.write(s + "\n")
proc.stdin.flush()
def _expect_hex_field(self, text: str, label: str) -> str:
m = re.search(rf"{re.escape(label)}\s*:\s*([0-9a-fA-F]+)", text)
assert m, f"Missing '{label}' in output. Tail:\n{text[-400:]}"
hx = m.group(1)
assert self._HEX_RE.match(hx), f"{label} is not hex"
return hx
def distribute(self, flag):
try:
os.makedirs(os.path.dirname(self.flag_location), exist_ok=True)
with open(self.flag_location, 'w') as f:
f.write(flag)
os.makedirs(os.path.dirname(self.history_location), exist_ok=True)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = self._read_container_flag()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('[ok] flag parity (sheesh)')
proc = self._spawn()
def menu():
self._read_until(proc, "> ", timeout=5.0)
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt: ", timeout=3.0)
pt_hex = "414243444546" # "ABCDEF"
self._send_line(proc, pt_hex)
out = self._read_until(proc, "\n\n", timeout=3.0)
ct_hex = self._expect_hex_field(out, "ct")
ct = bytes.fromhex(ct_hex)
assert len(ct) == 16 + len(bytes.fromhex(pt_hex)), "CFB ct length mismatch (IV+PT)"
iv_a = ct[:16]
self.logger.info("[ok] encrypt(1) basic")
menu()
self._send_line(proc, "1")
self._read_until(proc, "pt: ", timeout=3.0)
pt_hex2 = "00" * 8
self._send_line(proc, pt_hex2)
out2 = self._read_until(proc, "\n\n", timeout=3.0)
ct2_hex = self._expect_hex_field(out2, "ct")
ct2 = bytes.fromhex(ct2_hex)
assert len(ct2) == 16 + len(bytes.fromhex(pt_hex2)), "Second CFB ct length mismatch"
iv_b = ct2[:16]
assert iv_a != iv_b, "CFB IV appears reused"
self.logger.info("[ok] encrypt(1) IV rotates")
menu()
self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "00" * 15)
out3a = self._read_until(proc, "\n", timeout=3.0)
assert "hmmm" in out3a.lower(), "Option 3 must reject non-16-byte input (15B)"
menu()
self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "00" * 17)
out3b = self._read_until(proc, "\n", timeout=3.0)
assert "hmmm" in out3b.lower(), "Option 3 must reject non-16-byte input (17B)"
menu()
self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "11" * 16)
out3 = self._read_until(proc, "\n\n", timeout=3.0)
ct3_hex = self._expect_hex_field(out3, "ct")
ct3 = bytes.fromhex(ct3_hex)
assert len(ct3) in (16, 32), f"Unexpected CT length for option 3: {len(ct3)} (expected 16 or 32)"
if len(ct3) == 16:
self.logger.info("[ok] get third(3): vuln flavor (16-byte CT)")
else:
self.logger.info("[ok] get third(3): patched flavor (32-byte CT)")
menu()
self._send_line(proc, "3")
out4 = self._read_until(proc, "\n", timeout=3.0)
assert "sheesh" in out4.lower(), "Second call to option 3 should be rejected with 'sheesh'"
self.logger.info("[ok] get third(3) lockout")
menu()
self._send_line(proc, "2")
out5 = self._read_until(proc, "\n\n", timeout=3.0)
iv1_hex = self._expect_hex_field(out5, "iv1")
iv2_hex = self._expect_hex_field(out5, "iv2")
ct5_hex = self._expect_hex_field(out5, "ct")
assert len(bytes.fromhex(iv1_hex)) == 16, "iv1 length invalid"
assert len(bytes.fromhex(iv2_hex)) == 16, "iv2 length invalid"
assert len(bytes.fromhex(ct5_hex)) >= 16 and len(bytes.fromhex(ct5_hex)) % 16 == 0, "CBC ct invalid length"
self.logger.info("[ok] profit(2) first call")
menu()
self._send_line(proc, "2")
out6 = self._read_until(proc, "\n\n", timeout=3.0)
iv1_hex_2 = self._expect_hex_field(out6, "iv1")
iv2_hex_2 = self._expect_hex_field(out6, "iv2")
assert iv1_hex_2 != iv1_hex or iv2_hex_2 != iv2_hex, "IVs did not change between consecutive '2' calls"
self.logger.info("[ok] profit(2) fresh IVs")
menu()
self._send_line(proc, "4")
try:
proc.wait(timeout=2.0)
except subprocess.TimeoutExpired:
proc.kill()
raise AssertionError("Program did not exit after option 4")
self.logger.info("[ok] service exit on 4")
proc_alarm = self._spawn()
self._read_until(proc_alarm, "zzz", timeout=190.0) # 180s + slack
try:
proc_alarm.wait(timeout=5.0)
except subprocess.TimeoutExpired:
proc_alarm.kill()
raise AssertionError("Alarm fired but process did not exit")
self.logger.info("[ok] alarm fired ('zzz') and process self-terminated")
self.logger.info('Check passed for sheesh')
return True
except Exception as e:
self.logger.error(f'Could not check sheesh: {e}')
return False
-88
View File
@@ -1,88 +0,0 @@
from .Challenge import Challenge
import io
import requests
import random
import subprocess
import requests
import re
class Wanderer(Challenge):
flag_location = 'flags/wanderer.txt'
history_location = 'history/wanderer.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
session = requests.Session()
# Step 1: Check Register
register_url = f'http://localhost:{self.port}/index.php?module=user&action=register'
registration_data = {'username': 'adminwreckitchecker','password': 'adminwreckitchecker'}
response = session.post(register_url, data=registration_data)
assert "username already exists" in response.text or "Registration Success" in response.text or response.status_code == 200 , 'Function Register Failed'
# Step 2: Check Login
login_url = f'http://localhost:{self.port}/index.php?module=user&action=login'
login_data = {'username': 'adminwreckitchecker', 'password': 'adminwreckitchecker'}
response = session.post(login_url, data=login_data)
assert "Login Success" in response.text or response.status_code == 200, 'Login function failed'
# Step 3: Check UUID Not hidden
view_page_url = f'http://localhost:{self.port}/index.php?module=page&action=viewPage'
response = session.get(view_page_url)
uuid_pattern = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
found_uuids = uuid_pattern.findall(response.text)
assert found_uuids, 'UUID not found'
# Step 4: Check create sample page
sample_page_url = f'http://localhost:{self.port}/index.php?module=page&action=samplePage'
response = session.get(sample_page_url)
assert "Create Success" in response.text or response.status_code == 200, 'Create Sample Failed'
# Step 5: Check Edit HTML Functionality
edit_url_html = f'http://localhost:{self.port}/index.php?module=user&action=edit&type=html'
response = session.get(edit_url_html)
assert response.status_code == 200, 'Edit html page not accessible'
# Step 6: Check Edit JS Functionality
edit_url_js = f'http://localhost:{self.port}/index.php?module=user&action=edit&type=js'
response = session.get(edit_url_js)
assert response.status_code == 200, 'Edit js page not accessible'
# Step 7: Check Edit CSS Functionality
edit_url_css = f'http://localhost:{self.port}/index.php?module=user&action=edit&type=css'
response = session.get(edit_url_css)
assert response.status_code == 200, 'Edit css page not accessible'
# Step 8: Check flag
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run(
["docker", "exec", "wanderer_container", "cat", "/flag.txt"],
capture_output=True,
text=True
).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('Check passed for wanderer')
return True
except Exception as e:
self.logger.error(f'Could not check wanderer: {e}')
return False
+41
View File
@@ -0,0 +1,41 @@
from .Challenge import Challenge
import io
import requests
import random
import subprocess
class Warmup(Challenge):
flag_location = 'flags/warmup.txt'
history_location = 'history/warmup.txt'
def distribute(self, flag):
try:
with open(self.flag_location, 'w') as f:
f.write(flag)
with open(self.history_location, 'a') as f:
f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True
except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False
def check(self):
try:
url = f'http://localhost:{self.port}/'
r = requests.get(url, timeout=5)
assert "Simple file viewing application" in r.text.lower(), 'Warmup app not available'
with open(self.flag_location, 'r') as f:
host_flag = f.read().strip()
container_flag = subprocess.run([
"docker", "exec", "warmup_container", "cat", "/flag.txt"
], capture_output=True, text=True).stdout.strip()
assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('Check passed for warmup')
return True
except Exception as e:
self.logger.error(f'Could not check warmup: {e}')
return False
+12 -4
View File
@@ -3,8 +3,12 @@ from pydantic import BaseModel
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from config import get_settings
from challenges.Poke import Poke
from challenges.Blinkpdf import BlinkPDF
from challenges.Blogspot import Blogspot
from challenges.Carbeat import Carbeat
from challenges.CDN import CDN
from challenges.Phew import Phew
from challenges.Sheesh import Sheesh
from challenges.Warmup import Warmup
import os
@@ -13,8 +17,12 @@ security = HTTPBasic()
settings = get_settings()
challenges = {
"blogpost": Poke(10000),
"cdn": BlinkPDF(11000),
"blogpost": Blogspot(10000),
"carbeat": Carbeat(11000),
"cdn": CDN(12000),
"phew": Phew(13000),
"sheesh": Sheesh(14000),
"warmup": Warmup(15000),
}
class Flag(BaseModel):