update cdn
@@ -9,6 +9,4 @@ services:
|
|||||||
- PASSWORD=root
|
- PASSWORD=root
|
||||||
ports:
|
ports:
|
||||||
- "4400:8000"
|
- "4400:8000"
|
||||||
- "4422:22"
|
- "4422:22"
|
||||||
environment:
|
|
||||||
- FLAG=GEMASTIK{fake_flag}
|
|
||||||
@@ -28,7 +28,7 @@ class WebAppSLA(Challenge):
|
|||||||
"""
|
"""
|
||||||
flag_location = 'flags/webapp.txt' # Host copy (used by your orchestrator)
|
flag_location = 'flags/webapp.txt' # Host copy (used by your orchestrator)
|
||||||
history_location = 'history/webapp.txt'
|
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
|
container_name = 'chal_app' # <-- set to your actual container name
|
||||||
|
|
||||||
# Heuristics to recognize ExifTool output
|
# Heuristics to recognize ExifTool output
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -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
|
||||||
@@ -13,7 +13,7 @@ from werkzeug.utils import secure_filename
|
|||||||
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
DB_PATH = os.path.join(APP_DIR, "data.db")
|
DB_PATH = os.path.join(APP_DIR, "data.db")
|
||||||
UPLOAD_DIR = os.path.join(APP_DIR, "uploads")
|
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"}
|
ALLOWED_EXT = {"png", "jpg", "jpeg", "bmp"}
|
||||||
MAX_CONTENT_LENGTH = 8 * 1024 * 1024
|
MAX_CONTENT_LENGTH = 8 * 1024 * 1024
|
||||||
@@ -261,5 +261,4 @@ def too_large(_):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
init_db()
|
init_db()
|
||||||
generate_flag_at_boot()
|
|
||||||
app.run(host="0.0.0.0", port=8000, debug=False)
|
app.run(host="0.0.0.0", port=8000, debug=False)
|
||||||
@@ -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
|
||||||
|
After Width: | Height: | Size: 1012 KiB |
|
After Width: | Height: | Size: 1012 KiB |
|
After Width: | Height: | Size: 1012 KiB |
|
After Width: | Height: | Size: 1012 KiB |
|
After Width: | Height: | Size: 1012 KiB |
|
After Width: | Height: | Size: 1012 KiB |
@@ -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
|
||||||
@@ -7,7 +7,7 @@ import requests
|
|||||||
|
|
||||||
print("SSTI (Vuln) Exploit — fixed HOST env, redirects, timeouts")
|
print("SSTI (Vuln) Exploit — fixed HOST env, redirects, timeouts")
|
||||||
|
|
||||||
HOST = "http://localhost:4414"
|
HOST = "http://localhost:4500"
|
||||||
REGISTER_URL = f"{HOST}/register"
|
REGISTER_URL = f"{HOST}/register"
|
||||||
LOGIN_URL = f"{HOST}/login"
|
LOGIN_URL = f"{HOST}/login"
|
||||||
UPLOAD_URL = f"{HOST}/upload"
|
UPLOAD_URL = f"{HOST}/upload"
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1012 KiB |
@@ -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"]
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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())
|
|
||||||