
За два последних CTF-сезона я хронометрировал каждое рутинное действие: ручной запуск nmap, копипаст открытых портов в блокнот, запуск gobuster по каждому HTTP-порту — 7-8 минут на одну цель. При 12 машинах в соревновании это полтора часа, потраченные не на анализ уязвимостей, а на набивание одних и тех же команд. Один bash-скрипт на 9 строк сократил цикл разведки до 20 секунд на машину.
Bash скрипты для CTF — не программирование в классическом смысле, а склейка утилит, которые вы и так запускаете руками, в автоматическую цепочку. Ниже — три рабочих скрипта с разбором каждого флага, набор one-liner-ов для парсинга и конкретные ошибки, на которых буксует каждый новичок.
Bash есть на любой Linux-системе. На урезанных CTF-контейнерах и минимальных Docker-образах Python может отсутствовать, но sh, grep, awk и sed найдутся даже в BusyBox. На одном из недавних заданий удалённая машина содержала только sh и awk — этого хватило, чтобы прошерстить файловую систему, вытянуть конфиги и обнаружить SUID-бинарники. Подробнее — в нашем статье о создание ctf заданий.
Скорость создания — второй аргумент. Связать nmap, извлечение портов и gobuster — шесть строк на bash. Аналогичная штука на Python с argparse, обработкой исключений и вызовами subprocess.run() — проект на 50+ строк. Когда таймер показывает два часа на десять задач, этот разрыв решает всё.
Третий момент — прямое соответствие техникам MITRE ATT&CK. Запуск shell-скриптов на целевой системе маппится на T1059.004 Unix Shell (тактика Execution). Разведка файловой системы через find и ls — T1083 File and Directory Discovery (тактика Discovery). Сбор системных данных через uname, id, cat /etc/os-release — T1082 System Information Discovery. Перебор паролей через curl в цикле — T1110.001 Password Guessing (тактика Credential Access).
В репозитории Atomic Red Team для T1059.004 есть тесты «Create and Execute Bash Shell Script» и «Harvest SUID executable files» — оба выполняются через sh на Linux. На CTF вы делаете ровно то же: пишете скрипт, запускаете, собираете данные. Понимание маппинга помогает не только на соревнованиях, но и при написании отчётов по реальным пентестам — вы сразу указываете TTP, а не просто «запустил nmap».
Автоматизация CTF задач через bash экономит когнитивный ресурс. Вместо набивания одних и тех же команд вы думаете над логикой задачи. Это отличает участника, закрывающего 12 заданий за соревнование, от того, кто закрывает 4.
Перед первым скриптом проверьте три вещи. Версия bash: bash --version — подойдёт любая 4.x и выше. На macOS по умолчанию стоит 3.2 из-за лицензии — обновите через brew install bash. На Windows — WSL2 с любым дистрибутивом.
Утилиты: скрипты из статьи работают с nmap, gobuster, curl, grep, awk, sed и wc. В Kali и Parrot OS всё стоит из коробки. На минимальных дистрибутивах доставьте через apt install nmap curl gobuster.
Рабочая директория: mkdir ~/ctf && cd ~/ctf. Каждый скрипт начинается с shebang-строки #!/bin/bash — она указывает системе интерпретатор. После создания файла — chmod +x script.sh, иначе получите Permission denied. Альтернатива — запуск через bash script.sh, но при частом использовании это менее удобно. Редактор — любой: nano для начинающих, vim для готовых к модальному режиму.
Самая частая рутина на CTF — просканировать порты, определить сервисы, проверить веб-директории. Три команды, которые всегда идут друг за другом, — идеальный кандидат для автоматизации. Логика: принимает IP-адрес, создаёт папку под результаты, запускает nmap. Находит HTTP-порт — автоматически натравливает gobuster. Не находит — ограничивается nmap-этапом.
#!/bin/bash
set -euo pipefail
[ -z "${1:-}" ] && echo "Использование: $0 <IP>" && exit 1
IP="$1"; DIR="recon_$IP"; mkdir -p "$DIR"
nmap -sV -sC -oN "$DIR/nmap.txt" "$IP"
WEBPORT=$(grep -E '/tcp.*http' "$DIR/nmap.txt" | grep -oE '^[0-9]+' | head -1 || true)
[ -n "$WEBPORT" ] && gobuster dir -u "http://$IP:$WEBPORT" \
-w /usr/share/wordlists/dirb/common.txt -o "$DIR/dirs.txt" 2>/dev/null
echo "[+] Результаты: ./$DIR/"
Разберём построчно.
set -euo pipefail — страховка от тихих ошибок. Флаг -e завершает скрипт при любой команде с ненулевым кодом возврата. Флаг -u ловит обращения к неопределённым переменным — без него опечатка в имени переменной вернёт пустую строку вместо ошибки. Флаг -o pipefail ловит сбои в пайпах: без него false | echo ok молча вернёт успех. По данным hadess.io, эти три флага «prevent silent failures that lead to incorrect results». На CTF такие «тихие» сбои особенно мерзкие — вы не замечаете проблему и парсите пустой файл.
Конструкция [ -z "${1:-}" ] проверяет, передан ли аргумент. Обратите внимание: ${1:-} вместо голого $1. Конструкция :- возвращает пустую строку, если переменная не задана, и предотвращает ошибку при активном set -u. Без этой проверки nmap запустится без цели и зависнет в ожидании ввода.
Переменная DIR формирует имя папки вида recon_10.10.10.5 — результаты каждой машины хранятся раздельно. mkdir -p создаёт директорию без ошибки, если она уже существует.
Флаги nmap -sV -sC — определение версий сервисов плюс дефолтные NSE-скрипты. Без них nmap покажет только номера открытых портов без контекста — для CTF этого мало. Флаг -oN сохраняет вывод в текстовом формате (Normal output), пригодном для парсинга.
Ключевая конструкция — извлечение HTTP-порта. grep -E '/tcp.*http' находит строки вида 80/tcp open http. Затем grep -oE '^[0-9]+' вытаскивает номер порта из начала строки. head -1 берёт первый HTTP-порт. Если nmap HTTP не нашёл, || true гарантирует, что pipeline не уронит скрипт из-за pipefail — переменная WEBPORT останется пустой и блок с gobuster просто не выполнится.
Перенаправление 2>/dev/null подавляет прогресс-бар gobuster, который засоряет терминал при автоматическом запуске. Результаты при этом сохраняются в dirs.txt.
Для запуска на нескольких целях: while read -r ip; do ./recon.sh "$ip"; done < targets.txt. Файл targets.txt — по одному IP на строку. Запускаете команду и читаете условия задач, пока автоматизация пашет в фоне.
Флаг grep -E использует расширенные регулярные выражения (ERE). В BusyBox он доступен только если grep собран с соответствующей опцией. На урезанных системах альтернатива: grep '/tcp.*http' "$DIR/nmap.txt" | awk '{print $1}' | cut -d/ -f1 | head -1 — awk переносимее и работает даже в минимальных BusyBox-сборках.
Nmap генерирует текстовый вывод со стабильной структурой — идеальная мишень для парсинга. Русскоязычные материалы обычно ограничиваются базовым grep, но awk и sed открывают совсем другой уровень. Разберём конкретные задачи.
Извлечение всех открытых портов. Конструкция grep -oE '^[0-9]+/tcp' nmap.txt | cut -d/ -f1 | paste -sd, вытаскивает номера портов и склеивает через запятую. Результат вида 22,80,443,8080 — готовый аргумент для прицельного повторного сканирования: nmap -p 22,80,443,8080 -A $IP. Обратите внимание на paste -sd, — она делает то же, что tr '\n' ',' | sed 's/,$//', но в одну команду вместо двух.
Версии сервисов — чистая таблица. Команда awk '/^[0-9]+\/tcp/{print $1, $3, $4, $5}' nmap.txt вытаскивает порт, статус и название сервиса с версией. awk делит строку на поля по пробелам: $1 — первое поле, $NF — последнее, $(NF-1) — предпоследнее. Для вывода nmap $1 — это 80/tcp, $3 — http, $4 и $5 — название и версия сервера.
Фильтрация по типу сервиса. Для SSH-портов: grep -E '^[0-9]+/tcp.*ssh' nmap.txt | awk '{print $1}'. Для HTTP — замените ssh на http. Связка grep для фильтрации и awk для извлечения — базовый паттерн, который работает с любым текстовым выводом.
# Открытые порты через запятую
grep -oE '^[0-9]+/tcp' nmap.txt | cut -d/ -f1 | paste -sd,
# Версии сервисов
awk '/^[0-9]+\/tcp/{print $1, $3, $4, $5}' nmap.txt
# HTTP-порты для gobuster
grep '/tcp.*http' nmap.txt | grep -oE '^[0-9]+'
# Уникальные IP из access.log
awk '{print $1}' access.log | sort -u
# Неудачные SSH-входы — top по IP
grep "Failed password" auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn
Последний one-liner стоит разобрать. NF в awk — количество полей в строке. $(NF-3) — третье поле с конца. В строке Failed password for admin from 192.168.1.100 port 22 ssh2 IP-адрес стоит именно на третьей позиции с конца. Эта конструкция из реальной практики анализа /var/log/auth.log — hadess.io описывает её для задач SOC-аналитика.
Пайп sort | uniq -c | sort -rn — классическая тройка: сортировка, подсчёт уникальных вхождений, сортировка по убыванию количества. Запомните эту связку — она всплывает в каждом втором задании, связанном с анализом логов.
Текстовый вывод (-oN) удобен для простого парсинга, но для сложных запросов лучше XML. Запускайте nmap с -oX nmap.xml. Для извлечения номеров портов: grep -oP 'portid="\K[^"]+' — конструкция \K (Perl-совместимый lookbehind) отбрасывает всё до значения атрибута. Если grep -P недоступен, альтернатива: awk -F'"' '/portid/{print $2}' — awk делит строку по кавычкам, второе поле содержит номер порта.
По данным hadess.io, xmllint --xpath '//port[@protocol="tcp"]/state[@state="open"]/..' nmap_output.xml позволяет делать полноценные XPath-запросы к XML-выводу nmap. Но xmllint не всегда стоит на CTF-стендах — я бы не рассчитывал на него.
Если API отвечает JSON-ом, используйте jq. Конструкция curl -s http://api/endpoint | jq -r '.token' извлечёт значение ключа token. Для вложенных структур: jq -r '.data.users[].name'.
Если jq не установлен (частая ситуация на CTF), простейшие JSON-поля вытаскиваются через grep -oP '"key":"\K[^"]+' — грубый метод, но на CTF работает. Для структурированных логов в JSON-формате jq незаменим: cat app.log | jq -r 'select(.level == "ERROR") | "\(.timestamp) \(.message)"' фильтрует только ошибки и выводит их с таймстампами.
Веб-форма авторизации без rate-limiting и CSRF-защиты — классика CTF начального уровня. Четырёхзначный PIN или пароль из короткого вордлиста. По OWASP это A07:2021 Identification and Authentication Failures — на CTF такая слабость намеренная, в продакшене это дыра размером с ворота.
Перед запуском полного перебора убедитесь вручную, что сервер не возвращает HTTP 429/503 после серии запросов — если rate-limiting всё же есть, скрипт ниже этого не обрабатывает.
Перед написанием скрипта разберите запрос руками. Откройте Developer Tools (вкладка Network), введите заведомо неверный пароль и зафиксируйте четыре вещи: URL формы (например, http://target:8080/login), метод (POST или GET), имена полей (user, pass) и текст ответа при ошибке (Access denied). Без этой ручной подготовки скрипт стреляет вслепую.
#!/bin/bash
# set -euo pipefail здесь не используется: grep -q возвращает non-zero при каждом неверном пароле,
# что при set -e прервало бы цикл перебора на первой же попытке.
URL="http://target:8080/login"; FAIL="Access denied"
WORDLIST="${1:-/usr/share/wordlists/rockyou.txt}"
while read -r pass; do
resp=$(curl -s -d "user=admin" --data-urlencode "pass=${pass}" "$URL")
echo "$resp" | grep -q "$FAIL" || { echo "[+] Пароль: $pass"; exit 0; }
done < "$WORDLIST"
echo "[-] Совпадений нет"
Переменная FAIL содержит маркер неверного пароля — строку из Developer Tools. Конструкция ${1:-/usr/share/wordlists/rockyou.txt} берёт путь к вордлисту из аргумента. Если аргумент не передан — подставляет дефолтный путь к rockyou.txt из Kali.
Цикл while read -r pass читает файл построчно, не загружая в память целиком. Это критично: rockyou.txt весит более 140 МБ. Конструкция for pass in $(cat rockyou.txt) попытается загрузить весь файл в оперативную память и убьёт оболочку (проверено — не повторяйте). Флаг -r предотвращает интерпретацию обратных слешей — без него пароль pa\ss превратится в pass.
curl -s -d ... --data-urlencode отправляет POST-запрос. curl автоматически объединяет несколько -d и --data-urlencode параметров через & в теле запроса. --data-urlencode кодирует спецсимволы в значении пароля — без этого флага пароль с & или = сломает структуру запроса. Флаг -s (silent) подавляет прогресс-бар.
Конструкция grep -q "$FAIL" || { ...; } — инвертированная проверка. Если grep НЕ находит строку ошибки в ответе — пароль подошёл. Оператор || выполняет блок справа только при неудаче команды слева. Компактнее, чем if ! echo "$resp" | grep -q "$FAIL"; then.
Скрипт работает с простыми формами без CSRF-токенов. Если форма генерирует токен в скрытом поле — сначала заберите страницу curl-ом, извлеките токен через sed -n 's/.*value="\([^"]*\)".*/\1/p' и подставьте в POST-запрос. Это добавляет 3-4 строки, но логика та же.
Для генерации вордлистов на лету: seq -w 0000 9999 > pins.txt создаёт список четырёхзначных PIN-кодов. Флаг -w обеспечивает ведущие нули (0001, 0002 вместо 1, 2). Для шестнадцатеричных значений: printf "%04x\n" $(seq 0 65535) > hex.txt.
По MITRE ATT&CK перебор через curl в цикле маппится на T1110.001 Password Guessing (тактика Credential Access). Все три публичных теста Atomic Red Team для T1110.001 ориентированы на Windows (SMB, LDAP, Kerberos) — Linux/bash-тестов для этой техники в репозитории нет, curl-based брутфорс веб-форм не покрыт официальными atomic tests. Маппинг на технику при этом корректен: принцип перебора паролей по словарю одинаков на любой платформе.
Сила bash — в пайпах. Каждая утилита делает одну вещь хорошо, а bash склеивает их в цепочку. По данным hadess.io, «the real power of Bash in security is piping output between tools». Разберём типовые связки.
nmap → nikto. После обнаружения HTTP-портов нужно проверить веб-сервер на известные уязвимости. Цепочка: grep '/tcp.*http' nmap.txt | grep -oE '^[0-9]+' | while read -r port; do nikto -h "http://$IP:$port" -o "nikto_$port.txt"; done. Каждый HTTP-порт из вывода nmap автоматически проходит через nikto. На выходе — отдельный файл на каждый порт.
Массовый поиск флагов в архиве. На CTF часто нужно прошерстить скачанный архив: find . -type f -exec grep -lH "flag{" {} \; 2>/dev/null. find рекурсивно находит файлы, grep -lH выводит имя файла с совпадением. Перенаправление 2>/dev/null подавляет ошибки на бинарных файлах.
Проверка целостности файлов. В задачах на forensics: find /etc -type f -exec sha256sum {} \; создаёт базовый снимок, а diff baseline.txt current.txt | grep "^[<>]" обнаруживает изменения. Этот приём работает и в реальном incident response — hadess.io описывает аналогичный скрипт для File Integrity Check.
Мониторинг сертификатов. На длительном пентесте полезно отслеживать сроки TLS-сертификатов: echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2 извлекает дату истечения. Оберните в цикл по списку доменов — готовый мониторинг.
Subdomain enumeration → live check. На CTF с реалистичной инфраструктурой: subfinder -d target.com -silent | sort -u | while read -r sub; do curl -s -o /dev/null --max-time 5 -w "%{http_code} $sub\n" "http://$sub"; done | grep -v "^000". subfinder находит субдомены, curl -w "%{http_code}" проверяет код ответа, grep -v "^000" фильтрует недоступные хосты.
Каждая из этих ошибок стоила мне или знакомым от 20 минут до часа на CTF. Собрал пять самых болезненных.
1. Забытые кавычки вокруг переменных. Команда grep $PATTERN file.txt сломается, если PATTERN содержит пробел. Правильно: grep "$PATTERN" file.txt. Аналогично с путями: cd $DIR при DIR="my folder" попытается перейти в my. Привычка: всегда оборачивайте переменные в двойные кавычки.
2. for line in $(cat file) вместо while read -r line. Конструкция for загружает файл в память целиком и разбивает по пробелам — строка hello world станет двумя итерациями. while read -r line; do ...; done < file читает построчно, сохраняя пробелы. На вордлистах размером в десятки мегабайт это разница между рабочим и мёртвым скриптом.
3. Игнорирование кодов возврата. Если nmap завершился с ошибкой, а скрипт продолжает работать — вы парсите пустой файл и не понимаете, почему ничего не находится. Решение: set -e в начале скрипта или ручная проверка $? после критичных команд. set -euo pipefail покрывает три типа сбоев одновременно.
4. Неэкранированные спецсимволы в regex. Точка в regex — любой символ. Поиск grep 192.168.1.1 совпадёт и с 192X168Y1Z1. Правильно: grep -F '192.168.1.1' (фиксированная строка) или grep '192\.168\.1\.1' (экранированные точки). На CTF с парсингом логов эта ошибка приводит к ложным совпадениям и потерянному времени.
5. Useless Use of Cat. cat file | grep pattern — лишний процесс. grep принимает файл аргументом: grep pattern file. В единичном вызове разница незаметна, но в цикле по 14 миллионам строк rockyou.txt суммарные потери ощутимы. Если утилита принимает файл аргументом — не кормите его через cat.
Bash-скрипты на CTF — атакующая техника, но полезно понимать, как выглядит ваша активность с другой стороны. Sigma-правила из репозитория SigmaHQ покрывают обнаружение подозрительной shell-активности: lnx_shell_susp_commands.yml ловит типичные reconnaissance-команды (перечисление пользователей, сетевая разведка). lnx_shell_susp_rev_shells.yml — попытки создания обратных шеллов. lnx_shell_priv_esc_prep.yml — подготовку к повышению привилегий: поиск SUID-бинарников, чтение /etc/shadow, перечисление sudo-прав. Всего по тегу T1059.004 в SigmaHQ 20 правил детекции.
Для T1082 System Information Discovery — 36 правил, включая lnx_auditd_system_info_discovery.yml, отслеживающее запуск uname, hostname, cat /etc/os-release через auditd. Те самые команды, которые на CTF запускаются первыми.
Зная, что детектируется, вы лучше ориентируетесь на защищённой инфраструктуре. На CTF-стендах обычно нет EDR, и эти правила не активны. Но на реальном пентесте осознание детекции меняет подход: вместо шумного nmap -sV -sC по всем портам — точечные проверки конкретных сервисов.
Контрмеры из MITRE D3FEND дополняют картину. Против T1059.004 Unix Shell: Executable Allowlisting (D3-EAL) — разрешение запуска только одобренных бинарников; File Analysis (D3-FA) — анализ содержимого файлов до выполнения; File Integrity Monitoring (D3-FIM) — отслеживание изменений критических файлов. Против T1110.001 Password Guessing: Password Rotation (D3-PR), One-time Password (D3-OTP), Operational Process Monitoring (D3-OPM) — мониторинг логов аутентификации на аномальное количество попыток.
Связь с AbuseIPDB: если ваш IP засветился массовым сканированием портов (категория #14 Port Scan) или SSH-брутфорсом (категория #22 SSH), он попадёт в базу с определённым abuseConfidenceScore. При значении выше 75 многие организации блокируют такие IP автоматически. На CTF это не проблема — стенды изолированы. Но если вы случайно направили скрипт на реальный хост вместо целевого — последствия будут.
Bash-автоматизация на CTF — порог входа, а не потолок. Я вижу две крайности: одни решают каждую задачу руками, теряя время на копипаст между терминалами, другие пытаются до начала соревнований собрать универсальный фреймворк на Python. Первые упираются в скорость, вторые — в overengineering.
Рабочий путь — набор из 5-7 bash-скриптов, каждый под конкретную рутину: разведка, брутфорс, парсинг логов, обработка файлов. Не фреймворк, а коллекция заточенных инструментов.
На практике 80% CTF-задач решаются связкой nmap + grep + awk и парой curl-ов в цикле. Остальные 20% — нестандартные вещи: криптография, бинарная эксплуатация, работа с сетевыми протоколами на уровне байтов. Тут bash уже не тянет и нужен Python или специализированные инструменты. Но без базовой bash-автоматизации вы тратите первые 80% времени на рутину и не добираетесь до тех самых интересных 20%.
Навык переносится на реальные задачи напрямую. Связка grep | awk | sort | uniq -c работает в SOC при анализе логов. set -euo pipefail — стандарт в DevSecOps-пайплайнах. curl в цикле — основа автоматизированного тестирования API. CTF — тренировочная площадка, но навыки из неё живут в продакшене. Если хочешь не ковыряться в Kali в одиночку, а системно разобраться с основами — на IB Basics это выстроено от нуля до первых рабочих задач без академического тона.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...