// Lab-based writeup в формате статьи, смоделировал 3 CTF задачи и решил их скриптом!
Что вас ждёт в статье:
1. Введение
2. Типовые задачи, где автоматизация решает
3. Первая практика: «Noise Pipeline»
4. Вторая практика: «Access Denied»
5. Третья практика: «Lost Artifact»
6. Как использовать такие скрипты в реальной работе?
7. Что это даёт Blue Team
8. Заключение
1. Введение
В CTF и прикладном пентесте системы почти никогда не падают от одного красивого приёма.Чаще всего работа выглядит куда приземлённее. Перед исследователем оказывается набор однотипных действий и данных, в которых нужно терпеливо вычленить смысл. Почти всегда среда имеет сотни строк мусорного вывода, повторяющиеся ответы сервиса, каталоги с десятками файлов, среди которых спрятана одна важная деталь. Теоретически всё это можно разобрать вручную, но на практике именно здесь быстрее всего заканчивается внимание и аккуратность.
Python в таких ситуациях оказывается полезным рабочим инструментом. Несколько десятков строк кода позволяют превратить хаотичное ковыряние в последовательный процесс. Появляется понятная логика действий, проверяемые гипотезы и результат, который можно воспроизвести в любой момент. Машина берёт на себя повторяющуюся работу, а человек остаётся там, где действительно нужен анализ.
Эта статья посвящена именно таким задачам. Без сложных эксплойтов и больших фреймворков. Речь пойдёт о небольших скриптах, которые снимают рутину и позволяют работать с данными системно. Декодирование шумных потоков, перебор пароля на пути к эндпоинту и анализ файловых свалок после инцидента - это примеры задач, которые плохо масштабируются вручную и отлично ложатся на автоматизацию.
В качестве практической части я собрал три лабораторные задачи, оформленные в духе классических CTF, но основанные на типичных реальных сценариях. Каждая из них начинается с ручной разведки и довольно быстро упирается в предел человеческого подхода. Дальше появляется необходимость формализовать действия и переложить их на код. На этих примерах видно, как возникает идея автоматизации, почему первые версии скриптов почти всегда наивные и как они постепенно превращаются в полезный инструмент.
Важно и то, что такой взгляд полезен не только атакующей стороне. Для Blue Team эти же ситуации хорошо показывают, насколько легко автоматизируется эксплуатация слабых мест.
Любая защита, рассчитанная на человеческий темп и поведение, начинает сбоить, когда одно и то же действие выполняется сотни или тысячи раз без усталости и ошибок. Именно в таких условиях становятся заметны логические допущения, избыточные доверия и неаккуратная обработка данных, которые в спокойном режиме могут годами оставаться незамеченными.
2. Типовые задачи, где автоматизация решает
Одна из самых частых ситуаций в СTF - это работа с данными, которые намеренно или случайно приведены в нечитаемый вид. Различные кодировки, несколько слоёв преобразований, обфускация ради обфускации. Вручную можно раскрутить одну строку, максимум две, но когда таких сообщений десятки или сотни, ручной анализ превращается в бессмысленное повторение одних и тех же действий. Скрипт здесь позволяет сэкономить огромное количество времени.Далее выделю перебор, как самый очевидный формат, где без автоматизации никуда. Параметры, значения, эндпоинты, последовательности запросов. Почти всегда на старте кажется, что пространство поиска небольшое и всё можно проверить руками. На практике же вариантов оказывается больше, ответы сервиса ведут себя не всегда очевидно, а различия между ошибкой и успехом бывают минимальными. Автоматизация позволяет действовать системно. Фиксировать попытки, отслеживать скорость, реагировать на изменения в ответах и вовремя остановиться, когда цель достигнута. Этому блоку посвящена наша вторая лаба.
Ну и напоследок продемонстрирую автоматизированный анализ эмулированного дампа пользовательских файлов, куда я по классике включил логи, временные каталоги, кэши и бинарные данные. Большая часть таких данных не несёт полезной информации, но среди них может скрываться один артефакт, который нам и нужен. Просматривать всё это вручную - занятие неблагодарное и крайне ненадёжное. Скрипт же может пройтись по всей структуре, отфильтровать очевидный мусор, вытащить строки интереса, попытаться декодировать подозрительные фрагменты и свести результат в удобный вид.
Во всех этих случаях автоматизация не заменяет анализ, а освобождает для него место. Код берёт на себя механическую часть работы, а мы сидим и смотрим за его выполнением результата. Именно поэтому такие скрипты становятся универсальным инструментом как в CTF, так и в реальном пентесте или DFIR‑разборе.
Ну что же, начнём практический модуль с загадочным образом зашифрованных данных, в которых нам нужно будет найти флаг любым путём!
3. Первая практика: «Noise Pipeline»
Я смоделировал следующие условия CTF задачи:
Был произведёт перехват потока данных, который, по всей видимости, используется для передачи управляющих сообщений между компонентами системы.
Каждое сообщение выглядит как случайный набор символов и на первый взгляд не содержит полезной информации.
Однако анализ одного из пакетов показал, что данные проходят через несколько этапов преобразования перед отправкой.
Задача: восстановить исходное содержимое сообщений и найти флаг.
Формат флага: CTF{...}
Для начала произведём знакомство с артефактом:
Работа с задачей начинается с минимальной разведки предоставленных данных. В нашем распоряжении находится единственный артефакт - каталог с файлом messages.txt. Проверка содержимого директории показывает, что дополнительных вспомогательных файлов или скриптов нет, а вся полезная нагрузка сосредоточена в одном объекте.
Размер файла около 10 килобайт указывает на то, что внутри содержится достаточно большое количество сообщений. Это сразу наводит на мысль, что задача рассчитана не на разбор одного-двух примеров, а на массовую обработку однотипных данных. Такой объём ещё можно просмотреть вручную.
Первый взгляд на содержимое файла
На следующем этапе логично заглянуть внутрь файла и понять, с чем именно предстоит работать.
Первичный просмотр файла показывает, что данные не являются случайными. Все строки имеют одинаковую длину, состоят исключительно из символов Base64‑алфавита и начинаются с сигнатуры H4sIA. Эта последовательность хорошо известна и характерна для gzip‑архивов, закодированных в Base64.
Из этого можно сделать предварительный вывод, что перед нами многострочный поток данных, прошедший как минимум два этапа преобразования: Base64‑кодирование, за которым следует сжатие gzip. При этом наличие большого количества строк указывает на то, что каждая строка представляет собой отдельное сообщение, обработанное по одной и той же схеме.
Пока что можно попробовать вручную декодировать одну строку, чтобы понять характер вложенных данных. Однако очевидно, что при большом количестве сообщений такой подход не эффективен.
Формируем ключевую гипотезу лабораторной работы: перед нами не одна сложная загадка, а простой алгоритм, многократно применённый к разным сообщениям. А значит, решаться он должен кодом, а не руками.
Проверка гипотезы о Base64 и gzip
После первичного анализа возникает логичное предположение, что строки в файле представляют собой gzip‑сжатые данные, дополнительно закодированные в Base64. Эту гипотезу можно быстро проверить, взяв одну строку и последовательно применив оба преобразования.
Полученный результат на первый взгляд выглядит как бессмысленный набор символов и явно не является читаемым текстом. Тем не менее этот шаг принципиально важен, ведь команда выполнилась без ошибок, а значит оба предположенных слоя действительно присутствуют и корректно декодируются.
Именно отсутствие ошибок и появление бинарного вывода подтверждают гипотезу. Если бы порядок преобразований был неверным или сигнатура оказалась ложной, мы получили бы сообщение об ошибке декодирования или повреждённого архива. Вместо этого мы видим выходные данные, просто представленные не в привычном текстовом виде.
Это позволяет сделать следующий вывод:
Base64 и gzip - лишь внешние слои шума, а внутри скрывается ещё одно преобразование.
Судя по характеру вывода, речь идёт не о полноценном шифровании, а о простой, массово применённой обфускации, например XOR с фиксированным ключом, побитовых операциях или замене символов. Такие техники часто используются именно для того, чтобы сломать автоматическое чтение, но при этом оставаться тривиальными в реализации.
Переход от символов к байтам
На предыдущем этапе мы убедились, что после снятия слоёв Base64 и gzip данные остаются нечитаемыми. Однако попытка интерпретировать бинарный поток как текст - заведомо слабая стратегия. Чтобы понять, с чем мы имеем дело на самом деле, нужно посмотреть на байты напрямую.
Для этого выведем результат в виде hex‑дампа:
После распаковки gzip данные по‑прежнему не читаются, но hex‑дамп даёт куда более полезную картину. Поток тоже выглядит упорядоченным. значения байтов лежат в ограниченном диапазоне, отсутствует характерный для сжатых или зашифрованных данных белый шум, а структура повторяется.
Такой профиль данных типичен для простой XOR‑обфускации с фиксированным ключом. Это распространённый приём в CTF‑задачах, он почти ничего не даёт с точки зрения криптографии, но эффективно ломает наивные попытки анализа и вынуждает перейти к байтовому уровню.
На этом этапе важно, что нам больше не нужно гадать вслепую. Цепочка преобразований уже вырисовывается чётко:
Base64 => gzip => XOR.
Остаётся определить ключ. И здесь начинают появляться первые подсказки.
При попытках ручного анализа и частичного перебора становится заметно, что результат расшифровки подозрительно складывается в осмысленные ASCII‑символы, а в выходных данных начинает угадываться слово key!
Это сильный сигнал о том, что используется короткий, человекочитаемый XOR‑ключ, применённый ко всем сообщениям одинаково.
Проверка гипотезы об XOR‑ключе
После анализа бинарного потока возникает обоснованное предположение, что поверх gzip‑данных применена простая XOR‑обфускация с коротким фиксированным ключом. Чтобы не усложнять и не писать код раньше времени, эту гипотезу удобно проверить на одной строке.
В качестве пробного ключа используется "key".
Применим XOR к результату распаковки:
Результат сразу становится осмысленным текстом, победа!!
Этот вывод однозначно подтверждает всю цепочку преобразований. Данные представляют собой обычные текстовые сообщения, последовательно прошедшие через XOR‑обфускацию с ключом key, затем сжатие gzip и, в финале, Base64‑кодирование. Каждый слой по отдельности тривиален, но вместе они эффективно маскируют содержимое при поверхностном анализе.
На этом этапе задача перестаёт быть просто исследовательской, алгоритм полностью понятен.
Однако вручную применять эту цепочку к сотне строк бессмысленно.
Настало время оформить полученные шаги в полноценный Python скрипт и прогнать его по всему файлу,
чтобы восстановить все сообщения и найти среди них флаг!!
Написание и применение скрипта
После того как цепочка преобразований была полностью восстановлена, оставалось зафиксировать её в виде простого скрипта и применить ко всем строкам входного файла.
На этом этапе задача окончательно превращается в инженерную, нужно аккуратно и воспроизводимо снять все уровни обработки данных и мы это прописываем в следующем виде:
Скрипт реализует ровно ту же последовательность шагов, которая ранее проверялась вручную на одном сообщении. Для каждой строки файла выполняется Base64‑декодирование, затем распаковка gzip‑данных, после чего применяется XOR‑декодирование с найденным ключом. Все операции выполняются над байтовыми данными, без попыток преждевременно интерпретировать их как текст.
XOR‑декодирование вынесено в отдельную функцию и реализовано максимально прямо.
Каждый байт входных данных складывается по XOR с соответствующим байтом ключа, который повторяется циклически. Такой подход подчёркивает, что здесь нет никакой криптографии, и это лишь простая обфускация, одинаково применённая ко всем сообщениям.
Полученный результат преобразуется в строку с использованием кодировки latin1. Это осознанный выбор, ведь такая кодировка позволяет корректно отобразить любые байты и избежать ошибок декодирования, что особенно важно при работе с нестандартными или частично повреждёнными данными.
Файл messages.txt читается построчно, пустые строки отбрасываются, а каждая строка обрабатывается независимо от остальных. Это делает скрипт устойчивым к ошибкам, даже если одно сообщение окажется некорректным, обработка всего файла не прервётся. Для удобства анализа вывод нумеруется, что позволяет легко сопоставлять исходные и расшифрованные сообщения.
В результате весь шумный поток данных за один запуск превращается в читаемый лог, внутри которого уже можно искать аномалии, управляющие команды или сам флаг.
Именно на этом этапе становится очевидно, насколько ручной подход был бы неэффективен, то, что заняло бы десятки минут однообразной работы, сводится к одному запуску скрипта.
Так давайте же воспроизведём пуск:
Наблюдаем, что после запуска скрипта весь файл декодируется в читаемый текстовый поток, состоящий в основном из однотипных служебных сообщений. Большая часть вывода представляет собой регулярные heartbeat и status update, которые формируют фоновый шум и не несут самостоятельной ценности. Именно из‑за такого объёма однотипных данных ручной анализ на исходном этапе был бы практически бесполезен.
Чтобы упростить дальнейшую работу, результат декодирования перенаправляется в отдельный файл. Это позволяет применять стандартные инструменты анализа текста и быстро искать интересующие маркеры:
Поиск по шаблону сразу выявляет строку, выбивающуюся из общего потока!
Выводы по первой лабе:
Первая лабораторная показала, как легко потеряться в море одинаковых служебных сообщений.
Флаг здесь не прятался в сложной криптографии, он был зарыт под тонной однообразного шума, ну тупо как сокровище.
Без автоматизации искать его вручную было бы тупой пыткой, десятки почти идентичных строк съедают внимание и время, анализаторские способности сходят на нет. Скрипт, который мы написали, снял всю рутину, превратил хаос в аккуратный поток и позволил сосредоточиться только на том, что реально важно, т.е. на флаге.
В итоге CTF{noise_pipeline_working} всплыл почти сам собой, и мы убедились, что правильный инструмент - это важнейшая в работе вещь.
Лаба наглядно показывает, почему знание python и автоматизации являютя обязательными навыками в любом CTF или пентесте, без этих знаний уныние съест тебя быстрее любого задания.
P.S.
У Burp Suite есть схожий функционал через Decoder, Intruder или цепочки трансформаций можно вручную прогонять Base64 => Gzip => XOR и смотреть результат.
Для разового анализа это работает и даже удобно на этапе гипотез.
Но в рамках настоящего CTF такой подход быстро начинает проигрывать скрипту. Burp остаётся интерактивным инструментом, а не средством массовой обработки, ведь он плохо масштабируется на десятки строк, требует ручных действий и не даёт воспроизводимого пайплайна.
Скрипт же автоматически обрабатывает весь файл целиком, чётко фиксирует порядок декодирования, легко модифицируется под другой ключ или формат и позволяет сразу применять фильтрацию (grep, strings, регулярки). В итоге анализ превращается не в кнопконажимательство, а в подконтрольный повторяемый и контролируемый процесс.
P.P.S.
Сам скрипт, пользуйтесь на здоровье:
Python:
#!/usr/bin/env python3
import base64
import gzip
KEY = b"key" # найденный XOR-ключ
def xor_data(data: bytes, key: bytes) -> bytes:
return bytes(b ^ key[i % len(key)] for i, b in enumerate(data))
def decode_line(line: str) -> str:
# 1. base64
compressed = base64.b64decode(line)
# 2. gzip
xored = gzip.decompress(compressed)
# 3. XOR
decoded = xor_data(xored, KEY)
# 4. перевод в текст
return decoded.decode("latin1")
def main():
with open("messages.txt", "r", encoding="utf-8") as f:
lines = [line.strip() for line in f if line.strip()]
for i, line in enumerate(lines, 1):
try:
decoded = decode_line(line)
print(f"[{i:03}] {decoded}")
except Exception as e:
print(f"[{i:03}] <decode error: {e}>")
if __name__ == "__main__":
main()
4. Вторая практика: «Access Denied»
Я смоделировал следующие условия CTF задачи:В ходе тестирования веб-приложения был обнаружен служебный flask эндпоинт, предназначенный для внутреннего доступа. Эндпоинт принимает параметр password и возвращает различный ответ в зависимости от корректности значения.
Известно, что:
- пароль состоит из шести символов
- используются только цифры
- при неверном значении сервер отвечает стандартным сообщением об ошибке
Задача: получить доступ к защищённому разделу и извлечь флаг.
Формат флага: CTF{...}
Решение:
Сначала проверим, как эндпоинт реагирует на ручные запросы. Для этого используем curl и отправим несколько произвольных значений параметра password:
Во всех случаях сервер отвечает одинаковым сообщением access denied, независимо от переданного значения. Ответ не меняется ни по содержимому, ни по формату, что говорит об отсутствии дополнительных защитных механизмов вроде капчи, блокировок или рандомизации ошибок.
Такое поведение означает, что, во-первых, сервер обрабатывает каждый запрос детерминированно, а во-вторых, он не пытается скрыть логику проверки пароля.
При этом одинаковый ответ делает ручной перебор бессмысленным, визуально отличить корректное значение от некорректного невозможно, а количество вариантов слишком велико для перебора вручную.
Следовательно, дальнейшая работа должна быть сосредоточена на автоматизации и поиске косвенных различий в поведении сервера, которые не видны при поверхностном тестировании.
Автоматизация перебора
Поскольку эндпоинт не ограничивает количество попыток и всегда возвращает одинаковое сообщение об ошибке, становится возможной полная автоматизация перебора пароля.
Это классическая ситуация для CTF-задач:
защита формально есть, но она никак не мешает машинной атаке.
Ручной перебор сразу отбрасывается как непрактичный, ведь диапазон значений слишком большой, а визуальных различий в ответах нет. Следовательно, единственный разумный путь в том, чтобы написать собственный брутфорс скрипт!
Что я и сделал:
Скрипт начинается с подключения библиотек requests, concurrent.futures, threading и time, которые используются для отправки HTTP-запросов, организации многопоточности, синхронизации потоков и замера скорости работы.
Переменная URL задаёт адрес целевого эндпоинта, а MAX_WORKERS = 20 ограничивает количество одновременно работающих потоков. Это сделано для стабильности системы, пробовал разные значения и 20 потоков - это всего лишь потолок моей слабенькой VM, на которую я в процессе брутфорса старался не дышать греха подальше.
found_event - это глобальный флаг, который используется как сигнал остановки. Когда правильный пароль найден, он устанавливается, и все потоки прекращают дальнейшую работу.
attempts - счётчик количества выполненных запросов,
lock - объект синхронизации, защищающий доступ к счётчику в многопоточном окружении,
start_time - точка отсчёта для расчёта скорости перебора.
Функция try_password - это рабочая единица атаки. Она принимает один вариант пароля, отправляет его серверу через POST-запрос и анализирует ответ. Если глобальный флаг found_event уже установлен, функция сразу завершает работу, чтобы не выполнять лишние запросы. После отправки запроса счётчик попыток увеличивается в потокобезопасном режиме. Если в ответе сервера отсутствует строка access denied, это означает, что пароль корректный и флаг завершения устанавливается, и функция возвращает найденный пароль и ответ сервера.
Функция status_printer работает в отдельном фоновом потоке и служит исключительно для мониторинга. Она регулярно считывает количество попыток, вычисляет скорость перебора в запросах в секунду и выводит эту информацию в консоль в режиме реального времени. Это позволяет с интересом наблюдать эффективность атаки.
Далее создаётся пул потоков ThreadPoolExecutor, внутри которого генерируются все шестизначные числовые комбинации с форматированием f"{i:06d}". Каждое значение передаётся в пул как отдельная задача. Таким образом формируется параллельный поток запросов к эндпоинту.
Как только один из потоков возвращает успешный результат, основной цикл перехватывает его, выводит найденный пароль и ответ сервера, после чего перебор немедленно останавливается.
В результате скрипт реализует полный автоматизированный брутфорс без ручного участия, демонстрируя уязвимость эндпоинта, в котором нет лимита попыток, капчи, тайминговых задержек и механизмов защиты от автоматизации. Такая ситуация превращает задачу подбора пароля в чисто техническую операцию, полностью решаемую кодом.
Запуск скрипта
После запуска начинается активный перебор пароля. В консоль в реальном времени выводится статистика работы. Добавил этот элемент, чтобы скрипт не смотрелся совсем уныло без вывода данных, а так циферки бодро крутятся, процесс наблюдения занимательный.
Показатель Attempts же отражает то, сколько запросов уже было отправлено на эндпоинт, Speed демонстрирует фактическую пропускную способность атаки с учётом сетевых задержек и обработки запросов сервером. В данном случае скорость порядка ~170 запросов в секунду, далее она может меняться, всё зависит от пропускной способности flask.
Скрипт сработал!
Примерно через 10 минут работы скрипт зафиксировал изменение поведения сервера.
На фоне стандартного потока ответов access denied появляется иной ответ, что сразу перехватывается логикой проверки в коде.
В этот момент перебор останавливается, выводится найденное значение пароля и полный ответ сервера. Это наглядно демонстрирует ключевой момент задачи, что защита, рассчитанная на ручной ввод и ограниченное число попыток, полностью разваливается при автоматизированном подходе. Шестизначный числовой пароль при отсутствии лимита попыток, задержек и блокировок превращается в задачу на ожидание, в нашем случае около десяти минут при скорости ~150 запросов в секунду.
Полученный ответ сервера сразу содержит искомый флаг CTF{awesome_bruteforce}, что логически завершает лабораторную работу и подчёркивает, насколько тривиальной становится эксплуатация подобных эндпоинтов при использовании даже простого Python-скрипта.
В логах сервера картина наглядная:
P.S. сам скрипт:
Python:
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
import time
URL = "..."
MAX_WORKERS = 20
found_event = threading.Event()
attempts = 0
lock = threading.Lock()
start_time = time.time()
def try_password(password):
global attempts
if found_event.is_set():
return None
try:
r = requests.post(URL, data={"password": password}, timeout=3)
except Exception:
return None
with lock:
attempts += 1
if "access denied" not in r.text.lower():
found_event.set()
return password, r.text
return None
def status_printer():
while not found_event.is_set():
with lock:
count = attempts
elapsed = time.time() - start_time
rate = count / elapsed if elapsed else 0
print(
f"\r[*] Attempts: {count} | Speed: {rate:.0f} req/s",
end="",
flush=True
)
time.sleep(0.5)
threading.Thread(target=status_printer, daemon=True).start()
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = []
for i in range(1_000_000):
password = f"{i:06d}"
futures.append(executor.submit(try_password, password))
for future in as_completed(futures):
result = future.result()
if result:
pwd, response = result
print("\n\n[+] PASSWORD FOUND!")
print(f"[+] password = {pwd}")
print(f"[+] server response:\n{response}")
break
5. Третья практика: «Lost Artifact»
Здесь условия CTF задачи следующие:Задача 3. Lost Artifact
После инцидента на одном из серверов был получен дамп пользовательских данных. Данные представляют собой набор файлов различного формата и назначения. Предполагается, что атакующий оставил в системе артефакт, содержащий флаг, однако его точное расположение неизвестно.
Задача: проанализировать предоставленный набор файлов и найти флаг.
Формат флага: CTF{...}
Решение:
Начинаем с первичного осмотра предоставленного дампа, не пытаясь сразу что‑то угадать, просто оцениваем масштаб и структуру данных. Вывод tree показывает типичную для серверной системы файловую свалку, где несколько каталогов с разным назначением и десятки файлов внутри каждого из них.
Здесь сразу бросается в глаза объём. 86 файлов, распределённых по логам, кэшу, временным данным, бинарным блобам и пользовательским профилям. Ручной просмотр в таком масштабе быстро теряет смысл: большая часть этих файлов почти наверняка не содержит ничего интересного, но пропустить один важный артефакт очень легко.
Структура при этом выглядит понятно. Каталоги logs, cache, tmp и backups - это классический фон, который в реальных инцидентах обычно не несёт прямой полезной нагрузки. binaries и user_data выглядят потенциально интереснее, но и там данные разбиты на множество однотипных файлов, названия которых не дают никаких прямых подсказок.
Наш следующий логичный шаг состоит в том, чтобы понять, с чем вообще предстоит работать на уровне типов данных. Для этого без лишних раздумий используем file по всем файлам в дампе:
Команда сразу показывает, что лог‑файлы представляют собой обычный ASCII‑текст без сжатия, шифрования или бинарной обфускации.
Значится, что логи можно читать, грепать и анализировать стандартными средствами, не тратя время на предварительное декодирование. В контексте задачи это означает, что если атакующий действительно оставил след в логах, то он, скорее всего, будет либо в явном виде, либо спрятан среди служебных сообщений.
Однако даже здесь ручной просмотр не масштабируется. Тридцать лог‑файлов с однотипными названиями - это классическая ловушка для CTF плэера, вроде бы всё текстовое и понятное, но читать эти журналы по очереди чирьевато пропуском чего-то важного. Поэтому логи рассматриваются как первый кандидат на массовый автоматизированный поиск, чем мы займёмся чуть позже, а пока ещё поближе познакомимся с рабочей средой:
После беглого просмотра одного из логов становится понятно, с чем мы имеем дело. Файл app_1.log содержит ровно одну строку служебного сообщения вида [INFO] user login success id=… без деталей, без полезного контекста и без каких‑либо аномалий.
Чтобы убедиться, что это не единичный случай, выполняем поиск по всем логам:
Результат ожидаемо однообразный, каждый файл содержит однотипную запись с одинаковой структурой и разными идентификаторами пользователей. Никаких ошибок, отладочных сообщений, странных строк или следов посторонней активности не обнаружено.
На этом этапе директория логов исключается нами из дальнейшего осмотра.
Логи просто всегда проверяются в подобных задачах в числе первых, ведь это стандартная практика как в CTF, так и в реальных расследованиях. Здесь же они выполняют роль фонового шума, т.е. формально корректные, но информационно пустые записи, не содержащие ни флага, ни намёков на него.
Короче, двигаемся дальше, к более подозрительным частям дампа, где вероятность налутать артефакт значительно выше:
Смотрим хэши и бэкапы
Дальше логично проверить хэши, именно туда в реальных системах часто утекают временные артефакты или следы активности.
Просмотр типов файлов в каталоге cache показывает однородную картину, все объекты определяются утилитой file как абстрактные данные без сигнатур известных форматов.
Для быстрой проверки гипотезы о скрытом текстовом содержимом применяем strings:
Результат нам ничего не даёт, только набор случайных символов, короткие фрагменты без читаемых слов, структур или повторяющихся маркеров. Это типичное бинарное наполнение, имеем только кэш, или мусор, не предназначенный для прямого чтения человеком.
С практической точки зрения это позволяет уверенно исключить папку кэшей из подозрения. Даже если данные там имеют смысл для приложения, вероятность того, что флаг лежит в явном виде среди бинарного кэша, минимальна, а дальнейший анализ потребовал бы сурового реверса, что редко встречается в CTF, однако всё равно придётся лезть в бинарники, эту папку ещё не трогали.
Только сначала посмотрим бэкапы, хотя в них и редко можно что-то найти:
Формально присутствуют в структуре дампа, но оказываются пустым, как мы и подозревали.
Ну всё, последний шаг, перед тем как лезть в бинарные файлы и писать код, проведём максимально простой и дешёвый чек. Иногда флаг лежит в дампе в открытом виде, временных файлах или мб он случайно был забыт разработчиком. Поэтому осуществляем поиск строки CTF{ по всему каталогу:
Ответа не последовало, вот мы и нагрэпались…
Резюмируем ручные попытки найти флаг:
Он скорее всего скрыт как бинарник, скорее всего в папке bin, в ручную его искать, как иголку в стоге сена, бессмысленно.
Значит пишем сканирующий скрипт, который также будет декодить Base64, а только потом ловко искать CTF по всему дампу для уверенности:
Скрипт должен проходить по всему дампу, начиная с корневого каталога lost_artifact. Для этого используется os.walk, который гарантирует, что ни один файл, независимо от глубины вложенности, не будет пропущен. Это важно, потому что артефакт редко лежит на поверхности.
Все файлы открываются в бинарном режиме. Содержимое файлов читается построчно, но именно как байты.
Далее каждая строка сначала очищается от переводов строки и пробелов, после чего проверяется регулярным выражением. Нас интересуют только те фрагменты, которые похожи на Base64: допустимый алфавит, достаточная длина, отсутствие посторонних символов. Это сразу отсекает подавляющее большинство бинарного мусора и резко сокращает объём данных для анализа.
Если строка проходит эту проверку, предпринимается попытка строгого Base64‑декодирования с validate=True. Это ещё один фильтр, любые случайные совпадения по алфавиту, но не являющиеся реальным Base64, отбрасываются автоматически.
Уже после декодирования выполняется единственная целевая проверка, т.е. поиск декодированной строки CTF{.
При успешном совпадении скрипт сразу сообщает о находке, указывает путь к файлу и выводит декодированное содержимое.
Отдельно важно, что весь код обёрнут в обработку исключений. Любые ошибки чтения, битые файлы или неожиданные форматы не валят выполнение целиком, скрипт просто идёт дальше.
Запуск скрипта
Мдэм.
Пусть для вас это будет очередным напоминанием о том, что рабочую среду и инструменты нужно разделять. Бросил я этот скрипт прямо в дамп на автомате и по итогу он нашёл сам себя, как пёс, таки догнавший свой хвост.
Чиним и запускаем:
А флаг то, оказывается лежал в логах!!
Несмотря на то что каталог журналов был осмотрен нами вручную, и на первый взгляд не содержал ничего интересного, флаг в итоге там и оказался, аккуратно спрятанный в виде закодированного фрагмента внутри одного из файлов.
Это типичная ситуация как для CTF, так и для реальных расследований. Видимый анализ почти никогда не даёт прямого результата. Данные зачастую выглядят скучно, однотипно и чисто, а артефакт маскируется под обычный служебный шум. Без автоматизации скриптом на питончике такой файл легко пропустить, особенно когда десятки логов выглядят одинаково.
Именно здесь и оправдывает себя написанный скрипт. Он последовательно проверяет весь дамп, одинаково внимательно относясь и к логам, и к бинарникам, и к временным данным.
В результате флаг CTF{lost_artifact_found} извлекается корректно не благодаря удаче, а за счёт машинной точности, которая здесь и зарешала!
P.S. сам скрипт:
Python:
import os
import base64
import re
ROOT_DIR = "lost_artifact"
b64_regex = re.compile(rb'^[A-Za-z0-9+/=]{20,}$')
def try_base64_decode(data):
try:
decoded = base64.b64decode(data, validate=True)
return decoded
except Exception:
return None
for root, dirs, files in os.walk(ROOT_DIR):
for name in files:
path = os.path.join(root, name)
try:
with open(path, "rb") as f:
for line in f:
line = line.strip()
if not line:
continue
if not b64_regex.match(line):
continue
decoded = try_base64_decode(line)
if not decoded:
continue
if b"CTF{" in decoded:
print("\n[+] FLAG FOUND!")
print(f"[+] File: {path}")
print("[+] Decoded content:")
print(decoded.decode(errors="replace"))
except Exception:
continue
6. Как использовать такие скрипты в реальной работе?
В реальной работе такие скрипты почти никогда не существуют как одноразовые поделки. Наоборот, именно из них со временем складывается личный набор инструментов спеца.Каждая подобная задача - это и есть заготовка для дальнейшей работы, крайне полезное занятие, в общем. В следующий раз поменяются входные данные, но логика остаётся той же, и вместо переписывания решения с нуля достаточно слегка допилить уже готовый код.
На практике, по моему мнению, эти скрипты лучше всего подходят для начальных этапов разведки или пост-эксплуатации, т.е. для пентеста и форензики.
Во время пентеста они позволяют быстро проверить гипотезы, как мы это делали в каждой из лаб. В форензике мы задействовали подходы для первичного просеивания дампов и логов, когда важно быстро выделить потенциально значимые артефакты из общей массы данных.
И в отличие от ручных действий, скрипт можно запустить повторно, приложить к отчёту или использовать спустя месяцы на схожем кейсе. Это превращает хаотичный анализ в процесс, который можно объяснить, проверить и масштабировать.
Короче, это вполне достойный рабочий подход, потому что видимый анализ почти никогда не даёт результат сразу, данные почти всегда спрятаны хитрее, чем кажется на первый взгляд. И именно поэтому автоматизация выступает, как базовое умение, что мы и поняли, проведя 3 практические работы.
7. Что это даёт Blue Team
Для Blue Team знание обращения с python скриптами даёт понимание того, какие ошибки усиливают риск компрометации системы. А напрямую из дэфэнсов применяются они, например, DFIRами (как в 3 лабе).Давайте будем отталкиваться даже от моих лабораторных примеров.
В первой лабе мы видели, как простое XOR-кодирование с коротким ключом оказалось тривиально, потому что ключ был коротким и человекочитаемым. В реальной инфраструктуре защита должна быть серьёзной, криптографически стойкой и не поддаваться простому перебору или автоматизации, иначе аналогичные скрипты легко выдают содержимое даже без глубокого анализа ситуации атакующим.
В задаче с перебором пароля для внутреннего эндпоинта мы буквально заставили скрипт за десяток минут перебрать сто тысяч вариантов при скорости ~150 запросов/с.
Очевидно, что в любой среде такой доступ должен быть защищён как минимум элементарными лимитами количества попыток, капчами, двухфакторная аутентификациями и ограничениями по IP.
Вы, наверное, думаете, что эта задача тривиальная и уже большинством предприятий закрытая.
Действительно, брутфорсить фэйсбук юзеров, как в сериале "Мистер Робот" сейчас уже невозможно, однако в современной промышленности и инфраструктуре с безопасностью систем аутентификации до сих пор наблюдаются большие проблемы.
И проблемы эти, когда обнаруживаются недоброжелателями, стоят куда дороже, чем привычный пользовательский взлом соцсетей.
Например, В конце июля 2025 года была произведена масштабная кибератака на Аэрофлот.
Технические подробности полностью до конца не раскрыты официально, но известно, что злоумышленники находились внутри корпоративной сети в течение примерно года и получили доступ к ключевым системам, включая базы данных, CRM и служебные серверы, после чего выгрузили порядка 22 ТБ информации и уничтожили около 7 000 физических и виртуальных серверов. Им это удалость благодаря использованию аэрофлотом уязвимого и устаревшего Windows XP и слабым паролям внутри системы, которые по словам хакеров не обновлялись с 2022 года.
Пароли, кто-знает, может и просто забрутфорсили, учитывая обнаруженные бреши в безопасности авиакомпании, можно допустить и такую теорию. Но глобально я описал этот кейс для вас чтобы выработать понимания того, что настоящая атака имеет куда больше уровней, чем учебное задание, не всё бьётся в перебор паролей, хотя и этот приём на практике может часто встречаться.
//Нашёл для вас подробный технический разбор, если стало интересно:
Ссылка скрыта от гостей
В итоге, ~60 рейсов были отменены или задержаны, как внутренние, так и международные.
Это серьёзно нарушило работу аэропорта Шереметьево и других узлов перевозчика.
Экономический урон для компании также оказался значительным, помимо прямых потерь из-за отмен и задержек рейсов, оценочных затрат на восстановление систем и потерь выручки, эксперты называют сотни миллионов рублей ущерба, учитывая сбои в операциях и репутационные потери бизнеса.
Можно сделать вывод, что Blue Team важно думать не только о сложных угрозах, но и об элементарных защитных практиках по отдельности, чтобы атакующие не могли автоматизировать их на лету. Ведь, вероятно, без подобранного пароля, хакеры бы не увели столько чувствительных данных об авиакомпании.
А скрипты вроде тех, что я писал в рамках статьи легко масштабируются и видоизменяются, они подходят как для CTF, так и для проверки реальных сервисов на слабые места, т.е. такой скрипт в руках пентестера, вероятно, мог бы частично предотвратить атаку, если пофантазировать.
Ну важно тоже понимать, что я приводил упрощённые задачи для автоматизации, это учебный материал, в реальности таски куда сложнее и скрипты выглядят страшнее, однако не бойтесь вникать в эту тему, знания такого характера получать интересно, а их применение высокооплачиваемо!
8. Заключение
Тема python скриптов на самом деле очень жизненная и вы это поймёте на практике. Пока данных мало, можно что-то поискать руками, погрепать, открыть файлик, прикинуть на глаз. Но как только появляется объём, шум и куча однотипных артефактов, то ручной анализ превращается в бессмысленное залипание в терминал.Скрипты здесь решают задачу по недопущению пропуск важной информации. Машине всё равно, сколько файлов, строк или попыток, она просто делает работу. А юзер в это время думает, куда копать дальше, а не проверяет сотый файл подряд. В третьей лабе это особенно хорошо видно, ведь логи были просмотрены вручную, выводы сделаны корректные, но флаг всё равно оказался там, просто в закодированном виде. Без автоматизации его можно было бы не увидеть вообще.
Спасибо за внимание и удачи в построении автоматизации!
Последнее редактирование: