Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 .dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__pycache__/
.idea/
.idea/
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ __pycache__/
.env
.venv
env/
venv/
venv/
16 changes: 16 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/pylint-dev/pylint
rev: v4.0.2
hooks:
- id: pylint
args: ['--disable', 'R1705,C0301,W0511,R0903,W0718,R0911']
language: system
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ RUN python3 -m pip install --no-cache-dir -r requirements.txt && \
sed -i 's/from jinja2 import/from markupsafe import/g' /usr/local/lib/python3.13/site-packages/flask_recaptcha.py

EXPOSE 5000
CMD ["waitress-serve", "--host=0.0.0.0", "--port=5000", "--threads=8", "run:app"]
CMD ["waitress-serve", "--host=0.0.0.0", "--port=5000", "--threads=8", "run:app"]
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Features

- Multi containers
- Multi containers
- Multi exposed ports
- Each challenge are in a separate network
- Relation between containers using `hostname`
Expand Down Expand Up @@ -50,7 +50,7 @@ export DEBUG=1
sudo -E python3 run.py
```

> You can utilize the `ADMIN_ONLY` flag to restrict login to administrators only. It's useful for testing your challenges before the beginning of the CTF.
> You can utilize the `ADMIN_ONLY` flag to restrict login to administrators only. It's useful for testing your challenges before the beginning of the CTF.

## Deployment

Expand Down Expand Up @@ -100,7 +100,6 @@ All the slaves must build all docker images present in the `config.json` file (i

## Todo

- pylint
- add more docs about `config.json` format
- Extend instance feature
- Display connection string (ex: ssh -p ..., http://host:port, nc host port, ...)
Expand Down
3 changes: 3 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
''' Base flask '''

import logging
from os import getenv
from secrets import token_hex
Expand All @@ -8,6 +10,7 @@


def create_app():
''' Base flask app '''
app = Flask(__name__)

app.secret_key = token_hex()
Expand Down
4 changes: 4 additions & 0 deletions app/auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
''' App authentication '''

from functools import wraps

from flask import redirect, session, url_for


def login_required(f):
''' Login check '''
@wraps(f)
def wrap(*args, **kwargs):
if session and session["verified"]:
Expand All @@ -15,6 +18,7 @@ def wrap(*args, **kwargs):


def admin_required(f):
''' Admin check '''
@wraps(f)
def wrap(*args, **kwargs):
if session and session["admin"]:
Expand Down
4 changes: 3 additions & 1 deletion app/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
''' Configuration '''

#!/usr/bin/env python3
from json import load
from os import getenv
Expand All @@ -7,7 +9,7 @@
DEBUG = getenv("DEBUG", "").strip().upper() in ["1", "TRUE"]
ADMIN_ONLY = getenv("ADMIN_ONLY", "").strip().upper() in ["1", "TRUE"]

with open("config.json", "r") as config_file:
with open("config.json", "r", encoding="utf-8") as config_file:
config = load(config_file)

WEBSITE_TITLE = config["website_title"]
Expand Down
4 changes: 3 additions & 1 deletion app/database.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
''' Database connexion '''

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
db = SQLAlchemy()
2 changes: 2 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
''' Database models '''

from datetime import datetime

from app.database import db
Expand Down
46 changes: 23 additions & 23 deletions app/templates/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ <h3>{{ instances_count }} container{% if instances_count > 1 %}s{% endif %} runn
<div class="row center full_width">
<div class="terminal full_width" style="overflow-x: auto;">
<h2 style="margin-top: 0; text-align: center;">All Instances</h2>

<div style="margin-bottom: 1em; display: flex; gap: 1em; align-items: center; flex-wrap: wrap;">
<div style="display: flex; align-items: center; gap: 0.5em; width: 38%; min-width: 350px;">
<div class="form-group" style="margin-bottom: 0; flex: 1; min-width: 0; width: 100%;">
<input
type="text"
id="search-input"
<input
type="text"
id="search-input"
placeholder="Search by team, username, image, or instance name..."
style="flex: 1; min-width: 0; width: 100%;"
>
</div>
<button
type="button"
id="clear-search"
<button
type="button"
id="clear-search"
class="clear-search-btn"
style="display: none; flex-shrink: 0;"
title="Clear search"
Expand All @@ -38,7 +38,7 @@ <h2 style="margin-top: 0; text-align: center;">All Instances</h2>
</button>
</div>
</div>

<table class="full_width">
<thead>
<tr>
Expand Down Expand Up @@ -117,7 +117,7 @@ <h2 style="margin: 0;" id="alert-title">Notification</h2>
// Search functionality
const searchInput = document.getElementById('search-input');
const clearSearchBtn = document.getElementById('clear-search');

function updateClearButton() {
if (clearSearchBtn && searchInput) {
if (searchInput.value && searchInput.value.trim() !== '') {
Expand All @@ -129,7 +129,7 @@ <h2 style="margin: 0;" id="alert-title">Notification</h2>
}
}
}

if (searchInput) {
searchInput.addEventListener('input', function(e) {
const query = e.target.value.toLowerCase().trim();
Expand Down Expand Up @@ -220,17 +220,17 @@ <h2 style="margin: 0;" id="alert-title">Notification</h2>

function filterAndRender(query) {
let filtered = allContainers;

if (query) {
filtered = allContainers.filter(container => {
const team = (container.team || '').toLowerCase();
const username = (container.username || '').toLowerCase();
const image = (container.image || '').toLowerCase();
const instanceName = (container.instance_name || '').toLowerCase();
return team.includes(query) ||
username.includes(query) ||
image.includes(query) ||

return team.includes(query) ||
username.includes(query) ||
image.includes(query) ||
instanceName.includes(query);
});
}
Expand Down Expand Up @@ -267,7 +267,7 @@ <h2 style="margin: 0;" id="alert-title">Notification</h2>
// Get current search query
const searchInput = document.getElementById('search-input');
const query = searchInput ? searchInput.value.toLowerCase().trim() : '';

// Filter and sort
let filtered = allContainers;
if (query) {
Expand All @@ -276,10 +276,10 @@ <h2 style="margin: 0;" id="alert-title">Notification</h2>
const username = (container.username || '').toLowerCase();
const image = (container.image || '').toLowerCase();
const instanceName = (container.instance_name || '').toLowerCase();
return team.includes(query) ||
username.includes(query) ||
image.includes(query) ||

return team.includes(query) ||
username.includes(query) ||
image.includes(query) ||
instanceName.includes(query);
});
}
Expand Down Expand Up @@ -361,17 +361,17 @@ <h2 style="margin: 0;" id="alert-title">Notification</h2>

titleEl.textContent = title;
messageEl.textContent = message;

// Remove previous styling classes
modalContent.classList.remove('alert-success', 'alert-error');

// Add appropriate styling based on title
if (title === 'Success') {
modalContent.classList.add('alert-success');
} else if (title === 'Error') {
modalContent.classList.add('alert-error');
}

modal.classList.add('show');

const close = () => {
Expand Down
6 changes: 3 additions & 3 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</nav>
{% endif %}
{% endblock %}

{% block modal %}
{% if session and session.get("verified") %}
<!-- Info Modal -->
Expand Down Expand Up @@ -56,7 +56,7 @@ <h2 style="margin: 0;">Information</h2>
{% block main %}
{% endblock %}
</main>

{% block scripts %}
{% if session and session.get("verified") %}
<script>
Expand Down Expand Up @@ -137,4 +137,4 @@ <h2 style="margin: 0;">Information</h2>
{% endif %}
{% endblock %}
</body>
</html>
</html>
Loading