Что нам стоит CTF устроить? Прокачиваем CTFd
Специально для паблика SMARTRHINO.
Как и обещал, сегодня мы не только исправим баг в панели настроек, но и добавим новый функционал.
Поехали!
0010. Исправляем баг
CTFd предоставляет возможность изменять тему и тело письма, которое отправляется пользователю.
Давайте, попробуем изменить формат сообщения Account Registration.
Изначально это выглядит следующим образом:
Внесём изменения:
Нажимаем на кнопку Update внизу страницы и видим следующее:
Тело письма не изменилось, вместо него подставилось значение по умолчанию, печально (
К счастью данная проблема решается не так уж и сложно. Открываем в браузере инспектор и смотрим, что к чему.
Я намеренно выделил на скриншоте две группы, так ошибку видно практически сразу.
А затаилась она в атрибуте name
компонента textarea
. Скорее всего данная ошибка вызвана невнимательностью во время копипасты.
Чтобы всё поехало, нужно изменить значение на successful_registration_email_body
, т.е. оно должно быть таким же, как у атрибута id
.
Под наш скальпель попадёт один-единственный html-шаблон, находящийся по пути CTFd/CTFd/themes/admin/templates/config/email.html
.
Исправляем досадную ошибку, выключаем контейнер, перезапускаем сервер, поднимаем контейнер и удостоверяемся, что всё работает должным образом.
0011. “Вас много, а я одна!”
CTFd имеет всего два типа пользователей: Admin и User. Первый имеет неограниченную власть и огромную ответственность.
Второй же может зарегаться, создать команду или присоединиться к уже существующей, и поиграть после начала соревнований.
Данный факт меня несколько расстроил и навёл на мысль: “А как же быть с ответственными за таски? Не давать же всем админские права, мало ли что..”.
После чего родилась идея: “Может, добавить новый тип пользователя с ограниченными правами админа?”.
Окей, погнали, но для начала стоит определиться с правами: данным пользователям можно создавать таски, редактировать их, при необходимости добавлять Hints и смотреть статистику решений.
0012. Ловим питона за хвост
Открываем PyCharm, создаём проект из существующего кода и начинаем дивное приключение.
Файлов и папок просто тьма, поэтому предлагаю заглянуть в самое очевидное место - admin/challenges.py
.
Практически сразу бросается в глаза строка №11:
from CTFd.utils.decorators import admins_only
Ага, декораторы! Спускаемся чуть ниже и видим его использование.
Так, понятно, разграничение прав пользователей реализовали с помощью декораторов.
Ну что же, добавим и мы свой декоратор, для этого надо отредактировать файл decorators/__init__.py
.
Нарекаем новый тип пользователя TaskDeployer.
def admins_or_task_deployers_only(f):
"""
Decorator that requires the user to be authenticated and an admin
:param f:
:return:
"""
@functools.wraps(f)
def admins_or_task_deployers_only_wrapper(*args, **kwargs):
if is_admin() or is_task_deployer():
return f(*args, **kwargs)
else:
if request.content_type == "application/json":
abort(403)
else:
return redirect(url_for("auth.login", next=request.full_path))
return admins_or_task_deployers_only_wrapper
Кроме того необходимо добавить проверку is_task_deployer
, для этого идём в файл user/__init__.py
:
def is_task_deployer():
if authed():
return session["type"] == "task_deployer"
else:
return False
Не забываем и про модели - models/__init__.py
:
class TaskDeployers(Users):
__tablename__ = "task_deployers"
__mapper_args__ = {"polymorphic_identity": "task_deployer"}
Общий смысл такой: ищём файлы, в которых есть декоратор @admins_only
или проверка is_admin
, и правим их, в соответствии с правами нового пользователя.
Правок довольно много, поэтому оставлю их под спойлером, вроде ничего не забыл.
Правки
-
models/__init__.py:
- (Line 374) add 'TaskDeployers' class
-
utils/user/__init__.py:
- (Line 38) add 'is_task_deployer' func
-
decorators/__init__.py:
- (Line 22) add assert 'is_task_deployer' in 'during_ctf_time_only_wrapper' func
- (Line 64) add assert 'is_task_deployer' in '_require_verified_emails' func
- (Line 120) add decorator 'admins_or_task_deployers_only'
-
admin/__init__.py:
- (Line 37) import func 'is_task_deployer'
- (Line 53) add assert 'is_task_deployer' in 'view' func
-
admin/challenges.py:
- (Line 11) import decorator 'admins_or_task_deployers_only'
- (Line 16) change decorator to 'admins_or_task_deployers_only' for 'challenges_listing' func
- (Line 24) change decorator to 'admins_or_task_deployers_only' for 'challenges_detail' func
- (Line 63) change decorator to 'admins_or_task_deployers_only' for 'challenges_new' func
-
admin/notifications.py:
- (Line 5) import decorator 'admins_or_task_deployers_only'
- (Line 10) change decorator to 'admins_or_task_deployers_only' for 'notifications' func
-
admin/scoreboard.py:
- (Line 5) import decorator 'admins_or_task_deployers_only'
- (Line 10) change decorator to 'admins_or_task_deployers_only' for 'scoreboard_listing' func
-
admin/statistics.py:
- (Line 5) import decorator 'admins_or_task_deployers_only'
- (Line 12) change decorator to 'admins_or_task_deployers_only' for 'statistics' func
-
admin/submissions.py:
- (Line 5) import decorator 'admins_or_task_deployers_only'
- (Line 12) change decorator to 'admins_or_task_deployers_only' for 'submissions_listing' func
-
api/v1/challenges.py:
- (Line 24) import decorator 'admins_or_task_deployers_only'
- (Line 34) import func 'is_task_deployer'
- (Line 68) add assert 'is_task_deployer' in 'get' func
- (Line 121) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 134) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 156) add assert 'is_task_deployer' in 'get' func
- (Line 183) add assert 'is_task_deployer' in 'get' func
- (Line 215) add assert 'is_task_deployer' in 'get' func
- (Line 263) add assert 'is_task_deployer' in 'get' func
- (Line 279) change decorator to 'admins_or_task_deployers_only' for 'patch' func
- (Line 288) change decorator to 'admins_or_task_deployers_only' for 'delete' func
- (Line 313) add assert 'is_task_deployer' in 'post' func
- (Line 424) add assert 'is_task_deployer' in 'post' func
- (Line 442) add assert 'is_task_deployer' in 'post' func
- (Line 511) add assert 'is_task_deployer' in 'get' func
- (Line 530) add assert 'is_task_deployer' in 'get' func
- (Line 551) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 568) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 585) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 601) change decorator to 'admins_or_task_deployers_only' for 'get' func
-
api/v1/files.py:
- (Line 7) import decorator 'admins_or_task_deployers_only'
- (Line 15) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 28) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 52) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 64) change decorator to 'admins_or_task_deployers_only' for 'delete' func
-
api/v1/flags.py:
- (Line 7) import decorator 'admins_or_task_deployers_only'
- (Line 15) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 26) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 48) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 68) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 82) change decorator to 'admins_or_task_deployers_only' for 'delete' func
- (Line 93) change decorator to 'admins_or_task_deployers_only' for 'patch' func
-
api/v1/hints.py:
- (Line 6) import decorator 'admins_or_task_deployers_only'
- (Line 7) import func 'is_task_deployer'
- (Line 15) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 26) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 60) add assert 'is_task_deployer' in 'get' func
- (Line 72) change decorator to 'admins_or_task_deployers_only' for 'patch' func
- (Line 91) change decorator to 'admins_or_task_deployers_only' for 'delete' func
-
api/v1/notifications.py:
- (Line 6) import decorator 'admins_or_task_deployers_only'
- (Line 24) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 63) change decorator to 'admins_or_task_deployers_only' for 'delete' func
-
api/v1/submissions.py:
- (Line 7) import decorator 'admins_or_task_deployers_only'
- (Line 17) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 34) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 59) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 71) change decorator to 'admins_or_task_deployers_only' for 'delete' func
-
api/v1/tags.py:
- (Line 6) import decorator 'admins_or_task_deployers_only'
- (Line 14) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 27) change decorator to 'admins_or_task_deployers_only' for 'post' func
- (Line 49) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 61) change decorator to 'admins_or_task_deployers_only' for 'patch' func
- (Line 79) change decorator to 'admins_or_task_deployers_only' for 'delete' func
-
api/v1/tokens.py:
- (Line 10) import func 'is_task_deployer'
- (Line 58) add assert 'is_task_deployer' in 'get' func
- (Line 76) add assert 'is_task_deployer' in 'delete' func
-
api/v1/unlocks.py:
- (Line 10) import decorator 'admins_or_task_deployers_only'
- (Line 23) change decorator to 'admins_or_task_deployers_only' for 'get' func
-
api/v1/statistics/challenges.py:
- (Line 7) import decorator 'admins_or_task_deployers_only'
- (Line 14) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 32) change decorator to 'admins_or_task_deployers_only' for 'get' func
- (Line 83) change decorator to 'admins_or_task_deployers_only' for 'get' func
-
api/v1/statistics/submissions.py:
- (Line 6) import decorator 'admins_or_task_deployers_only'
- (Line 11) change decorator to 'admins_or_task_deployers_only' for 'get' func
Остался лишь последний штрих - поправить html-шаблоны.
0013. Лучший язык программирования
Для начала добавим ограничение на доступные страницы, редактируем файл CTFd/CTFd/themes/admin/templates/base.html
.
Прокручиваем до 48 строки и вставляем проверку типа пользователя { % if type == 'admin' % }
:
{ % if type == 'admin' % }
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="true">Pages</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{ { url_for('admin.pages_listing') } }">All Pages</a>
<a class="dropdown-item" href="{ { url_for('admin.pages_new') } }">New Page</a>
</div>
</li>
<li class="nav-item"><a class="nav-link" href="{ { url_for('admin.users_listing') } }">Users</a></li>
{ % if get_config('user_mode') == 'teams' % }
<li class="nav-item"><a class="nav-link" href="{ { url_for('admin.teams_listing') } }">Teams</a></li>
{ % endif % }
{ % endif % }
<li class="nav-item"><a class="nav-link" href="{ { url_for('admin.scoreboard_listing') } }">Scoreboard</a></li>
<li class="nav-item"><a class="nav-link" href="{ { url_for('admin.challenges_listing') } }">Challenges</a></li>
<li class="nav-item dropdown">
<a href="#" class="nav-link dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="true">Submissions</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{ { url_for('admin.submissions_listing') } }">All Submissions</a>
<a class="dropdown-item" href="{ { url_for('admin.submissions_listing', submission_type='correct') } }">Correct Submissions</a>
<a class="dropdown-item" href="{ { url_for('admin.submissions_listing', submission_type='incorrect') } }">Wrong Submissions</a>
</div>
</li>
{ % if type == 'admin' % }
<li class="nav-item"><a class="nav-link" href="{ { url_for('admin.config') } }">Config</a></li>
{ % endif % }
Тем самым мы отстранили деплоеров от доступа к пользовательским данным и панели управления бордой.
Даём доступ (частичный) к админской панели - CTFd/CTFd/themes/core/templates/base.html
, строка 85.
Добавляем проверку or type == 'task_deployer'
:
{ % if type == 'admin' or type == 'task_deployer' % }
<li class="nav-item">
<a class="nav-link" href="{ { url_for('admin.view') } }">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Admin Panel">
<i class="fas fa-wrench d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-wrench pr-1"></i>Admin Panel
</span>
</a>
</li>
{ % endif % }
Данных пользователей может создать только админ, поэтому не забываем добавить опцию TaskDeployer в соответствующие формы.
Изменения коснутся двух файлов - CTFd/CTFd/themes/admin/templates/modals/users/create.html
и CTFd/CTFd/themes/admin/templates/modals/users/edit.html
.
В обоих файлах откатываемся на 45 строку и добавляем следующее:
<option value="task_deployer"{ % if user is defined and user.type == 'task_deployer' % } selected{ % endif % }>
Task Deployer
</option>
И наконец-то последняя правка - добавляем badge
для пользователей типа TaskDeployer.
Редактировать придётся тоже два файла - CTFd/CTFd/themes/admin/templates/users/user.html
(62 строка), CTFd/CTFd/themes/admin/templates/users/users.html
(114 строка):
{ % if user.type == 'task_deployer' % }
<span class="badge badge-primary">task deployer</span>
{ % endif % }
Как обычно перезагружаем сервер и контейнер, после чего можно взглянуть на результат.
Список пользователей
Добавление нового пользователя
0014. На этом всё!
Обсудить и задать вопросы можно в чате.
Спасибо за внимание!