diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa90b8f..9454ab3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 82a0951..00cb8b9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: db: image: postgres volumes: - - db_data:/var/lib/postgresql/data + - db_data:/var/lib/postgresql networks: - backend environment: @@ -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: @@ -49,3 +50,4 @@ networks: volumes: cctf: db_data: + args_store: diff --git a/reset.sh b/reset.sh index ca8c68f..8db7227 100755 --- a/reset.sh +++ b/reset.sh @@ -1,5 +1,5 @@ #!/bin/bash -docker-compose down +docker compose down docker volume rm dtanm_cctf docker volume rm dtanm_db_data diff --git a/restore.sh b/restore.sh index dbf5999..cc93bbc 100755 --- a/restore.sh +++ b/restore.sh @@ -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 diff --git a/web/Dockerfile b/web/Dockerfile index 0324e33..988b9e6 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -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 diff --git a/web/Pipfile b/web/Pipfile index 0f69c2c..0c5fe74 100644 --- a/web/Pipfile +++ b/web/Pipfile @@ -13,6 +13,8 @@ psycopg2 = "*" bcrypt = "*" dulwich = "*" email-validator = "*" +pytz = "*" +argon2_cffi = "*" [dev-packages] pytest = "*" @@ -25,4 +27,4 @@ ipython = "*" mock = "*" [requires] -python_version = "3.8" +python_version = "3.14" diff --git a/web/web/__init__.py b/web/web/__init__.py index 6f0a2da..a96bdb4 100644 --- a/web/web/__init__.py +++ b/web/web/__init__.py @@ -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): @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 = {} diff --git a/web/web/blueprints/admin/__init__.py b/web/web/blueprints/admin/__init__.py index e5f978b..c6c1886 100644 --- a/web/web/blueprints/admin/__init__.py +++ b/web/web/blueprints/admin/__init__.py @@ -15,6 +15,7 @@ import csv import secrets import string +import random admin = Blueprint('admin', __name__, template_folder='templates') @@ -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() @@ -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() diff --git a/web/web/blueprints/attacks/__init__.py b/web/web/blueprints/attacks/__init__.py index 4fe3931..36366fc 100644 --- a/web/web/blueprints/attacks/__init__.py +++ b/web/web/blueprints/attacks/__init__.py @@ -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 diff --git a/web/web/blueprints/instructions/__init__.py b/web/web/blueprints/instructions/__init__.py index 214bc14..3d94be5 100644 --- a/web/web/blueprints/instructions/__init__.py +++ b/web/web/blueprints/instructions/__init__.py @@ -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('/') 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) \ No newline at end of file + abort(404) diff --git a/web/web/blueprints/program/__init__.py b/web/web/blueprints/program/__init__.py index c9bdab7..4e4766f 100644 --- a/web/web/blueprints/program/__init__.py +++ b/web/web/blueprints/program/__init__.py @@ -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') @@ -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 diff --git a/web/web/blueprints/teams/templates/teams/show_table.html b/web/web/blueprints/teams/templates/teams/show_table.html index 0179f34..c62998d 100644 --- a/web/web/blueprints/teams/templates/teams/show_table.html +++ b/web/web/blueprints/teams/templates/teams/show_table.html @@ -39,7 +39,7 @@ Output:
{{ result.output }}
{% elif not result.passed %} {% if formatters|length > 1 %} -
+