Главная / Блог / Python для CTF: шаблоны скриптов для веб-запросов, байтов и crypto-задач

12 мин.00

Python для CTF: шаблоны скриптов для веб-запросов, байтов и crypto-задач

Python для CTF: шаблоны скриптов для веб-запросов, байтов и crypto-задач

Python для CTF: шаблоны скриптов для веб-запросов, байтов и crypto-задач

На NahamConCTF задача Flaskmetal Alchemist — time-based SQL injection через SQLAlchemy — сожрала три часа на подбор рабочего payload. А сам скрипт-эксплойт? 15 строк: requests.post() в цикле, замер elapsed.total_seconds(), посимвольное извлечение флага. Пропорция типичная: 80% времени уходит на «а что тут вообще происходит», и 20% — на код. Python закрывает три направления: автоматизацию HTTP-взаимодействия, низкоуровневую работу с байтами и решение типовых crypto-задач. Ниже — готовые шаблоны, которые можно скопировать, адаптировать под конкретный таск и складывать в свой репозиторий. Через пару соревнований этот репозиторий начнёт экономить часы.

Требования к окружению

Перед первым скриптом убедитесь, что всё на месте:

  • ОС: любая (Linux, macOS, Windows). Для pwntools лучше Linux или WSL2 — под Windows часть модулей (GDB-интеграция, shellcraft) просто не работает
  • Python: 3.8 и выше. Проверка: python3 --version
  • RAM: от 2 ГБ. Скрипты легковесные, узкое место — сам интерпретатор и сетевые задержки
  • Зависимости: три пакета покрывают 90% CTF-задач — pip install requests pwntools pycryptodome

requests — HTTP-клиент с автоматическим управлением cookies и сессиями. pwntools (репозиторий gallopsled/pwntools на GitHub, активно поддерживается) — фреймворк для binary exploitation и TCP-взаимодействия. pycryptodome — криптографические примитивы: RSA, AES, DES и десятки других алгоритмов. Для задач с base64, hashlib, struct и binascii ничего ставить не нужно — всё в стандартной библиотеке Python.

Работаете на Windows без WSL и не хотите тянуть pwntools? Для TCP-задач подойдёт стандартный socket — возможностей меньше, зато зависимостей ноль.

Работа с байтами в Python: основа CTF-скриптинга

Половина ошибок в первых CTF-скриптах — путаница между str и bytes. Python 3 жёстко разделяет текстовые строки (str, Unicode) и сырые байтовые данные (bytes). Попытка написать b"hello" + "world" выбросит TypeError. Это не баг — это защита от вас самих. В CTF данные почти всегда приходят в байтовом виде, и смешивать типы — верный путь к часу отладки на ровном месте.

Преобразования, которые нужны на каждом соревновании:

# hex → bytes → строка
flag_hex = "4354467b68656c6c6f7d"
flag_bytes = bytes.fromhex(flag_hex)   # b'CTF{hello}'
flag_str = flag_bytes.decode('utf-8')  # 'CTF{hello}'

# строка → bytes → hex → int
data = b'CTF{hello}'
as_hex = data.hex()                       # '4354467b68656c6c6f7d'
as_int = int.from_bytes(data[:3], 'big')  # 4412742
back = as_int.to_bytes(3, 'big')          # b'CTF'

Разберём по строкам. bytes.fromhex() принимает hex-строку без префикса 0x и возвращает байтовый объект. .decode('utf-8') превращает байты в читаемый текст — но только если данные действительно текстовые. Если в байтах бинарный мусор, .decode() упадёт с UnicodeDecodeError. Тогда берите .decode('latin-1') — этот кодек проглотит любой байт от 0x00 до 0xFF, маппя каждый на символ.

int.from_bytes() незаменим в crypto-задачах, где числа хранятся как байтовые последовательности. Второй аргумент — 'big' или 'little' — задаёт порядок байтов: big-endian — старший байт первый, little-endian — младший. В RSA-задачах на CTF почти всегда big-endian.

Для работы с бинарными форматами (сетевые пакеты, файловые заголовки) есть модуль struct. Вызов struct.unpack('>I', data[:4]) распакует первые 4 байта как unsigned int в big-endian формате. Символ > — big-endian, I — unsigned int (4 байта). Минимальная таблица форматов: B — 1 байт, H — 2 байта, I — 4 байта, Q — 8 байт. Для большинства CTF-форматов этого за глаза.

binascii дополняет картину: binascii.unhexlify(b'4354467b7d') возвращает b'CTF{}'. Разница с bytes.fromhex() — binascii-версия принимает и отдаёт bytes, что удобнее в цепочках, где всё остаётся в байтовом виде.

Для быстрой проверки гипотезы прямо в терминале: python3 -c "print(bytes.fromhex('4354467b7d'))" — без файла, без IDE, результат за секунду.

Автоматизация CTF-задач через requests

Большинство web-задач на CTF сводятся к одному из трёх сценариев: перебор параметров (brute-force PIN-кода, имени файла, значения cookie), отправка вредоносного payload (SQLi, SSTI, XXE) или анализ поведения сервера (timing attack, различия в длине ответов). Во всех трёх случаях базовый инструмент — requests.Session().

Зачем Session(), а не голый requests.get()? Сессия сохраняет cookies между запросами. В CTF это критично: часто нужно сначала залогиниться, получить session cookie, а потом слать запросы уже с ней. Без сессии придётся руками протаскивать cookies через каждый вызов — и это та ещё возня.

Шаблон: brute-force с анализом ответа

import requests

s = requests.Session()
url = "http://target.ctf:8080/login"

for pin in range(10000):
    r = s.post(url, data={"pin": f"{pin:04d}"})
    if "Invalid" not in r.text:
        print(f"[+] PIN: {pin:04d}")
        print(r.text[:200])
        break

Скрипт перебирает четырёхзначные PIN-коды, отправляя POST на каждой итерации. Условие остановки — отсутствие слова "Invalid" в ответе. Форматирование f"{pin:04d}" добавляет ведущие нули: 0001, 0042, 0999. Срез .text[:200] выводит начало ответа — обычно там и лежит флаг.

Что менять под конкретную задачу:

  • URL и endpoint — заменяем на целевой
  • Параметры — data={"pin": ...} на нужные поля формы. Если сервер ждёт JSON — вместо data= пишите json={"key": "val"}, тогда requests сам выставит Content-Type: application/json
  • Условие успеха — вместо "Invalid" not in r.text может быть r.status_code != 403, "flag" in r.text.lower() или len(r.text) > 100
  • Заголовки — s.headers.update({"Authorization": "Bearer token123"}) добавит заголовок ко всем последующим запросам сессии

Timing-атаки и посимвольное извлечение

Для задач с time-based SQLi (как в Flaskmetal Alchemist, описанной в writeup'е shellbr3ak) ключевое отличие — проверяем не содержимое ответа, а время. Автор того writeup'а использовал randomblob() в SQLite для создания искусственной задержки: если условие истинно, сервер генерирует 500 миллионов случайных байт, что занимает несколько секунд. Проверка в скрипте: if int(r.elapsed.total_seconds()) >= 3 — длинный ответ означает «символ угадан». По этой схеме флаг вытаскивается посимвольно.

Такие задачи — прямое отражение техники Exploit Public-Facing Application (T1190, Initial Access) по MITRE ATT&CK. Перебор параметров через автоматизированные HTTP-запросы — Password Cracking (T1110.002, Credential Access). Понимание этих паттернов на CTF даёт контекст для работы с реальными уязвимостями веб-приложений, тем, что OWASP относит к категории A03:2021 Injection.

Типовые грабли новичков при автоматизации CTF web-задач:

  • Забытый Content-Type — сервер ждёт JSON, а requests.post(data=...) шлёт application/x-www-form-urlencoded. Используйте json= вместо data=
  • Rate-limiting — сервер отвечает 429 или блокирует IP после N запросов. Добавьте time.sleep(0.1) между итерациями
  • HTTPS с самоподписанным сертификатом — requests.get(url, verify=False) отключает проверку. В CTF это нормально
  • Кодировка ответа — если r.text содержит мусор, попробуйте r.content (сырые байты) и декодируйте вручную

Место в цепочке CTF-решения

Web-скрипт — обычно не первый шаг. Типичная последовательность:

  1. Разведка — открыть URL, осмотреть HTML-код, проверить robots.txt, заглянуть в DevTools (cookies, заголовки, скрытые формы)
  2. Гипотеза — определить тип уязвимости: SQLi, SSTI, brute-force, IDOR, XXE
  3. Ручная проверка — один-два запроса через Burp Suite или curl
  4. Автоматизация — написать скрипт для перебора или извлечения данных
  5. Извлечение флага — парсинг ответа, декодирование при необходимости

Скрипт появляется на шаге 4, когда гипотеза уже подтверждена. Автоматизировать с нуля, не понимая что именно автоматизируешь — пустая трата времени. Я видел, как люди запускают sqlmap на каждую форму подряд, не заглянув в исходник страницы. Не надо так.

pwntools: примеры для TCP-задач

Когда задача выглядит как nc challenge.ctf 1337 — это TCP-сервис, не HTTP. Тут requests бесполезен. Нужен pwntools, заточенный под binary exploitation и сетевое взаимодействие.

Базовый паттерн: from pwn import * импортирует всё. r = remote('challenge.ctf', 1337) устанавливает TCP-соединение. r.recvline() читает строку от сервера, r.sendline(b'answer') отправляет ответ с переводом строки. Когда нужно прочитать «до конкретного маркера» — r.recvuntil(b': ') остановится на двоеточии с пробелом.

Для PPC-задач (Professional Programming and Coding — задачи, где нужно автоматизировать ответы на вопросы сервера) типичный цикл: получить строку, распарсить задание (математическое выражение, перевод из одной системы счисления в другую, реверс строки), отправить ответ. После N успешных раундов сервер выдаёт флаг. Завершающий вызов r.interactive() переключает в ручной режим — видите всё, что сервер шлёт после автоматической части.

Когда pwntools не нужен: если в задаче URL — берите requests. Если нестандартный протокол и нельзя поставить pwntools (ограниченная среда) — стандартный socket из stdlib.

Инструмент Преимущества Ограничения Когда использовать Когда не использовать
requests Cookies, редиректы, сессии — автоматически Только HTTP/HTTPS Web: SQLi, brute-force, SSRF Binary, raw TCP
pwntools TCP/UDP, send/recv, GDB-интеграция Тяжёлый (~50 МБ), Windows — частично Pwn, PPC, TCP-сервисы Простые HTTP-задачи
socket (stdlib) Ноль зависимостей, полный контроль Ручная обработка буферов и таймаутов Нестандартные протоколы, минимальная среда Когда есть requests или pwntools

Crypto-задачи CTF: XOR, base64-цепочки и hashlib

Категория crypto на CTF для начинающих — в основном три типа задач: однобайтовый XOR-шифр, многослойное кодирование и задачи на хеши. Python покрывает все три стандартной библиотекой плюс PyCryptodome для продвинутых алгоритмов.

Однобайтовый XOR: brute-force за секунду

XOR-шифрование с одним ключевым байтом — классика CTF начального уровня. Каждый байт открытого текста XOR'ится с одним и тем же значением ключа (от 0 до 255). Для расшифровки достаточно перебрать все 256 вариантов — это мгновенно.

import base64

ciphertext = base64.b64decode("GhkWFRYMChcaFRYMFgod")

for key in range(256):
    plaintext = bytes([b ^ key for b in ciphertext])
    try:
        text = plaintext.decode('ascii')
        if text.isprintable():
            print(f"Key=0x{key:02x}: {text}")
    except UnicodeDecodeError:
        continue

Что здесь происходит: bytes([b ^ key for b in ciphertext]) создаёт новый байтовый объект, XOR'я каждый байт шифротекста с кандидатом-ключом. .decode('ascii') пытается интерпретировать результат — если хотя бы один байт вне диапазона 0-127, исключение перехватывается и скрипт идёт дальше. .isprintable() дополнительно отсеивает управляющие символы.

Если формат флага известен (например, CTF{...}), фильтрация ещё проще: if plaintext.startswith(b'CTF{'). Для многобайтовых XOR-ключей (repeating-key XOR) подход другой: шифротекст разбивается на N подпоследовательностей по длине ключа, каждая расшифровывается как однобайтовый XOR. Длину ключа можно прикинуть через индекс совпадений или расстояние Хэмминга между блоками шифротекста.

Многослойное декодирование

На CTF часто встречается цепочка кодирований — по типу матрёшки: данные обёрнуты base64, внутри gzip-архив, а внутри XOR-шифрованный текст. Подход всегда одинаковый: снимаем слои один за другим.

Стандартная библиотека Python закрывает это полностью. base64.b64decode() снимает base64, gzip.decompress() распаковывает gzip, XOR-расшифровка — однострочник из предыдущего раздела. Сигнатура помогает определить следующий слой: если base64-данные начинаются с H4sIA — это gzip внутри base64 (байты \x1f\x8b\x08 в base64 кодируются именно так). Видите после декодирования ASCII-символы из набора A-Za-z0-9+/=? Скорее всего, ещё один слой base64.

Такие задачи — отражение реальной техники Deobfuscate/Decode Files or Information (T1140, Defense Evasion) по MITRE ATT&CK. Вредоносные payload'ы тоже прячутся за слоями кодирования — Standard Encoding (T1132.001). Навык быстрого снятия слоёв пригодится и в CTF, и в incident response.

hashlib и PyCryptodome

Задачи вида «найдите строку, MD5 которой начинается с 00000» решаются тупым перебором: hashlib.md5(f"prefix{i}".encode()).hexdigest() в цикле. Модуль hashlib поддерживает MD5, SHA-1, SHA-256 и десятки других алгоритмов из коробки.

Когда стандартной библиотеки мало — RSA с конкретными параметрами, AES-CBC padding oracle, генерация ключей — подключается PyCryptodome: from Crypto.PublicKey import RSA для работы с RSA-ключами, from Crypto.Cipher import AES для блочных шифров. Но это уже уровень задач medium-hard, где одним шаблоном не обойтись — нужно понимать математику конкретного алгоритма.

Шаблоны скриптов CTF: какой выбрать

Перед написанием скрипта определите тип задачи. Decision tree:

Условие Инструмент Шаблон
В задаче URL или HTTP-endpoint requests.Session() Brute-force / payload injection
В задаче nc host port pwntools remote() PPC / binary interaction
Дан файл с закодированными данными base64 + gzip + binascii Многослойное декодирование
Дан шифротекст, предположительно XOR bytes + XOR brute Однобайтовый или repeating-key XOR
Нужно найти прообраз хеша hashlib в цикле Hash brute-force
Дан бинарный файл для разбора struct.unpack() Парсинг заголовков и полей
Даны n, e, c (RSA-параметры) PyCryptodome + math RSA-атаки (factorization, small e)

Если задача не попадает ни в одну категорию — начните с python3 -c "..." в терминале. Однострочник часто заменяет полноценный скрипт на этапе проверки гипотезы.

Про организацию: заведите папку ctf-templates/ и после каждого CTF складывайте туда рабочие скрипты, очищенные от конкретных URL и значений. Через два-три соревнования у вас будет набор, покрывающий 60-70% задач без написания кода с нуля.

Ограничения скриптового подхода

Шаблоны из этой статьи покрывают типовые CTF-задачи начального и среднего уровня. Где они не работают:

  • Binary exploitation (buffer overflow, ROP-цепочки) — pwntools помогает с взаимодействием, но payload конструируется вручную через ROP(), p64(), shellcraft. Отдельная дисциплина с порогом входа выше, чем web или crypto
  • Reverse engineering — Python не заменяет Ghidra или GDB. Скрипт может автоматизировать подбор входных данных к бинарю, но для статического анализа нужен дизассемблер
  • Web с WAF и фильтрацией — если сервер режет payload'ы на уровне WAF-правил, автоматический перебор будет бесполезен. Тут нужна ручная работа с bypass-техниками
  • Нестабильная инфраструктура — CTF-серверы бывают перегружены. Добавляйте try/except с повторными попытками и time.sleep() между запросами, иначе скрипт ляжет на сетевом таймауте

Главное ограничение — скрипты не заменяют понимание уязвимости. Если не понимаете, почему time-based SQLi работает или что делает XOR на уровне битов, шаблон не спасёт. Автоматизация ускоряет эксплуатацию, но не заменяет анализ. Сначала разберитесь в механике вручную, потом автоматизируйте.

Навык, который отличает CTF-игрока с опытом от новичка — не знание конкретных эксплойтов и не скорость набора кода. Это умение за тридцать секунд определить, к какому классу относится задача, и достать из своего репозитория нужный шаблон. На одном из CTF видел, как команда из двух человек обошла команду из восьми — у них был отлаженный набор из десяти скриптов-заготовок, а вторая команда каждый раз открывала пустой файл.

Проблема большинства «python для CTF» туториалов — они показывают решение конкретной задачи, а не переиспользуемый паттерн. Человек прочитал writeup, повторил код, решил ровно ту же задачу — и через неделю не может адаптировать скрипт под похожую, но чуть другую. Шаблоны в этой статье намеренно минимальны: чем меньше специфики конкретного CTF, тем проще переиспользовать. Начните с папки шаблонов и привычки чистить скрипты после каждого соревнования — убирать хардкод URL, заменять конкретные значения на переменные. Через три-четыре CTF соберётся инструментарий, который реально экономит время. Если вы в самом начале пути и хочется выстроить фундамент системно — на курсе IB Basics в Codeby School эту базу дают от сетей до первых практических задач.

🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».

Поделиться

0 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...