Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
47bb4fb
worker: Use a volume to expose attack data to program image-based con…
ZachyCatGames Feb 6, 2026
dc1ed85
pipenv devs hate him
ZachyCatGames Feb 10, 2026
d5b3c39
worker: Part 2 of using docker volumes to mount attack information in…
ZachyCatGames Feb 10, 2026
e9a537c
worker: Update all deps to their latest version.
ZachyCatGames Feb 17, 2026
e4d4fe8
worker: Update result class for sqlalchemy updates.
ZachyCatGames Feb 17, 2026
4e10d8e
worker: Set package versions in Pipfile to versions compatible with t…
ZachyCatGames Feb 17, 2026
7cad7c7
Update reset and restore scripts for docker compose v2 and add basic …
ZachyCatGames Apr 1, 2026
6ba4fa9
Initial attempt at updating the web container for latest / recent <ev…
ZachyCatGames Apr 1, 2026
bfd06e7
worker: Destroy containers when we're done with them, also some stuff…
ZachyCatGames Apr 1, 2026
018e35b
web: Update model classes to modern sqlalchemy mapped_column etc.
ZachyCatGames Apr 20, 2026
9116343
worker: misc cleanup
ZachyCatGames Apr 20, 2026
ce7bc43
blueprints: index_table: Don't hardcode the background color on the r…
ZachyCatGames Apr 20, 2026
fd55e8b
blueprints: program: pass a `hashlib.sha1` to PackStreamReader. Newer…
ZachyCatGames Apr 20, 2026
7b08e04
web: Provide `app` context as a global variable. Logging (and probabl…
ZachyCatGames Apr 20, 2026
1302960
worker: Use xargs to pass cmdline args (currently return code gets so…
ZachyCatGames Apr 30, 2026
f5d46db
Use relationships to connect Results, Attacks, and Teams together.
ZachyCatGames May 20, 2026
0e5f982
worker: Use eval to execute a generated command containing the target…
ZachyCatGames May 20, 2026
f2d52b9
web: Implement templating for pack docs. Currently this supports temp…
ZachyCatGames May 20, 2026
612ab6a
update ci to use py 3.14.
ZachyCatGames May 20, 2026
d688081
split Result and Attack again, importing everything early in __init__…
ZachyCatGames May 21, 2026
c3d1ad9
worker: Pull docker-py from git and update other deps.
ZachyCatGames May 21, 2026
8b02697
make purge-*.sh actually useful
ZachyCatGames May 21, 2026
33668fd
worker: Use subpath support in new docker-py.
ZachyCatGames May 21, 2026
f317be9
web: More typing bug fixes.
ZachyCatGames May 21, 2026
8f49d9b
web: Use sqlalchemy 2.x select objects for Result queries.
ZachyCatGames May 21, 2026
012149d
cleanup misc debug prints, commented code, etc
ZachyCatGames May 21, 2026
4e8b1f9
remove my pruge scripts
ZachyCatGames May 21, 2026
6311acb
models: User: make email and id required
ZachyCatGames Jun 8, 2026
01e5b8f
cleanup debug stuff
ZachyCatGames Jun 8, 2026
712df23
worker: Also take OperationError as a reason to retry db init
ZachyCatGames Jun 8, 2026
101b2fe
worker: Use RNG'd path in args volume for the arg storage.
ZachyCatGames Jun 8, 2026
227c806
apparently those massive sql expressions were there for a reason (rei…
ZachyCatGames Jun 11, 2026
0fc31bc
web: models: Attack/Team: Move results passing check to the main expr
ZachyCatGames Jun 11, 2026
ac33276
worker: Fix shell script injection vuln(?)
ZachyCatGames Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: 3.14
- name: Build Containers
run: make
- name: Run Tests
Expand Down
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
db:
image: postgres
volumes:
- db_data:/var/lib/postgresql/data
- db_data:/var/lib/postgresql
networks:
- backend
environment:
Expand All @@ -18,6 +18,7 @@ services:
- backend
volumes:
- cctf:/cctf
- args_store:/args_store
- /var/run/docker.sock:/var/run/docker.sock
- ./pack:/pack
depends_on:
Expand Down Expand Up @@ -49,3 +50,4 @@ networks:
volumes:
cctf:
db_data:
args_store:
2 changes: 1 addition & 1 deletion reset.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash

docker-compose down
docker compose down
docker volume rm dtanm_cctf
docker volume rm dtanm_db_data
4 changes: 2 additions & 2 deletions restore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

backup_dir="$(pwd)/backup"

docker-compose down
docker compose down
docker volume rm dtanm_cctf
docker volume rm dtanm_db_data

docker volume create dtanm_cctf
docker run --rm -v dtanm_cctf:/recover -v "$backup_dir:/backup" ubuntu bash -c "cd /recover && tar xvf /backup/cctf.tar"
docker-compose up -d
docker compose up -d
cat "$backup_dir/dump.sql" | docker exec -i dtanm_db_1 psql -U postgres

6 changes: 4 additions & 2 deletions web/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
FROM python:3.8.3
FROM python:3.14

RUN pip install pipenv

WORKDIR /server

COPY Pipfile Pipfile.lock /server/
#COPY Pipfile Pipfile.lock /server/
COPY Pipfile /server/
RUN pipenv install --deploy
RUN pipenv lock
COPY web web


Expand Down
4 changes: 3 additions & 1 deletion web/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ psycopg2 = "*"
bcrypt = "*"
dulwich = "*"
email-validator = "*"
pytz = "*"
argon2_cffi = "*"

[dev-packages]
pytest = "*"
Expand All @@ -25,4 +27,4 @@ ipython = "*"
mock = "*"

[requires]
python_version = "3.8"
python_version = "3.14"
38 changes: 24 additions & 14 deletions web/web/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@
from flask import Flask, request, url_for, render_template, flash, redirect, g
from flask_sqlalchemy import SQLAlchemy
from flask_security import Security, SQLAlchemyUserDatastore, login_required, current_user
from flask_security.models import fsqla_v3 as fsqla
from redis import Redis
from sqlalchemy.orm import DeclarativeBase
import pytz
from functools import wraps
import time

db = SQLAlchemy()
class ModelBase(DeclarativeBase):
pass

db = SQLAlchemy(model_class=ModelBase)

fsqla.FsModels.set_db_info(db)

redis = None
user_datastore = None

app = None

def team_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
Expand All @@ -24,9 +33,11 @@ def decorated_function(*args, **kwargs):
return decorated_function

def create_app():
global user_datastore, redis
global user_datastore, redis, app
app = Flask(__name__, instance_relative_config=True)

#app.config['DEBUG'] = True

app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev')

# Database Config for Flask-Security
Expand All @@ -35,6 +46,7 @@ def create_app():
app.config['POSTGRES_USER'] = os.environ.get('POSTGRES_USER', 'postgres')
app.config['SQLALCHEMY_DATABASE_URI'] = f'postgresql://{app.config["POSTGRES_USER"]}@{app.config["POSTGRES_HOST"]}/{app.config["POSTGRES_DB"]}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
#app.config['SQLALCHEMY_ECHO'] = True

# Other Config for Flask-Security
app.config['SECURITY_REGISTERABLE'] = False
Expand All @@ -57,23 +69,25 @@ def create_app():

# Setup Flask-Security
from web.models.security import User, Role
from web.models.team import Team
from web.models.attack import Attack
from web.models.result import Result
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)

# Create the administrative user
@app.before_first_request
def create_user():
with app.app_context():
db.create_all()
user_datastore.find_or_create_role(name='admin', description='Administrator')
admin_role = user_datastore.find_or_create_role(name='admin', description='Administrator')

admin_email = app.config['ADMIN_USER_EMAIL']
admin_password = app.config['ADMIN_USER_PASSWORD']

if not user_datastore.get_user(admin_email):
user_datastore.create_user(email=admin_email, password=admin_password, name="DTANM Administrator")
admin_user = user_datastore.find_user(email=admin_email)
if not admin_user:
admin_user = user_datastore.create_user(email=admin_email, password=admin_password, name="DTANM Administrator")
db.session.commit()

user_datastore.add_role_to_user(admin_email, 'admin')
user_datastore.add_role_to_user(admin_user, admin_role)
db.session.commit()

#@app.before_first_request
Expand Down Expand Up @@ -130,9 +144,6 @@ def test_against_gold():
flash("This page has not yet been implemented and does not yet do anything.", category="warning")
return render_template('test_against_gold.html')

from web.models.team import Team
from web.models.attack import Attack
from web.models.result import Result
from sqlalchemy.sql import func
def gen_stats():
global redis
Expand Down Expand Up @@ -161,8 +172,7 @@ def update_team_name():
flash("Your team's name has been updated.", "success")
return redirect(request.referrer)

@app.before_first_request
def register_pack_attacks():
with app.app_context():
from web.models.attack import Attack, create_attack_from_tar
if os.path.exists('/pack/attacks') and Attack.query.count() == 0:
attack_names = {}
Expand Down
5 changes: 5 additions & 0 deletions web/web/blueprints/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import csv
import secrets
import string
import random

admin = Blueprint('admin', __name__, template_folder='templates')

Expand Down Expand Up @@ -50,6 +51,9 @@ def add_user():
password = request.form['password']
user.password = hash_password(password)

# Generate unique identifier.
user.fs_uniquifier = random.randbytes(32).hex()

user_datastore.activate_user(user)
db.session.add(user)
db.session.commit()
Expand Down Expand Up @@ -191,6 +195,7 @@ def import_users():
user.email = row['Email']
user.password = hash_password(row['Password'] if 'Password' in row else 'password')
user.team = team
user.fs_uniquifier = random.randbytes(32).hex()
user_datastore.activate_user(user)
db.session.add(user)
db.session.commit()
Expand Down
1 change: 1 addition & 0 deletions web/web/blueprints/attacks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask import render_template, Blueprint, flash, request, url_for, redirect, send_from_directory, current_app
from flask_security import login_required, current_user
from web.models.attack import Attack, create_attack_from_post, create_attack_from_tar
from web.models.result import Result
from werkzeug.utils import secure_filename
from werkzeug.exceptions import NotFound
from web.models.task import add_task
Expand Down
23 changes: 18 additions & 5 deletions web/web/blueprints/instructions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
from flask import Blueprint, render_template, abort
from flask import Blueprint, render_template, abort, url_for
from jinja2 import Template
from werkzeug.utils import secure_filename
import os

instructions = Blueprint('instructions', __name__, template_folder='templates')

def format_doc(file):
return Template(file.read()).render(
DTANM_LINK_INSTRUCTIONS = url_for('instructions.show'),
DTANM_LINK_PROGRAM = url_for('program.index'),
DTANM_LINK_MY_SCORE = url_for('teams.me'),
DTANM_LINK_TEAMS = url_for('teams.index'),
DTANM_LINK_ATTACKS = url_for('attacks.index'),
DTANM_LINK_STATS = url_for('stats'),
DTANM_LINK_ADMIN = url_for('admin.index')
)


@instructions.route('/', defaults={'page': 'index'})
@instructions.route('/<page>')
def show(page):
base_file=f'/pack/docs/{secure_filename(page)}'
if os.path.isfile(base_file+'.html'):
with open(base_file+'.html') as file:
return render_template('instructions/index.html', page=file.read(), format="html")
return render_template('instructions/index.html', page=format_doc(file), format="html")
elif os.path.isfile(base_file+'.md'):
with open(base_file+'.md') as file:
return render_template('instructions/index.html', page=file.read(), format="markdown")
return render_template('instructions/index.html', page=format_doc(file), format="markdown")
elif os.path.isfile(base_file+'.txt'):
with open(base_file+'.txt') as file:
return render_template('instructions/index.html', page=file.read(), format="text")
return render_template('instructions/index.html', page=format_doc(file), format="text")
else:
abort(404)
abort(404)
3 changes: 2 additions & 1 deletion web/web/blueprints/program/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from web.models.team import Team
from dulwich.repo import Repo
from dulwich.archive import tar_stream
import hashlib

program = Blueprint('program', __name__, template_folder='templates')

Expand Down Expand Up @@ -150,7 +151,7 @@ def git_receive_pack(team_id: int):
p = subprocess.Popen(['git-receive-pack', '--stateless-rpc', os.path.join('/cctf/repos/', str(team_id))], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
data_in = request.data
pack_file = data_in[data_in.index(b'PACK'):]
objects = PackStreamReader(BytesIO(pack_file).read)
objects = PackStreamReader(hashlib.sha1, BytesIO(pack_file).read)
repo_updated = False
for obj in objects.read_objects():
if obj.obj_type_num == 1: # Commit
Expand Down
2 changes: 1 addition & 1 deletion web/web/blueprints/teams/templates/teams/show_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<strong>Output:</strong> <pre>{{ result.output }}</pre>
{% elif not result.passed %}
{% if formatters|length > 1 %}
<div style="background-color: #fff; padding: 10px;">
<div style="padding: 10px;">
<ul class="nav nav-tabs">
{% for formatter in formatters %}
<li class="nav-item">
Expand Down
Loading
Loading