diff --git a/services/blogpost/docker-compose.yml b/services/blogpost/docker-compose.yml index 4d72738..8961ef2 100644 --- a/services/blogpost/docker-compose.yml +++ b/services/blogpost/docker-compose.yml @@ -9,6 +9,4 @@ services: - PASSWORD=root ports: - "4400:8000" - - "4422:22" - environment: - - FLAG=GEMASTIK{fake_flag} \ No newline at end of file + - "4422:22" \ No newline at end of file diff --git a/services/blogpost/sla.py b/services/blogpost/sla.py index 64e90f2..ecf4f5c 100644 --- a/services/blogpost/sla.py +++ b/services/blogpost/sla.py @@ -28,7 +28,7 @@ class WebAppSLA(Challenge): """ flag_location = 'flags/webapp.txt' # Host copy (used by your orchestrator) history_location = 'history/webapp.txt' - container_flag_path = '/app/flag.txt' + container_flag_path = '/flag.txt' container_name = 'chal_app' # <-- set to your actual container name # Heuristics to recognize ExifTool output diff --git a/services/cdn/Dockerfile b/services/cdn/Dockerfile new file mode 100644 index 0000000..c56849b --- /dev/null +++ b/services/cdn/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + DEBIAN_FRONTEND=noninteractive + +WORKDIR /app + +# System deps: exiftool + sshd + bash (sqlite CLI not required for Python sqlite3) +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + libimage-exiftool-perl \ + openssh-server \ + bash \ + && rm -rf /var/lib/apt/lists/* + +# Unprivileged user for the app / SSH +RUN useradd -m -d /home/ctfuser -s /bin/bash ctfuser + +# SSH minimal setup +RUN mkdir -p /run/sshd && chmod 755 /run/sshd && ssh-keygen -A + +COPY chall/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY /chall /app + +RUN chmod +x /app/entrypoint.sh && mkdir -p /app/uploads && chmod 755 /app/uploads + +RUN mkdir -p /data /app/uploads /run/sshd \ + && chown -R ctfuser:ctfuser /app /app/uploads /data \ + && chmod 755 /app + +EXPOSE 8000 +EXPOSE 22 + +ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"] diff --git a/services/cdn/README.md b/services/cdn/README.md new file mode 100644 index 0000000..8c38903 --- /dev/null +++ b/services/cdn/README.md @@ -0,0 +1,30 @@ +## CDN + +db used: sqlite +flag.txt: GEMASTIK{random sha256 generated on app start} + +### feature: +[authentication required with login and register, register default as "user" role] +1. upload image + +### Vulns +#### vuln1: SSTI on image Date Created metadata, exiftool cant insert this, need to write the image's blob +example: +```bash +(base) jons@01-20-jonathanmarbun:/mnt/c/1Jonathan/CTFS/gawe/gms25/web2/exploit$ exiftool -overwrite_original -IPTC:DateCreated="{{7*7}}" image.png +Warning: Invalid date format (use YYYY:mm:dd) in IPTC:DateCreated (ValueConvInv) +Nothing to do. +``` +payload to inject: +```{{lipsum.__builtins__['open']('flag.txt').read()}}``` + +when editing the Date Created metadata manually, somehow it has limit of 46 char (but we can expand that to make it more by deleting the content of another metadata) -> check ssti.png +it probably have different behavior on another image file or format +payload: check exploit/exp3.py + +#### vuln2: + +### Patching Rule? +- dont remove flag.txt/changes its content +- ensure image metadata generation still available +- ensure exiftool still used \ No newline at end of file diff --git a/services/cdn/src/app.py b/services/cdn/chall/app.py similarity index 99% rename from services/cdn/src/app.py rename to services/cdn/chall/app.py index 0910c54..e2476b9 100644 --- a/services/cdn/src/app.py +++ b/services/cdn/chall/app.py @@ -13,7 +13,7 @@ from werkzeug.utils import secure_filename APP_DIR = os.path.dirname(os.path.abspath(__file__)) DB_PATH = os.path.join(APP_DIR, "data.db") UPLOAD_DIR = os.path.join(APP_DIR, "uploads") -FLAG_PATH = os.path.join(APP_DIR, "flag.txt") +FLAG_PATH = os.path.join("/flag.txt") ALLOWED_EXT = {"png", "jpg", "jpeg", "bmp"} MAX_CONTENT_LENGTH = 8 * 1024 * 1024 @@ -261,5 +261,4 @@ def too_large(_): if __name__ == "__main__": with app.app_context(): init_db() - generate_flag_at_boot() app.run(host="0.0.0.0", port=8000, debug=False) diff --git a/services/cdn/chall/entrypoint.sh b/services/cdn/chall/entrypoint.sh new file mode 100644 index 0000000..31b44f0 --- /dev/null +++ b/services/cdn/chall/entrypoint.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +umask 002 # group-writable (helps with bind mounts) + +APP_DIR="/app" +UPLOADS_DIR="${APP_DIR}/uploads" +DATA_DIR="/data" +DBFILE="${DATA_DIR}/app.db" +FLAG_FILE="/flag.txt" + +mkdir -p "${UPLOADS_DIR}" "${DATA_DIR}" + +# Make sure runtime dirs are writable (bind-mounts may ignore chown; that's OK) +chown -R ctfuser:ctfuser "${UPLOADS_DIR}" "${DATA_DIR}" 2>/dev/null || true +chmod 775 "${UPLOADS_DIR}" "${DATA_DIR}" || true + +# ------- Start SSH (and set password if provided) ------- +if [[ -n "${SSH_PASSWORD:-}" ]]; then + echo "ctfuser:${SSH_PASSWORD}" | chpasswd || true +fi + +if command -v service >/dev/null 2>&1; then + service ssh start || /usr/sbin/sshd & +else + /usr/sbin/sshd & +fi +# ------------------------------------------------------- + +# Initialize DB AS ctfuser (so SQLite can write WAL/SHM next to it) +su -s /bin/bash -c "python - <<'PY' +import app as m +from contextlib import contextmanager + +@contextmanager +def ctx(): + with m.app.app_context(): + yield + +with ctx(): + # your function should create tables if missing + m.init_db() + # OPTIONAL: if you had a generate_flag_at_boot, call it here as well + try: + m.generate_flag_at_boot() + except Exception: + pass +print('DB initialized by ctfuser.') +PY +" ctfuser + +# OPTIONAL: if your filesystem dislikes WAL, uncomment: +# su -s /bin/bash -c \"python - <<'PY' +# import sqlite3 +# import os +# db = sqlite3.connect(os.environ.get('DB_PATH', '${DBFILE}')) +# db.execute('PRAGMA journal_mode=DELETE;') +# db.execute('PRAGMA synchronous=NORMAL;') +# db.close() +# print('SQLite journal_mode=DELETE applied.') +# PY +# \" ctfuser + +# Secure the flag file if present +if [ -d "${FLAG_FILE}" ]; then + if [ -f "${FLAG_FILE}" ]; then + chown root:root "${FLAG_FILE}" || true + chmod 444 "${FLAG_FILE}" || true + fi +else + if [ -f "${FLAG_FILE}" ]; then + chown root:root "${FLAG_FILE}" || true + chmod 444 "${FLAG_FILE}" || true + fi +fi + +echo "Starting Flask app on 0.0.0.0:8000 as ctfuser…" +exec su -s /bin/bash -c "cd '${APP_DIR}' && python app.py" ctfuser diff --git a/services/cdn/src/requirements.txt b/services/cdn/chall/requirements.txt similarity index 100% rename from services/cdn/src/requirements.txt rename to services/cdn/chall/requirements.txt diff --git a/services/cdn/src/static/style.css b/services/cdn/chall/static/style.css similarity index 100% rename from services/cdn/src/static/style.css rename to services/cdn/chall/static/style.css diff --git a/services/cdn/src/templates/base.html b/services/cdn/chall/templates/base.html similarity index 100% rename from services/cdn/src/templates/base.html rename to services/cdn/chall/templates/base.html diff --git a/services/cdn/src/templates/gallery.html b/services/cdn/chall/templates/gallery.html similarity index 100% rename from services/cdn/src/templates/gallery.html rename to services/cdn/chall/templates/gallery.html diff --git a/services/cdn/src/templates/login.html b/services/cdn/chall/templates/login.html similarity index 100% rename from services/cdn/src/templates/login.html rename to services/cdn/chall/templates/login.html diff --git a/services/cdn/src/templates/register.html b/services/cdn/chall/templates/register.html similarity index 100% rename from services/cdn/src/templates/register.html rename to services/cdn/chall/templates/register.html diff --git a/services/cdn/src/templates/upload.html b/services/cdn/chall/templates/upload.html similarity index 100% rename from services/cdn/src/templates/upload.html rename to services/cdn/chall/templates/upload.html diff --git a/services/cdn/src/templates/view_post.html b/services/cdn/chall/templates/view_post.html similarity index 100% rename from services/cdn/src/templates/view_post.html rename to services/cdn/chall/templates/view_post.html diff --git a/services/cdn/chall/uploads/2c3f796bc1405f8ec42784088d7324dc1409f745ee41b7acf745ff2a9c1b55dc.png b/services/cdn/chall/uploads/2c3f796bc1405f8ec42784088d7324dc1409f745ee41b7acf745ff2a9c1b55dc.png new file mode 100644 index 0000000..13277a3 Binary files /dev/null and b/services/cdn/chall/uploads/2c3f796bc1405f8ec42784088d7324dc1409f745ee41b7acf745ff2a9c1b55dc.png differ diff --git a/services/cdn/chall/uploads/46ce52a0780252a01e1480d2cbf04248d4f01ff3821b7eb0737108fe99c07f18.png b/services/cdn/chall/uploads/46ce52a0780252a01e1480d2cbf04248d4f01ff3821b7eb0737108fe99c07f18.png new file mode 100644 index 0000000..350e556 Binary files /dev/null and b/services/cdn/chall/uploads/46ce52a0780252a01e1480d2cbf04248d4f01ff3821b7eb0737108fe99c07f18.png differ diff --git a/services/cdn/chall/uploads/6168295d811e738862fad5c0e7aa7306f7364034b5cc77c17a017aff63164b04.png b/services/cdn/chall/uploads/6168295d811e738862fad5c0e7aa7306f7364034b5cc77c17a017aff63164b04.png new file mode 100644 index 0000000..c41aa35 Binary files /dev/null and b/services/cdn/chall/uploads/6168295d811e738862fad5c0e7aa7306f7364034b5cc77c17a017aff63164b04.png differ diff --git a/services/cdn/chall/uploads/c5f9a4af5fcba824a8cdf505107d2c7aed459195c20c9859e132ce5e36ec298b.jpg b/services/cdn/chall/uploads/c5f9a4af5fcba824a8cdf505107d2c7aed459195c20c9859e132ce5e36ec298b.jpg new file mode 100644 index 0000000..6efa28f Binary files /dev/null and b/services/cdn/chall/uploads/c5f9a4af5fcba824a8cdf505107d2c7aed459195c20c9859e132ce5e36ec298b.jpg differ diff --git a/services/cdn/chall/uploads/c5f9a4af5fcba824a8cdf505107d2c7aed459195c20c9859e132ce5e36ec298b.png b/services/cdn/chall/uploads/c5f9a4af5fcba824a8cdf505107d2c7aed459195c20c9859e132ce5e36ec298b.png new file mode 100644 index 0000000..6efa28f Binary files /dev/null and b/services/cdn/chall/uploads/c5f9a4af5fcba824a8cdf505107d2c7aed459195c20c9859e132ce5e36ec298b.png differ diff --git a/services/cdn/chall/uploads/c7339bc5e7968ef9d2d7e72b235e72995ee3363da422a1fe67bf7a195a32ebb0.png b/services/cdn/chall/uploads/c7339bc5e7968ef9d2d7e72b235e72995ee3363da422a1fe67bf7a195a32ebb0.png new file mode 100644 index 0000000..0f72478 Binary files /dev/null and b/services/cdn/chall/uploads/c7339bc5e7968ef9d2d7e72b235e72995ee3363da422a1fe67bf7a195a32ebb0.png differ diff --git a/services/cdn/docker-compose.yml b/services/cdn/docker-compose.yml new file mode 100644 index 0000000..1985562 --- /dev/null +++ b/services/cdn/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3.8" +services: + cdn_services: + container_name: cdn_container + hostname: cdn + build: . + ports: + - "4500:8000" # app + - "4522:22" # ssh + environment: + SECRET_KEY: "c75f1259a4c95bb31563405d488d7bf9c0eaf4d562fd13557624f8e18eb5cfff" + # Optional: set SSH password for ctfuser at runtime + SSH_PASSWORD: "root" + # Optional: override if your app reads it + restart: always diff --git a/services/cdn/exploit/exp1.py b/services/cdn/exploit/exp1.py index f7e9b6f..74e5b28 100644 --- a/services/cdn/exploit/exp1.py +++ b/services/cdn/exploit/exp1.py @@ -7,7 +7,7 @@ import requests print("SSTI (Vuln) Exploit — fixed HOST env, redirects, timeouts") -HOST = "http://localhost:4414" +HOST = "http://localhost:4500" REGISTER_URL = f"{HOST}/register" LOGIN_URL = f"{HOST}/login" UPLOAD_URL = f"{HOST}/upload" diff --git a/services/cdn/exploit/ssti.png.bak b/services/cdn/exploit/ssti.png.bak new file mode 100644 index 0000000..7431e71 Binary files /dev/null and b/services/cdn/exploit/ssti.png.bak differ diff --git a/services/cdn/src/Dockerfile b/services/cdn/src/Dockerfile deleted file mode 100644 index 4266ff0..0000000 --- a/services/cdn/src/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM python:3.12-slim - -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - PIP_NO_CACHE_DIR=1 - -WORKDIR /app - -RUN apt-get update \ - && apt-get install -y --no-install-recommends libimage-exiftool-perl \ - && rm -rf /var/lib/apt/lists/* - -COPY requirements.txt . -RUN pip install -r requirements.txt - -COPY . /app -RUN chmod +x /app/entrypoint.sh \ - && mkdir -p /app/uploads && chmod 755 /app/uploads - -EXPOSE 8000 -ENTRYPOINT ["/bin/bash", "entrypoint.sh"] diff --git a/services/cdn/src/docker-compose.yml b/services/cdn/src/docker-compose.yml deleted file mode 100644 index e04dd5c..0000000 --- a/services/cdn/src/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: "3.8" -services: - cdn: - build: . - container_name: cdn_container - ports: - - "11000:8000" - environment: - SECRET_KEY: "c75f1259a4c95bb31563405d488d7bf9c0eaf4d562fd13557624f8e18eb5cfff" - RESEED_FLAG: "1" - volumes: - - ./uploads:/app/uploads - restart: unless-stopped diff --git a/services/cdn/src/entrypoint.sh b/services/cdn/src/entrypoint.sh deleted file mode 100644 index 8c23a88..0000000 --- a/services/cdn/src/entrypoint.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -mkdir -p /app/uploads -chmod 755 /app/uploads - -python - <<'PY' -import os -import app as m -with m.app.app_context(): - m.init_db() - m.generate_flag_at_boot() -print("DB & flag initialized.") -PY - -FLAG_PATH="${FLAG_PATH:-/app/flag.txt}" - -if [ -d "$FLAG_PATH" ]; then - if [ -f "$FLAG_PATH/flag.txt" ]; then - chown root:root "$FLAG_PATH/flag.txt" || true - chmod 444 "$FLAG_PATH/flag.txt" || true - fi -else - if [ -f "$FLAG_PATH" ]; then - chown root:root "$FLAG_PATH" || true - chmod 444 "$FLAG_PATH" || true - fi -fi - -exec python app.py diff --git a/services/cdn/src/flag_seed.py b/services/cdn/src/flag_seed.py deleted file mode 100644 index 415815c..0000000 --- a/services/cdn/src/flag_seed.py +++ /dev/null @@ -1,7 +0,0 @@ -# Convenience: reseed flag once without starting server -import hashlib, secrets, os -FLAG_PATH = os.path.join(os.path.dirname(__file__), "flag.txt") -sha = hashlib.sha256(secrets.token_bytes(32)).hexdigest() -with open(FLAG_PATH, "w", encoding="utf-8") as fh: - fh.write(f"GEMASTIK{{{sha}}}\n") -print("Flag reseeded:", open(FLAG_PATH).read().strip())