update cdn
@@ -9,6 +9,4 @@ services:
|
||||
- PASSWORD=root
|
||||
ports:
|
||||
- "4400:8000"
|
||||
- "4422:22"
|
||||
environment:
|
||||
- FLAG=GEMASTIK{fake_flag}
|
||||
- "4422:22"
|
||||
@@ -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
|
||||
|
||||
@@ -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__))
|
||||
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)
|
||||
@@ -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")
|
||||
|
||||
HOST = "http://localhost:4414"
|
||||
HOST = "http://localhost:4500"
|
||||
REGISTER_URL = f"{HOST}/register"
|
||||
LOGIN_URL = f"{HOST}/login"
|
||||
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())
|
||||