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
@@ -15,7 +15,7 @@ from PIL import Image # pillow for tiny PNG generation
from .Challenge import Challenge from .Challenge import Challenge
class WebAppSLA(Challenge): class Blogspot(Challenge):
""" """
SLA checker for the provided Flask challenge app. SLA checker for the provided Flask challenge app.
@@ -26,8 +26,8 @@ class WebAppSLA(Challenge):
- Login flow at /login, register at /register - Login flow at /login, register at /register
- Create post at /create, view post at /post/<id>, uploads at /uploads/<file> - Create post at /create, view post at /post/<id>, uploads at /uploads/<file>
""" """
flag_location = 'flags/webapp.txt' # Host copy (used by your orchestrator) flag_location = 'flags/blogspot.txt' # Host copy (used by your orchestrator)
history_location = 'history/webapp.txt' history_location = 'history/blogspot.txt'
container_flag_path = '/flag.txt' container_flag_path = '/flag.txt'
container_name = 'blogpost_container' # <-- set to your actual container name container_name = 'blogpost_container' # <-- set to your actual container name
@@ -12,7 +12,7 @@ from PIL import Image
from .Challenge import Challenge from .Challenge import Challenge
class WebAppSLA(Challenge): class CDN(Challenge):
""" """
SLA checker for the Flask app. SLA checker for the Flask app.
@@ -23,8 +23,8 @@ class WebAppSLA(Challenge):
""" """
# Paths & infra knobs # Paths & infra knobs
flag_location = 'flags/webapp.txt' # host copy (written by your orchestrator) flag_location = 'flags/cdn.txt' # host copy (written by your orchestrator)
history_location = 'history/webapp.txt' history_location = 'history/cdn.txt'
container_flag_path = '/flag.txt' container_flag_path = '/flag.txt'
container_name = 'chal_app' # adjust if your container name differs container_name = 'chal_app' # adjust if your container name differs
@@ -34,6 +34,25 @@ class WebAppSLA(Challenge):
'Date Created', '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): def _make_logger(self):
if not hasattr(self, 'logger') or self.logger is None: if not hasattr(self, 'logger') or self.logger is None:
self.logger = logging.getLogger(self.__class__.__name__) self.logger = logging.getLogger(self.__class__.__name__)
-1
View File
@@ -4,7 +4,6 @@ import string
from config import get_settings from config import get_settings
class Challenge(object): class Challenge(object):
name = __name__ name = __name__
settings = get_settings() settings = get_settings()
@@ -1,182 +1,182 @@
from .Challenge import Challenge from .Challenge import Challenge
import subprocess import subprocess
import time import time
import re import re
import os import os
class PailierBingo(Challenge): class Phew(Challenge):
flag_location = 'flags/phew.txt' flag_location = 'flags/phew.txt'
history_location = 'history/phew.txt' history_location = 'history/phew.txt'
_CONTAINER = "pailier_container" # <-- set to your container name _CONTAINER = "pailier_container" # <-- set to your container name
_SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"] _SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"]
_HEX_RE = re.compile(r'^[0-9a-fA-F]+$') _HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
def _read_container_flag(self) -> str: def _read_container_flag(self) -> str:
out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"], out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"],
capture_output=True, text=True) capture_output=True, text=True)
if out.returncode != 0 or not out.stdout.strip(): if out.returncode != 0 or not out.stdout.strip():
raise FileNotFoundError("Flag not found in container (/flag.txt)") raise FileNotFoundError("Flag not found in container (/flag.txt)")
return out.stdout.strip() return out.stdout.strip()
def _spawn(self): def _spawn(self):
return subprocess.Popen( return subprocess.Popen(
self._SERVICE_CMD, self._SERVICE_CMD,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
text=True, text=True,
bufsize=0, bufsize=0,
) )
def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000): def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000):
start = time.time() start = time.time()
buf = [] buf = []
r = proc.stdout.read r = proc.stdout.read
while True: while True:
if time.time() - start > timeout: if time.time() - start > timeout:
tail = ''.join(buf)[-500:] tail = ''.join(buf)[-500:]
raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}") raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}")
ch = r(1) ch = r(1)
if ch == "" and proc.poll() is not None: if ch == "" and proc.poll() is not None:
raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}") raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}")
buf.append(ch) buf.append(ch)
if len(buf) > max_bytes: if len(buf) > max_bytes:
raise RuntimeError("Exceeded max read size") raise RuntimeError("Exceeded max read size")
if token in "".join(buf): if token in "".join(buf):
return "".join(buf) return "".join(buf)
def _send_line(self, proc, s: str): def _send_line(self, proc, s: str):
proc.stdin.write(s + "\n") proc.stdin.write(s + "\n")
proc.stdin.flush() proc.stdin.flush()
def _expect_hex_field(self, text: str, label: str) -> str: def _expect_hex_field(self, text: str, label: str) -> str:
""" """
Extracts 'ct : <hex>' or 'pt : <hex>' (label = 'ct' or 'pt') Extracts 'ct : <hex>' or 'pt : <hex>' (label = 'ct' or 'pt')
The service prints with spaces around the colon: 'ct : <hex>' The service prints with spaces around the colon: 'ct : <hex>'
""" """
m = re.search(rf"{re.escape(label)}\s*:\s*([0-9a-fA-F]+)", text) 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:]}" assert m, f"Missing '{label}' in output. Tail:\n{text[-400:]}"
hx = m.group(1) hx = m.group(1)
assert self._HEX_RE.match(hx), f"{label} is not hex" assert self._HEX_RE.match(hx), f"{label} is not hex"
return hx return hx
def distribute(self, flag): def distribute(self, flag):
try: try:
os.makedirs(os.path.dirname(self.flag_location), exist_ok=True) os.makedirs(os.path.dirname(self.flag_location), exist_ok=True)
with open(self.flag_location, 'w') as f: with open(self.flag_location, 'w') as f:
f.write(flag) f.write(flag)
os.makedirs(os.path.dirname(self.history_location), exist_ok=True) os.makedirs(os.path.dirname(self.history_location), exist_ok=True)
with open(self.history_location, 'a') as f: with open(self.history_location, 'a') as f:
f.write(flag + '\n') f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}') self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True return True
except Exception as e: except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}') self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False return False
def check(self): def check(self):
try: try:
with open(self.flag_location, 'r') as f: with open(self.flag_location, 'r') as f:
host_flag = f.read().strip() host_flag = f.read().strip()
container_flag = self._read_container_flag() container_flag = self._read_container_flag()
assert host_flag == container_flag, 'Flag mismatch between host and container' assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('[ok] flag parity (phew)') self.logger.info('[ok] flag parity (phew)')
proc = self._spawn() proc = self._spawn()
def menu(): def menu():
self._read_until(proc, "> ", timeout=5.0) self._read_until(proc, "> ", timeout=5.0)
menu() menu()
self._send_line(proc, "1") self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0) self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0) self._read_until(proc, "> ", timeout=3.0)
pt_hex = "414243444546" # "ABCDEF" pt_hex = "414243444546" # "ABCDEF"
self._send_line(proc, pt_hex) self._send_line(proc, pt_hex)
out = self._read_until(proc, "\n", timeout=3.0) out = self._read_until(proc, "\n", timeout=3.0)
ct_hex = self._expect_hex_field(out, "ct") ct_hex = self._expect_hex_field(out, "ct")
self.logger.info("[ok] encrypt(1) produced hex CT") self.logger.info("[ok] encrypt(1) produced hex CT")
menu() menu()
self._send_line(proc, "3") self._send_line(proc, "3")
self._read_until(proc, "ct (hex)", timeout=3.0) self._read_until(proc, "ct (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0) self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, ct_hex) self._send_line(proc, ct_hex)
out_dec = self._read_until(proc, "\n", timeout=3.0) out_dec = self._read_until(proc, "\n", timeout=3.0)
pt_back_hex = self._expect_hex_field(out_dec, "pt") pt_back_hex = self._expect_hex_field(out_dec, "pt")
# Normalize leading zeros for comparison # Normalize leading zeros for comparison
def norm(h): return (h.lstrip("0") or "0").lower() 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}" 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") self.logger.info("[ok] decrypt(3) round-trip")
menu() menu()
self._send_line(proc, "1") self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0) self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0) self._read_until(proc, "> ", timeout=3.0)
same_pt = "01" * 8 same_pt = "01" * 8
self._send_line(proc, same_pt) self._send_line(proc, same_pt)
out_a = self._read_until(proc, "\n", timeout=3.0) out_a = self._read_until(proc, "\n", timeout=3.0)
ct_a = self._expect_hex_field(out_a, "ct") ct_a = self._expect_hex_field(out_a, "ct")
menu() menu()
self._send_line(proc, "1") self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0) self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0) self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, same_pt) self._send_line(proc, same_pt)
out_b = self._read_until(proc, "\n", timeout=3.0) out_b = self._read_until(proc, "\n", timeout=3.0)
ct_b = self._expect_hex_field(out_b, "ct") ct_b = self._expect_hex_field(out_b, "ct")
assert ct_a.lower() != ct_b.lower(), "Encryption appears deterministic for same plaintext" assert ct_a.lower() != ct_b.lower(), "Encryption appears deterministic for same plaintext"
self.logger.info("[ok] encrypt(1) randomness") self.logger.info("[ok] encrypt(1) randomness")
menu() menu()
self._send_line(proc, "2") self._send_line(proc, "2")
out2a = self._read_until(proc, "\n", timeout=3.0) out2a = self._read_until(proc, "\n", timeout=3.0)
bingo1 = self._expect_hex_field(out2a, "ct") bingo1 = self._expect_hex_field(out2a, "ct")
menu() menu()
self._send_line(proc, "2") self._send_line(proc, "2")
out2b = self._read_until(proc, "\n", timeout=3.0) out2b = self._read_until(proc, "\n", timeout=3.0)
bingo2 = self._expect_hex_field(out2b, "ct") bingo2 = self._expect_hex_field(out2b, "ct")
assert bingo1.lower() != bingo2.lower(), "Bingo ciphertexts reused randomness" assert bingo1.lower() != bingo2.lower(), "Bingo ciphertexts reused randomness"
self.logger.info("[ok] bingo(2) randomness") self.logger.info("[ok] bingo(2) randomness")
menu() menu()
self._send_line(proc, "1") self._send_line(proc, "1")
self._read_until(proc, "pt (hex)", timeout=3.0) self._read_until(proc, "pt (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0) self._read_until(proc, "> ", timeout=3.0)
pt_hex2 = "deadbeef" pt_hex2 = "deadbeef"
self._send_line(proc, pt_hex2) self._send_line(proc, pt_hex2)
out_enc2 = self._read_until(proc, "\n", timeout=3.0) out_enc2 = self._read_until(proc, "\n", timeout=3.0)
ct2 = self._expect_hex_field(out_enc2, "ct") ct2 = self._expect_hex_field(out_enc2, "ct")
menu() menu()
self._send_line(proc, "3") self._send_line(proc, "3")
self._read_until(proc, "ct (hex)", timeout=3.0) self._read_until(proc, "ct (hex)", timeout=3.0)
self._read_until(proc, "> ", timeout=3.0) self._read_until(proc, "> ", timeout=3.0)
self._send_line(proc, ct2) self._send_line(proc, ct2)
out_dec2 = self._read_until(proc, "\n", timeout=3.0) out_dec2 = self._read_until(proc, "\n", timeout=3.0)
pt2_back = self._expect_hex_field(out_dec2, "pt") 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}" 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)") self.logger.info("[ok] decrypt(3) round-trip (case 2)")
menu() menu()
self._send_line(proc, "4") self._send_line(proc, "4")
try: try:
proc.wait(timeout=2.0) proc.wait(timeout=2.0)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
proc.kill() proc.kill()
raise AssertionError("Program did not exit after option 4") raise AssertionError("Program did not exit after option 4")
self.logger.info("[ok] service exit on 4") self.logger.info("[ok] service exit on 4")
self.logger.info('Check passed for phew') self.logger.info('Check passed for phew')
return True return True
except Exception as e: except Exception as e:
self.logger.error(f'Could not check phew: {e}') self.logger.error(f'Could not check phew: {e}')
return False return False
@@ -1,191 +1,191 @@
from .Challenge import Challenge from .Challenge import Challenge
import subprocess import subprocess
import time import time
import re import re
import os import os
class Sheesh(Challenge): class Sheesh(Challenge):
flag_location = 'flags/sheesh.txt' flag_location = 'flags/sheesh.txt'
history_location = 'history/sheesh.txt' history_location = 'history/sheesh.txt'
_CONTAINER = "sheesh_container" _CONTAINER = "sheesh_container"
_SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"] _SERVICE_CMD = ["docker", "exec", "-i", _CONTAINER, "python3", "/usr/local/bin/chall.py"]
_HEX_RE = re.compile(r'^[0-9a-fA-F]+$') _HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
def _read_container_flag(self) -> str: def _read_container_flag(self) -> str:
out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"], out = subprocess.run(["docker", "exec", self._CONTAINER, "cat", "/flag.txt"],
capture_output=True, text=True) capture_output=True, text=True)
if out.returncode != 0 or not out.stdout.strip(): if out.returncode != 0 or not out.stdout.strip():
raise FileNotFoundError("Flag not found in container (/flag.txt)") raise FileNotFoundError("Flag not found in container (/flag.txt)")
return out.stdout.strip() return out.stdout.strip()
def _spawn(self): def _spawn(self):
return subprocess.Popen( return subprocess.Popen(
self._SERVICE_CMD, self._SERVICE_CMD,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
text=True, text=True,
bufsize=0, bufsize=0,
) )
def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000): def _read_until(self, proc, token, timeout=5.0, max_bytes=1_000_000):
start = time.time() start = time.time()
buf = [] buf = []
r = proc.stdout.read r = proc.stdout.read
while True: while True:
if time.time() - start > timeout: if time.time() - start > timeout:
tail = ''.join(buf)[-500:] tail = ''.join(buf)[-500:]
raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}") raise TimeoutError(f"Timeout waiting for '{token}'. Got so far:\n{tail}")
ch = r(1) ch = r(1)
if ch == "" and proc.poll() is not None: if ch == "" and proc.poll() is not None:
raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}") raise RuntimeError(f"Process ended while waiting for '{token}'. Output:\n{''.join(buf)}")
buf.append(ch) buf.append(ch)
if len(buf) > max_bytes: if len(buf) > max_bytes:
raise RuntimeError("Exceeded max read size") raise RuntimeError("Exceeded max read size")
if token in "".join(buf): if token in "".join(buf):
return "".join(buf) return "".join(buf)
def _send_line(self, proc, s: str): def _send_line(self, proc, s: str):
proc.stdin.write(s + "\n") proc.stdin.write(s + "\n")
proc.stdin.flush() proc.stdin.flush()
def _expect_hex_field(self, text: str, label: str) -> str: def _expect_hex_field(self, text: str, label: str) -> str:
m = re.search(rf"{re.escape(label)}\s*:\s*([0-9a-fA-F]+)", text) 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:]}" assert m, f"Missing '{label}' in output. Tail:\n{text[-400:]}"
hx = m.group(1) hx = m.group(1)
assert self._HEX_RE.match(hx), f"{label} is not hex" assert self._HEX_RE.match(hx), f"{label} is not hex"
return hx return hx
def distribute(self, flag): def distribute(self, flag):
try: try:
os.makedirs(os.path.dirname(self.flag_location), exist_ok=True) os.makedirs(os.path.dirname(self.flag_location), exist_ok=True)
with open(self.flag_location, 'w') as f: with open(self.flag_location, 'w') as f:
f.write(flag) f.write(flag)
os.makedirs(os.path.dirname(self.history_location), exist_ok=True) os.makedirs(os.path.dirname(self.history_location), exist_ok=True)
with open(self.history_location, 'a') as f: with open(self.history_location, 'a') as f:
f.write(flag + '\n') f.write(flag + '\n')
self.logger.info(f'Flag {flag} written to {self.flag_location}') self.logger.info(f'Flag {flag} written to {self.flag_location}')
return True return True
except Exception as e: except Exception as e:
self.logger.error(f'Could not write flag to {self.flag_location}: {e}') self.logger.error(f'Could not write flag to {self.flag_location}: {e}')
return False return False
def check(self): def check(self):
try: try:
with open(self.flag_location, 'r') as f: with open(self.flag_location, 'r') as f:
host_flag = f.read().strip() host_flag = f.read().strip()
container_flag = self._read_container_flag() container_flag = self._read_container_flag()
assert host_flag == container_flag, 'Flag mismatch between host and container' assert host_flag == container_flag, 'Flag mismatch between host and container'
self.logger.info('[ok] flag parity (sheesh)') self.logger.info('[ok] flag parity (sheesh)')
proc = self._spawn() proc = self._spawn()
def menu(): def menu():
self._read_until(proc, "> ", timeout=5.0) self._read_until(proc, "> ", timeout=5.0)
menu() menu()
self._send_line(proc, "1") self._send_line(proc, "1")
self._read_until(proc, "pt: ", timeout=3.0) self._read_until(proc, "pt: ", timeout=3.0)
pt_hex = "414243444546" # "ABCDEF" pt_hex = "414243444546" # "ABCDEF"
self._send_line(proc, pt_hex) self._send_line(proc, pt_hex)
out = self._read_until(proc, "\n\n", timeout=3.0) out = self._read_until(proc, "\n\n", timeout=3.0)
ct_hex = self._expect_hex_field(out, "ct") ct_hex = self._expect_hex_field(out, "ct")
ct = bytes.fromhex(ct_hex) ct = bytes.fromhex(ct_hex)
assert len(ct) == 16 + len(bytes.fromhex(pt_hex)), "CFB ct length mismatch (IV+PT)" assert len(ct) == 16 + len(bytes.fromhex(pt_hex)), "CFB ct length mismatch (IV+PT)"
iv_a = ct[:16] iv_a = ct[:16]
self.logger.info("[ok] encrypt(1) basic") self.logger.info("[ok] encrypt(1) basic")
menu() menu()
self._send_line(proc, "1") self._send_line(proc, "1")
self._read_until(proc, "pt: ", timeout=3.0) self._read_until(proc, "pt: ", timeout=3.0)
pt_hex2 = "00" * 8 pt_hex2 = "00" * 8
self._send_line(proc, pt_hex2) self._send_line(proc, pt_hex2)
out2 = self._read_until(proc, "\n\n", timeout=3.0) out2 = self._read_until(proc, "\n\n", timeout=3.0)
ct2_hex = self._expect_hex_field(out2, "ct") ct2_hex = self._expect_hex_field(out2, "ct")
ct2 = bytes.fromhex(ct2_hex) ct2 = bytes.fromhex(ct2_hex)
assert len(ct2) == 16 + len(bytes.fromhex(pt_hex2)), "Second CFB ct length mismatch" assert len(ct2) == 16 + len(bytes.fromhex(pt_hex2)), "Second CFB ct length mismatch"
iv_b = ct2[:16] iv_b = ct2[:16]
assert iv_a != iv_b, "CFB IV appears reused" assert iv_a != iv_b, "CFB IV appears reused"
self.logger.info("[ok] encrypt(1) IV rotates") self.logger.info("[ok] encrypt(1) IV rotates")
menu() menu()
self._send_line(proc, "3") self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0) self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "00" * 15) self._send_line(proc, "00" * 15)
out3a = self._read_until(proc, "\n", timeout=3.0) out3a = self._read_until(proc, "\n", timeout=3.0)
assert "hmmm" in out3a.lower(), "Option 3 must reject non-16-byte input (15B)" assert "hmmm" in out3a.lower(), "Option 3 must reject non-16-byte input (15B)"
menu() menu()
self._send_line(proc, "3") self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0) self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "00" * 17) self._send_line(proc, "00" * 17)
out3b = self._read_until(proc, "\n", timeout=3.0) out3b = self._read_until(proc, "\n", timeout=3.0)
assert "hmmm" in out3b.lower(), "Option 3 must reject non-16-byte input (17B)" assert "hmmm" in out3b.lower(), "Option 3 must reject non-16-byte input (17B)"
menu() menu()
self._send_line(proc, "3") self._send_line(proc, "3")
self._read_until(proc, "pt: ", timeout=3.0) self._read_until(proc, "pt: ", timeout=3.0)
self._send_line(proc, "11" * 16) self._send_line(proc, "11" * 16)
out3 = self._read_until(proc, "\n\n", timeout=3.0) out3 = self._read_until(proc, "\n\n", timeout=3.0)
ct3_hex = self._expect_hex_field(out3, "ct") ct3_hex = self._expect_hex_field(out3, "ct")
ct3 = bytes.fromhex(ct3_hex) ct3 = bytes.fromhex(ct3_hex)
assert len(ct3) in (16, 32), f"Unexpected CT length for option 3: {len(ct3)} (expected 16 or 32)" assert len(ct3) in (16, 32), f"Unexpected CT length for option 3: {len(ct3)} (expected 16 or 32)"
if len(ct3) == 16: if len(ct3) == 16:
self.logger.info("[ok] get third(3): vuln flavor (16-byte CT)") self.logger.info("[ok] get third(3): vuln flavor (16-byte CT)")
else: else:
self.logger.info("[ok] get third(3): patched flavor (32-byte CT)") self.logger.info("[ok] get third(3): patched flavor (32-byte CT)")
menu() menu()
self._send_line(proc, "3") self._send_line(proc, "3")
out4 = self._read_until(proc, "\n", timeout=3.0) out4 = self._read_until(proc, "\n", timeout=3.0)
assert "sheesh" in out4.lower(), "Second call to option 3 should be rejected with 'sheesh'" assert "sheesh" in out4.lower(), "Second call to option 3 should be rejected with 'sheesh'"
self.logger.info("[ok] get third(3) lockout") self.logger.info("[ok] get third(3) lockout")
menu() menu()
self._send_line(proc, "2") self._send_line(proc, "2")
out5 = self._read_until(proc, "\n\n", timeout=3.0) out5 = self._read_until(proc, "\n\n", timeout=3.0)
iv1_hex = self._expect_hex_field(out5, "iv1") iv1_hex = self._expect_hex_field(out5, "iv1")
iv2_hex = self._expect_hex_field(out5, "iv2") iv2_hex = self._expect_hex_field(out5, "iv2")
ct5_hex = self._expect_hex_field(out5, "ct") ct5_hex = self._expect_hex_field(out5, "ct")
assert len(bytes.fromhex(iv1_hex)) == 16, "iv1 length invalid" assert len(bytes.fromhex(iv1_hex)) == 16, "iv1 length invalid"
assert len(bytes.fromhex(iv2_hex)) == 16, "iv2 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" 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") self.logger.info("[ok] profit(2) first call")
menu() menu()
self._send_line(proc, "2") self._send_line(proc, "2")
out6 = self._read_until(proc, "\n\n", timeout=3.0) out6 = self._read_until(proc, "\n\n", timeout=3.0)
iv1_hex_2 = self._expect_hex_field(out6, "iv1") iv1_hex_2 = self._expect_hex_field(out6, "iv1")
iv2_hex_2 = self._expect_hex_field(out6, "iv2") 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" 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") self.logger.info("[ok] profit(2) fresh IVs")
menu() menu()
self._send_line(proc, "4") self._send_line(proc, "4")
try: try:
proc.wait(timeout=2.0) proc.wait(timeout=2.0)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
proc.kill() proc.kill()
raise AssertionError("Program did not exit after option 4") raise AssertionError("Program did not exit after option 4")
self.logger.info("[ok] service exit on 4") self.logger.info("[ok] service exit on 4")
proc_alarm = self._spawn() proc_alarm = self._spawn()
self._read_until(proc_alarm, "zzz", timeout=190.0) # 180s + slack self._read_until(proc_alarm, "zzz", timeout=190.0) # 180s + slack
try: try:
proc_alarm.wait(timeout=5.0) proc_alarm.wait(timeout=5.0)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
proc_alarm.kill() proc_alarm.kill()
raise AssertionError("Alarm fired but process did not exit") raise AssertionError("Alarm fired but process did not exit")
self.logger.info("[ok] alarm fired ('zzz') and process self-terminated") self.logger.info("[ok] alarm fired ('zzz') and process self-terminated")
self.logger.info('Check passed for sheesh') self.logger.info('Check passed for sheesh')
return True return True
except Exception as e: except Exception as e:
self.logger.error(f'Could not check sheesh: {e}') self.logger.error(f'Could not check sheesh: {e}')
return False 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 fastapi.security import HTTPBasic, HTTPBasicCredentials
from config import get_settings from config import get_settings
from challenges.Poke import Poke from challenges.Blogspot import Blogspot
from challenges.Blinkpdf import BlinkPDF 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 import os
@@ -13,8 +17,12 @@ security = HTTPBasic()
settings = get_settings() settings = get_settings()
challenges = { challenges = {
"blogpost": Poke(10000), "blogpost": Blogspot(10000),
"cdn": BlinkPDF(11000), "carbeat": Carbeat(11000),
"cdn": CDN(12000),
"phew": Phew(13000),
"sheesh": Sheesh(14000),
"warmup": Warmup(15000),
} }
class Flag(BaseModel): class Flag(BaseModel):
-2
View File
@@ -1,5 +1,3 @@
version: '3.1'
services: services:
# --- blogpost --- # --- blogpost ---
blogpost: blogpost:
+6 -6
View File
@@ -35,12 +35,12 @@ def main():
os.chdir(os.path.join(cwd, 'services')) os.chdir(os.path.join(cwd, 'services'))
os.system(f'docker compose -f docker-compose.yml up --build -d {args.challenges}') os.system(f'docker compose -f docker-compose.yml up --build -d {args.challenges}')
# os.chdir(os.path.join(cwd, 'receiver')) os.chdir(os.path.join(cwd, 'receiver'))
# os.system('apt-get install -y gcc python3-dev libgmp3-dev libssl-dev libffi-dev build-essential python3-venv') os.system('apt-get install -y gcc python3-dev libgmp3-dev libssl-dev libffi-dev build-essential python3-venv')
# os.system('python3 -m venv .') os.system('python3 -m venv .')
# os.system('sudo ./bin/activate') os.system('sudo ./bin/activate')
# os.system('sudo ./bin/python3 -m pip install -r requirements.txt') os.system('sudo ./bin/python3 -m pip install -r requirements.txt')
# os.system('sudo ./bin/python3 -m uvicorn main:app --reload --host 0.0.0.0 --port 80') os.system('sudo ./bin/python3 -m uvicorn main:app --reload --host 0.0.0.0 --port 80')
if __name__ == '__main__': if __name__ == '__main__':
main() main()