
На последнем онлайн-CTF один парень из нашей команды убил 40 минут, вручную вбивая пароли в форму логина — 200 попыток, ноль результата, чистое страдание. Другой закрыл тот же таск за три минуты: накидал скрипт на десять строк с requests, натравил на словарь и получил флаг в терминал. Разница между ними — не объём знаний Python, а конкретный набор приёмов: отправить POST-запрос, вытащить токен из HTML, перебрать значения в цикле. Эти приёмы автоматизации CTF на Python и разберём — с рабочими скриптами, объяснением каждой строки и пометками, когда какой инструмент тащить.
Требования к окружению:
Четыре библиотеки покрывают порядка 90% сценариев автоматизации задач CTF:
| Библиотека | Для чего | Плюсы | Ограничения | Когда использовать |
|---|---|---|---|---|
| requests | HTTP-запросы, брутфорс форм | Простой синтаксис, сессии с куками | Нет работы с raw TCP | Web-задачи, API, формы логина |
| BeautifulSoup4 | Парсинг HTML-ответов | Гибкий поиск по тегам | Не исполняет JavaScript | Извлечение токенов, флагов, ссылок |
| pwntools | Binary exploitation, TCP-сервисы | Упаковка адресов, шеллкод, ROP | Полноценно только на Linux | PWN, network, бинарные протоколы |
| hashlib (stdlib) | Хеширование | Встроен в Python, ставить ничего не надо | Только вычисление хешей | Crypto-задачи, проверка хешей |
Установка занимает минуту. Создаёте виртуальное окружение командой python3 -m venv ctf-env, активируете через source ctf-env/bin/activate (Linux/macOS) и ставите зависимости: pip install requests beautifulsoup4 pwntools. Виртуальное окружение изолирует пакеты — если что-то сломается, удалите папку и начните заново.
По статусу поддержки: requests и pwntools — активно поддерживаемые проекты с регулярными обновлениями и тысячами звёзд на GitHub. BeautifulSoup4 — стабильная библиотека, которая почти не меняется, потому что делает свою работу давно и хорошо. Все три безопасно тянуть в рабочие скрипты.
Брутфорс формы авторизации — техника Password Guessing (T1110.001, тактика Credential Access по MITRE ATT&CK). В CTF она нужна на этапе Initial Access: организаторы дают веб-приложение с формой логина и словарь (или подсказку вроде «пароль — название города»). Задача — подобрать комбинацию и попасть на страницу с флагом. В реальном пентесте тот же приём работает при внутреннем тестировании, когда есть доступ к веб-интерфейсу без WAF и rate limiting.
Логика брутфорс CTF-скрипта на Python укладывается в четыре действия: загрузить список паролей, для каждого отправить POST-запрос, проверить ответ, остановиться при успехе.
Три момента, без которых скрипт не заработает:
requests.Session() — объект сессии сохраняет куки между запросами. Без него каждый POST будет восприниматься сервером как новый визитор, и токен авторизации потеряется.
Имена полей формы — их нужно вытащить из HTML. Откройте страницу логина, нажмите F12 в браузере, найдите элемент <form> и посмотрите атрибуты name у полей <input>. Типичные варианты: username и password, но бывают user, login, pass — копируйте точное значение.
Условие успеха — в CTF работает проверка от обратного. Если в теле ответа нет строки «Invalid», «Wrong» или «Error» — скорее всего, вход удался. Альтернатива: проверять response.status_code — код 302 (редирект) после POST обычно означает успешную авторизацию.
import requests
s = requests.Session()
url = "http://ctf.example.com/login"
with open("passwords.txt") as f:
for pwd in f:
pwd = pwd.strip()
r = s.post(url, data={"username": "admin", "password": pwd})
if "Invalid" not in r.text:
print(f"[+] Пароль найден: {pwd}")
break
Скрипт идёт по файлу построчно, убирает переносы строки через strip(), отправляет POST и проверяет ответ. При совпадении — выводит пароль и останавливается. Словарь на 10 000 строк прогоняется за 10–30 секунд в зависимости от задержки сервера.
Rate limiting — сервер блокирует IP после N неудачных попыток. В CTF встречается редко, но бывает. Обход: добавить time.sleep(0.3) между запросами или использовать прокси-ротацию (для CTF обычно избыточно).
CSRF-токен — если форма требует одноразовый токен, скрипт сломается без его предварительного извлечения. Решение — парсинг ответа, разберём в следующем разделе.
JavaScript-рендеринг — если форма работает через AJAX и DOM-манипуляции, простой POST не сработает. Нужен headless-браузер (Selenium, Playwright), но в большинстве CTF формы работают через стандартный HTML.
[Применимо: CTF web-задачи, внутренний пентест без WAF. Не применимо: внешний пентест с Cloudflare/rate limiting.]
Большинство web-задач CTF требуют не одного изолированного запроса, а цепочки: GET для получения страницы со скрытым полем, парсинг этого поля, POST с извлечённым значением. Без автоматического парсинга ответов HTTP такую цепочку не замкнёшь в цикле брутфорса. В терминологии MITRE ATT&CK это Automated Collection (T1119) — программный сбор данных из ответов сервера для дальнейшей эксплуатации.
Библиотека BeautifulSoup принимает HTML-строку и превращает её в дерево объектов с удобным поиском:
soup.find("input", {"name": "csrf_token"}) — находит первый <input> с атрибутом name="csrf_token" и возвращает объект, из которого можно вытащить ["value"]soup.find_all("a", href=True) — возвращает список всех ссылок на странице, полезно для краулинга и поиска скрытых эндпоинтовtag.text или tag.get_text() — извлекает текстовое содержимое тега, убирая HTML-разметкуТипичный CTF-сценарий — форма с CSRF-защитой. Без токена POST-запрос вернёт 403 или «Invalid token». Скрипт на Python для CTF решает проблему в два шага: GET для получения токена, POST для отправки данных:
from bs4 import BeautifulSoup
import requests
s = requests.Session()
page = s.get("http://ctf.example.com/login")
soup = BeautifulSoup(page.text, "html.parser")
token = soup.find("input", {"name": "csrf_token"})["value"]
r = s.post("http://ctf.example.com/login", data={
"csrf_token": token, "username": "admin", "password": "test"
})
При комбинации с брутфорсом этот блок оборачивается в цикл: перед каждой попыткой скрипт заново получает страницу, извлекает свежий токен и подставляет его в POST. Это замедляет перебор вдвое (два запроса вместо одного), но без этого шага брутфорс формы с CSRF-защитой просто не работает.
После успешного входа флаг обычно где-то в HTML. Стандартные форматы: flag{...}, CTF{...}, codeby{...}. Надёжный способ — регулярка: вызов re.search(r'flag\{[^}]+\}', response.text) найдёт флаг в любом месте страницы. Если формат известен из правил соревнования (а он почти всегда указан), такой подход работает быстрее ручного ковыряния в HTML.
Ещё один приём: если флаг спрятан в атрибуте тега (например, <div data-flag="flag{secret}">), BeautifulSoup извлечёт его через soup.find("div")["data-flag"]. Проверяйте не только текст страницы, но и атрибуты элементов — организаторы CTF любят прятать флаги в неожиданных местах.
Категории pwn и network в CTF работают через raw TCP: подключаешься к серверу на конкретном порту, получаешь текстовый или бинарный вывод и отправляешь payload. Библиотека requests тут бесполезна — она заточена под HTTP. Нужен либо стандартный модуль socket, либо pwntools.
По MITRE ATT&CK: работа с сетевыми сервисами через Python — Execution: Python (T1059.006), а эксплуатация уязвимости в удалённом сервисе — Exploit Public-Facing Application (T1190, Initial Access). Скрипты Python для CTF в категории pwn чаще всего нацелены на переполнение буфера, format string уязвимости или логические ошибки в серверном приложении.
Модуль socket из стандартной библиотеки даёт полный контроль над TCP-соединением: socket.connect(), socket.send(), socket.recv(). Код получается многословным — нужно вручную обрабатывать буферы, следить за кодировками и таймаутами. Для понимания основ — полезно. Для соревнований — слишком медленно.
pwntools оборачивает сокеты в удобный интерфейс. remote("host", port) создаёт TCP-подключение, process("./binary") запускает локальный бинарник. Оба возвращают объект с методами sendline(), recvuntil(), recvall(), interactive(). Плюс pwntools даёт функции упаковки адресов (p32(), p64()), генерации циклических паттернов (cyclic(), cyclic_find()) и класс ELF для анализа бинарников (из документации: ELF(path, checksec=True) — загружает файл, позволяет получить адреса функций, секции, проверить защиты).
Характерный CTF-сценарий: сервер на порту 1337 отправляет математическое выражение и ждёт ответ за 2 секунды. Руками не успеть — нужна автоматизация:
from pwn import *
r = remote("ctf.example.com", 1337)
line = r.recvuntil(b"= ?").decode()
expr = line.split(":")[1].replace("= ?", "").strip()
result = str(eval(expr)) # пример для демонстрации концепции
r.sendline(result.encode())
print(r.recvall().decode())
Шесть строк: подключение, чтение до маркера, извлечение выражения, вычисление, отправка ответа, получение флага. На чистом socket этот код растянулся бы строк на двадцать с обработкой буферов и таймаутов.
Для задач на переполнение буфера pwntools даёт класс ROP (из документации: ROP(elfs, base=None, badchars=b'')) для автоматического построения ROP-цепочек. Для format string эксплуатации — класс FmtStr с методами write(addr, data) и execute_writes(). Полный цикл решения pwn-задачи: загрузить бинарник через ELF('./vuln'), проверить защиты через checksec(), найти offset через cyclic() и cyclic_find(), собрать payload и отправить через process() (локально) или remote() (на сервер).
shellcraft и asm на macOS работают с ограничениями, на Windows — только через WSL2print как функция, bytes вместо str)[Применимо: CTF pwn/network, локальная эксплуатация бинарников. Не применимо: web-задачи, задачи с JavaScript-фронтендом.]
Криптографические CTF-задачи часто сводятся к подбору входных данных по известному хешу — это Password Cracking (T1110.002, Credential Access) или Deobfuscate/Decode Files or Information (T1140, Defense Evasion). Типичная формулировка: «дан MD5-хеш, найдите пароль из 5 строчных букв».
Для коротких паролей (4–6 символов) с быстрыми хешами (MD5, SHA-1) Python-скрипт — оптимальный инструмент. Написать и запустить его быстрее, чем возиться с hashcat, его правилами и масками. Модуль hashlib вычисляет хеш одной строкой: hashlib.md5(b"test").hexdigest(). Модуль itertools.product генерирует все комбинации символов заданной длины.
import hashlib
from itertools import product
from string import ascii_lowercase
target = "5d41402abc4b2a76b9719d911017c592"
for combo in product(ascii_lowercase, repeat=5):
word = "".join(combo)
if hashlib.md5(word.encode()).hexdigest() == target:
print(f"[+] Найдено: {word}")
break
Скрипт перебирает 26^5 = 11 881 376 комбинаций. На современном CPU это занимает от 10 до 60 секунд — зависит от позиции искомого слова в алфавитном порядке.
Если длина пароля больше 6–7 символов или алфавит включает цифры и спецсимволы — количество комбинаций уходит в миллиарды. Для 8 символов из ascii_lowercase — 208 миллиардов вариантов, Python будет молотить часами. Тут переключайтесь на hashcat с GPU-ускорением или John the Ripper. Для алгоритмов с намеренным замедлением (bcrypt, Argon2) перебор на Python нереалистичен даже для 4 символов — нужны специализированные инструменты и железо.
Отдельная история — кастомные хеш-схемы вроде sha256(md5(password) + salt). Hashcat поддерживает десятки стандартных режимов, но кастомную схему проще реализовать коротким скриптом на Python, чем описывать через hashcat-правила. В CTF кастомные схемы встречаются регулярно — и вот тут скрипты Python для CTF незаменимы.
После каждого CTF-соревнования участники публикуют writeup — разборы решений с кодом. Умение быстро прочитать чужой Python exploit скрипт и адаптировать его под новую задачу — навык, который прокачивается только практикой. Три вещи, на которые смотрите в первую очередь:
Импорты — по первым строкам сразу видно категорию задачи. from pwn import * — pwn или network. import requests — web. from Crypto.Cipher import AES — crypto с нестандартной криптографией. Это определяет, какие знания нужны для понимания решения.
Точка взаимодействия с сервером — ищите remote(), process(), requests.post(), socket.connect(). Это центральная часть скрипта: всё остальное — подготовка данных до взаимодействия и обработка ответа после.
Payload — строка или байтовый массив, который отправляется серверу. В pwn-задачах это обычно конструкция из b"A" * offset + p64(address). В web-задачах — словарь параметров в data={}. Понимание структуры payload — ключ к пониманию уязвимости.
Автоматизация задач CTF — это не про знание синтаксиса, а про последовательность действий:
Шаг 1 — Разведка вручную. Откройте задачу в браузере, изучите в DevTools (F12) структуру запросов: какие URL, какие методы, какие параметры. Отправьте тестовый запрос через Burp Suite или curl — убедитесь, что понимаете логику приложения.
Шаг 2 — Минимальный скрипт. Воспроизведите ручной запрос в Python: один вызов requests.get() или requests.post() с теми же параметрами. Сравните ответ с тем, что видели в браузере. Если отличается — копайте в сторону кук, заголовков или User-Agent.
Шаг 3 — Цикл. Оберните запрос в for или while: если нужен брутфорс — перебирайте значения из файла; если нужна цепочка — выстраивайте последовательность GET → парсинг → POST. Добавьте проверку условия успеха.
Шаг 4 — Отладка. Скрипт не находит флаг? Вставьте print(r.status_code, r.text[:300]) после каждого запроса. Частые причины: опечатка в имени поля формы, отсутствие CSRF-токена, редирект, который requests не отрабатывает (попробуйте allow_redirects=False и проверьте заголовок Location).
Шаг 5 — Сохранение. Работающий скрипт — это шаблон для будущих задач. Складывайте решения в папку ~/ctf-scripts/ с именами вида bruteforce_csrf.py, hash_md5_brute.py. Через десять соревнований у вас будет библиотека, которую нужно только адаптировать под новый URL.
CTF-задачи среднего уровня часто требуют объединить несколько приёмов в одном скрипте. Характерный сценарий: GET-запрос получает страницу, BeautifulSoup извлекает закодированный токен, base64.b64decode() раскодирует его, hashlib.sha256() вычисляет контрольную сумму, POST отправляет результат. Четыре библиотеки, один скрипт, один флаг. Переключение между инструментами внутри скрипта — нормальная практика: requests и BeautifulSoup работают на уровне HTTP, hashlib — на уровне данных, pwntools — на уровне TCP. Конфликтов между ними нет, они дополняют друг друга.
CTF scripting tutorial по сути сводится к одной мысли: каждая задача — это последовательность «получить данные → обработать → отправить результат». Разница между категориями — в инструменте на каждом шаге. Web → requests + BeautifulSoup. Pwn → pwntools. Crypto → hashlib + itertools. Forensics → struct + binascii для работы с бинарными форматами. Научитесь выбирать правильный инструмент под задачу — и перестанете убивать 40 минут на ручной перебор паролей.
За год участия в CTF я пришёл к выводу, который многим покажется спорным: новичкам не нужен курс по Python перед первым соревнованием. Нужен один решённый таск. Берёте самую простую web-задачу, открываете документацию requests, пишете скрипт из пяти строк — и вот вы уже понимаете, зачем нужны сессии и что такое POST-параметры. Следующий таск — добавляете BeautifulSoup. Третий — пробуете pwntools. Каждая решённая задача учит конкретному приёму, который невозможно забыть, потому что он привязан к реальному результату, а не к абстрактному упражнению из учебника. Типичная ошибка — пытаться освоить весь инструментарий до старта. Люди неделями изучают декораторы и метаклассы, а потом не могут отправить POST-запрос с куками. Обратный подход работает быстрее: начни с задачи, подтяни ровно столько теории, сколько нужно для решения, двигайся к следующей. Через двадцать тасков у вас будет арсенал из рабочих скриптов и понимание, когда какая библиотека нужна. Если базового Python и понимания HTTP пока не хватает даже для первого шага — на IB Basics можно закрыть эту основу за пару месяцев, с упором на практические задачи.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...