
На одной из машин HackTheBox после получения начального shell обнаружилось: nmap в контейнере не установлен, образ урезан до BusyBox, Python отсутствует, pip недоступен. Из инструментов — bash, curl и grep. Перебор портов внутренней сети через netcat вручную занял бы часы. Восемь строк кода через встроенный механизм /dev/tcp — и первые 1024 порта просканированы за полторы минуты. Суть bash скриптов для CTF укладывается в одну фразу: склейка того, что уже под рукой, в рабочую цепочку, когда ничего другого нет. Не программирование ради программирования, а ремесло из подручных средств.
Три ситуации, в которых bash выигрывает у любого Python-фреймворка или специализированного инструмента. Подробнее — в нашем материале про создание ctf заданий.
На целевой машине нет ничего кроме shell. Минимальные контейнеры, урезанные Docker-образы, embedded-устройства — Python не стоит, ставить некуда, иногда и сети наружу нет. Bash есть везде, где есть терминал. В терминологии MITRE ATT&CK это техника T1059.004 (Unix Shell) — использование штатной оболочки для выполнения команд на целевой системе. Тесты Atomic Red Team для T1059.004 включают «Create and Execute Bash Shell Script» и «Harvest SUID executable files» — оба запускаются через sh на Linux: первый демонстрирует сам факт выполнения bash-скрипта как техники T1059.004, второй — конкретный пример сбора данных для privesc через поиск SUID-бинарников.
Нужно склеить три утилиты в одну цепочку. Запустить nmap, выдернуть открытые порты через grep, подставить результат в gobuster — пять строк на bash. То же самое на Python с argparse, обработкой исключений и импортом библиотек — проект на 50+ строк. На jeopardy-CTF с лимитом два часа на десять задач эта разница решает.
Понимание механики важнее скорости инструмента. Когда пишешь сканер портов на чистом bash, ты понимаешь, как работает TCP-соединение. Когда пишешь брутфорсер на curl — видишь, что POST-запрос несёт в теле. ffuf быстрее на порядок, но ffuf — чёрный ящик. Написание скриптов для CTF на bash формирует интуицию, которую GUI-инструменты не дают. После того как руками собрал свой сканер, nmap перестаёт быть магией — становится понятно, что внутри те же SYN/ACK.
Перед первым скриптом — три быстрые проверки. Версия bash: bash --version, нужна 4.x или выше (на macOS по умолчанию стоит 3.2 из-за лицензионных ограничений — обновите через brew install bash; на Windows используйте WSL2). Инструменты: nmap, curl, grep, awk — в Kali и Parrot всё из коробки, на минимальных дистрибутивах доставьте через apt install nmap curl. Рабочая директория и шебанг: mkdir ~/ctf && cd ~/ctf, каждый скрипт начинается с #!/bin/bash, после создания файла — chmod +x script.sh. Без chmod получите Permission denied — через эту ловушку проходят все.
Ни один русскоязычный гайд по автоматизации CTF не покрывает этот приём, хотя он критически полезен в ситуации из вступления: сканирование портов без единой внешней утилиты.
Bash (именно bash, не sh и не dash) поддерживает псевдоустройства /dev/tcp/HOST/PORT. При попытке открыть такой путь оболочка инициирует TCP-соединение. Порт открыт — соединение устанавливается, команда завершается с кодом 0. Закрыт — таймаут или отказ, код возврата ненулевой. На этом строится минимальный сканер для перебора портов bash-средствами.
#!/bin/bash
HOST="${1:?Использование: $0 <IP> [диапазон]}"
RANGE="${2:-1-1024}"
IFS='-' read -r START END <<< "$RANGE"
for ((port=START; port<=END; port++)); do
(echo >/dev/tcp/$HOST/$port) 2>/dev/null && \
echo "[+] $port open"
done
Конструкция ${1:?...} проверяет наличие аргумента — если IP не передан, скрипт выведет сообщение и завершится. Второй аргумент — диапазон портов в формате 1-1024, по умолчанию первые 1024. Команда IFS='-' read -r START END разбивает строку по дефису на две переменные.
Ключевая строка — (echo >/dev/tcp/$HOST/$port) 2>/dev/null. Круглые скобки запускают команду в subshell, чтобы ошибка соединения не убила основной скрипт. Перенаправление 2>/dev/null подавляет сообщения «Connection refused». Оператор && срабатывает только при успешном соединении.
Скорость: последовательное сканирование 1024 портов занимает 2-5 минут в зависимости от таймаутов сети. Для ускорения добавьте & в конец строки с echo — bash запустит проверки параллельно в фоновых процессах, а wait после done дождётся завершения всех. Осторожно: при диапазоне 1-65535 это порождает десятки тысяч процессов. Ограничивайте параллелизм пачками: вставляйте [[ $(jobs -r | wc -l) -ge 50 ]] && wait -n внутрь цикла. Учтите, что jobs -r в bash обновляется неточно в скриптовом режиме и реальное число процессов может превышать лимит. Более надёжный вариант — передать список портов через xargs -P50 -I{} bash -c '(echo >/dev/tcp/$HOST/{}) 2>/dev/null && echo "[+] {} open"' или использовать семафор на основе FIFO. Протестируйте реальную нагрузку перед применением на боевой цели.
Этот подход описан в EN-источнике Penetration Testing Lab как учебный TCP-сканер. Он не заменяет nmap — нет определения версий, нет NSE-скриптов, нет UDP. Но когда на целевой машине ничего нет, /dev/tcp — единственный вариант. И он работает.
Псевдоустройство /dev/tcp — фича bash, а не стандарт POSIX. На системах с dash по умолчанию (Ubuntu, Debian) скрипт нужно запускать явно через bash script.sh, а не sh script.sh. BusyBox ash тоже не поддерживает /dev/tcp — и вот тут начинаются танцы. Альтернатива — nc -z -w1 $HOST $port 2>/dev/null && echo "open". Флаг -z переводит netcat в режим сканирования без передачи данных, -w1 задаёт таймаут в секунду. Если и netcat нет — остаётся загрузить статически скомпилированный бинарник nmap через curl или wget с вашей машины.
Запустить nmap — полдела. Вторая половина — вытащить из вывода нужные данные и подставить их в следующий инструмент. Парсинг вывода bash-командами — навык, который экономит минуты на каждой CTF-машине.
Базовая связка: nmap -sV -sC -oN scan.txt TARGET сохраняет результат в текстовом формате, пригодном для grep. Дальше работаем с файлом.
Извлечь открытые порты через запятую для подстановки в другие утилиты: grep '/tcp.*open' scan.txt | cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//'. Конвейер из четырёх команд: grep находит строки с открытыми TCP-портами, cut вырезает номер порта до символа /, tr заменяет переводы строк на запятые, sed убирает финальную запятую. Результат: 22,80,443,8080 — готовая строка для nmap -p 22,80,443,8080 на втором проходе с глубокими скриптами.
Найти HTTP-сервисы для дальнейшего веб-сканирования: grep -E 'http|https' scan.txt | grep -oE '^[0-9]+' | head -5. Первый grep ищет строки с упоминанием HTTP-сервисов, второй извлекает номера портов из начала строки. Результат — список портов, на которые можно натравить gobuster или nikto.
Поиск флагов по файловой системе — абсолютная классика. Однострочник grep -rE 'flag\{|CTF\{|HTB\{|THM\{' / 2>/dev/null рекурсивно ищет строки в стандартных форматах флагов. Перенаправление 2>/dev/null подавляет ошибки доступа к защищённым директориям. Альтернатива: find / -name "*.txt" -exec grep -l "flag" {} \; 2>/dev/null — ищет файлы, содержащие слово «flag». MITRE ATT&CK классифицирует это как T1083 (File and Directory Discovery) и T1119 (Automated Collection).
Для повторяющихся задач оберните парсинг в функцию:
extract_ports() {
local file="${1:?Укажите файл nmap}"
grep '/tcp.*open' "$file" | \
cut -d'/' -f1 | tr '\n' ',' | sed 's/,$//'
}
# Использование: PORTS=$(extract_ports scan.txt)
# nmap -sV -p "$PORTS" $TARGET
Функция принимает имя файла, возвращает строку портов. Сохраните её в ~/.bash_ctf и добавьте source ~/.bash_ctf в .bashrc — функция доступна в каждом терминале без копирования.
awk мощнее grep для сложного парсинга. Извлечь из nmap-вывода таблицу «порт — сервис — версия»: awk '/^[0-9]+\/tcp/{print $1, $3, $4, $5}' scan.txt. Команда обрабатывает строки, начинающиеся с номера порта, и выводит нужные столбцы. Для tab-разделённого формата: awk -F' +' '/^[0-9]+\/tcp/{OFS="\t"; print $1, $3, $4}' scan.txt.
sed пригодится для массовых замен и вырезания секций: sed -n '/^PORT/,/^$/p' scan.txt — извлечь только блок с портами из полного вывода nmap. Команда печатает строки от PORT до первой пустой строки.
Комбинация grep, awk и sed для CTF — универсальный парсер любого текстового вывода. Не нужен jq для JSON или xmlstarlet для XML — на CTF начального уровня текстовый формат nmap (-oN) покрывает подавляющее большинство задач.
Классический CTF-сценарий начального уровня: форма авторизации с числовым PIN или паролем из короткого вордлиста. Без rate limiting, без CSRF-токенов. В терминологии OWASP это пересечение Broken Authentication (API2:2023) и Unrestricted Resource Consumption (API4:2023) — сервер не ограничивает частоту попыток. На CTF это намеренная слабость. MITRE ATT&CK классифицирует перебор паролей как T1110.001 (Password Guessing).
Перед написанием мини-эксплойта bash — обязательная ручная разведка. Откройте форму в браузере, введите заведомо неверный пароль и загляните в Developer Tools (вкладка Network). Зафиксируйте четыре вещи: URL формы (например, http://target:8080/login), метод (POST или GET), имена полей (user, pass) и текст ответа при ошибке (Access denied). Без этих четырёх значений скрипт стреляет вслепую.
#!/bin/bash
TARGET="http://target:8080/login"
FAIL="Access denied"
while read -r pass; do
resp=$(curl -s -d "user=admin&pass=${pass}" "$TARGET")
echo "$resp" | grep -q "$FAIL" || \
{ echo "[+] Пароль: $pass"; exit 0; }
done < wordlist.txt
echo "[-] Пароль не найден"
Конструкция while read -r pass читает wordlist.txt строка за строкой, помещая каждую строку в переменную pass. Флаг -r запрещает bash интерпретировать обратные слэши — без него пароль test\n123 превратится в testn123, и совпадения не будет. Синтаксический подвох: < wordlist.txt стоит после done, а не в начале цикла — это ловит практически всех с первого раза.
Команда curl -s -d "user=admin&pass=${pass}" отправляет POST-запрос. Флаг -s подавляет прогресс-бар. Строка с -d обёрнута в двойные кавычки. В одинарных кавычках ${pass} не раскрывается и улетает на сервер буквально как текст ${pass}. Эта ошибка стабильно входит в топ-3 у тех, кого я менторил.
Проверка grep -q "$FAIL" ищет строку ошибки. Флаг -q — тихий режим: ничего не выводит, только устанавливает код возврата. Оператор || срабатывает, когда строка ошибки не найдена — значит, ответ отличается от стандартного отказа. Фигурные скобки группируют вывод и exit 0.
Для числового PIN замените while read на for pin in $(seq -w 0000 9999); do и подставляйте $pin вместо $pass. Флаг -w добавляет ведущие нули — без него seq генерирует 1 вместо 0001, и четырёхзначный PIN не совпадёт.
Скрипт работает, когда вариантов немного (до 10 000-50 000) и сервер не ограничивает запросы. Против чего бесполезен: rate limiting (добавление sleep 0.5 растягивает перебор 10 000 вариантов с 40 секунд до 83 минут), CSRF-токены (для каждого запроса нужен уникальный токен — скрипт разрастается до 20+ строк и становится хрупким), JavaScript-рендеринг формы. На реальных проектах берите hydra для стандартных протоколов (SSH, FTP, HTTP Basic Auth) или Python с requests.Session() для сложной логики. Bash-скриптинг для пентеста через curl — инструмент для учебных задач и legacy-приложений без WAF.
Написать скрипт — 30% дела. Остальные 70% — понять, почему он не работает. Три инструмента отладки, без которых линукс для начинающих CTF превращается в бесконечное гугление ошибок.
set -x — режим трассировки. Добавьте после шебанга, и bash будет печатать каждую команду перед выполнением с подставленными значениями переменных. Увидите разницу между тем, что написали, и тем, что bash выполняет. Для отключения — set +x. На CTF, когда скрипт молча отрабатывает неправильно, set -x экономит десятки минут.
set -euo pipefail — защита от тихих ошибок. -e останавливает скрипт при первой упавшей команде (без него bash продолжает выполнение — скрипт разведки, где nmap вернул ошибку, а gobuster запустился без цели). -u ругается на неинициализированные переменные вместо молчаливой подстановки пустой строки. -o pipefail отслеживает ошибки внутри конвейеров — без него false | echo "ok" завершится успешно, хотя первая команда упала.
Пробелы в квадратных скобках — на этом спотыкаются все. Конструкция [ "$VAR" -ne 0 ] — это вызов команды test, а квадратная скобка — её алиас. Пробелы вокруг скобок и оператора обязательны. Запись ["$VAR"-ne 0] без пробелов выдаёт [: command not found. Запись [ "$VAR"-ne"0" ] воспринимается как одна строка вместо трёх аргументов и тоже ломается.
Кавычки вокруг переменных. if [ $VAR = "test" ] ломается, если $VAR пустая — bash развернёт в if [ = "test" ], что синтаксически некорректно. Всегда оборачивайте в двойные кавычки: "$VAR". Внутри [[ ]] bash обрабатывает пустые переменные корректно, но привычка с кавычками спасёт при переносе скрипта на другие оболочки.
Кодировка вордлиста — моя любимая боль. Файл, созданный на Windows, содержит \r\n вместо \n. Bash читает строку password\r — curl отправляет на сервер пароль с невидимым символом возврата каретки. Пароль «правильный», но не совпадает. Лечение: sed -i 's/\r$//' wordlist.txt перед использованием. Или dos2unix wordlist.txt, если утилита установлена. Я на эту проблему попадался трижды, прежде чем стал проверять автоматически — теперь в .bash_ctf первой строкой стоит функция clean_wordlist(), которая делает sed и sort -u на любом входном файле.
Разовые скрипты — начало. Следующий шаг — собрать коллекцию функций, которые работают от CTF к CTF без правок.
Создайте файл ~/.bash_ctf с функциями-хелперами. extract_ports() парсит nmap-вывод. flag_search() рекурсивно ищет флаги: grep -rE 'flag\{|CTF\{|HTB\{|THM\{' / 2>/dev/null. Функция serve() поднимает HTTP-сервер одной командой: python3 -m http.server 8000. Добавьте source ~/.bash_ctf в .bashrc — весь набор доступен в каждом терминале. Разница между «ищу скрипт, который писал месяц назад» и «набираю recon 10.10.10.5 и иду читать условие» — ощутимая. На CTF с лимитом по времени это решает.
tmux для параллельной работы. Запускайте каждый скан в отдельной панели: tmux new-session -d -s ctf для создания сессии, Ctrl+B % для вертикального разделения, Ctrl+B " для горизонтального. В одной панели — nmap, в другой — gobuster, в третьей — заметки. Автоматизация рутинных задач пентестера — это не только скрипты, но и организация рабочего пространства.
Системные данные одной командой. После получения shell на целевой машине — быстрый сбор: id; uname -a; cat /etc/os-release; ip a; ss -tlnp. Пять команд через точку с запятой дают полную картину: кто вы, какая ОС, какие сетевые интерфейсы, какие порты слушают. MITRE ATT&CK классифицирует это как T1082 (System Information Discovery). Оберните в алиас: alias sysinfo='id; uname -a; cat /etc/os-release; ip a; ss -tlnp' — на каждой новой машине набираете sysinfo вместо пяти команд.
SUID-файлы для повышения привилегий. Поиск бинарников с SUID-битом — один из первых шагов privesc: find / -perm -4000 -type f 2>/dev/null. Список сверяйте с GTFOBins — если в выводе bash, awk, find или другой бинарник из каталога GTFOBins, это вектор повышения привилегий. Тест Atomic Red Team «Harvest SUID executable files» для T1059.004 делает ровно это — ищет SUID-бинарники через shell-скрипт.
Base64-декодирование. Флаги и подсказки на CTF часто закодированы в base64. Однострочник echo "dGVzdA==" | base64 -d декодирует строку. Для файлов: base64 -d encoded.txt > decoded.bin. Для рекурсивного поиска строк, похожих на base64: grep -rEo '[A-Za-z0-9+/]{20,}={0,2}' /var/www/ 2>/dev/null | while read -r line; do echo "$line" | base64 -d 2>/dev/null; done — грубый, но рабочий подход для jeopardy-задач.
Каждая из этих техник — строительный блок. Bash скрипты для CTF не требуют архитектуры и паттернов проектирования. Они требуют набора рабочих кусков, которые комбинируются под задачу за две минуты. Файл .bash_ctf из 10-15 функций закрывает основную массу рутины на CTF начального и среднего уровня.
Позиция, которую я защищаю перед каждой новой группой: новичкам стоит писать собственные bash-обёртки, даже если ffuf или AutoRecon делают то же самое в десять раз быстрее. Не потому что bash лучше — потому что после написания собственного сканера портов через /dev/tcp человек понимает, что nmap внутри делает последовательность TCP SYN/ACK, а не магию. После написания брутфорсера на curl — понимает, что hydra внутри шлёт ровно тот же POST-запрос, только в тысяче потоков.
Это понимание отличает того, кто решает задачи, от того, кто запускает инструменты. Через полгода, когда на CTF среднего уровня попадётся нестандартная задача — кастомный протокол, нетипичная форма авторизации, бинарник с необычным поведением — именно навык «разобрать по кирпичикам и собрать своё» даст результат. Готовые инструменты в этот момент будут бесполезны.
Вторая неудобная вещь: большинство новичков застревают не на сложности задач, а на неумении быстро организовать рабочий процесс. tmux, алиасы, .bash_ctf — эти три вещи ускоряют решение CTF-машин начального уровня вдвое. Не потому что скрипты умнее, а потому что руки не заняты копированием одних и тех же команд из заметок месячной давности. На IB Basics эта связка «теория + руками + разбор» выстроена в систему, а не собирается по частям из разрозненных гайдов.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...