РАЗБОР На проверке 

Разбор атаки SSTI Standoff 365: от инъекции до root

Сергей Попов
Сергей Попов Red Team · 6,4 тыс. сообщений
Подписаться
30
Режим чтения
Портативное пентест-устройство на тёмном антистатическом коврике, на экране светится результат SSTI-инъекции Jinja2: 49, RCE, T1190.


На Standoff 365 за последний год я прошёл семь машин с веб-компонентом - в четырёх начальная точка входа оказалась SSTI. Машина Messenger стала показательным кейсом: от {{7*7}} в поле профиля до root-шелла и двух реализованных бизнес-рисков за четыре часа. Ниже - пошаговый разбор атаки SSTI Standoff 365 с payload'ами, командами и объяснением логики на каждом этапе. Без воды, без «давайте рассмотрим» - только цепочка.

Эксплуатация уязвимости шаблонизатора: разведка и детект SSTI​

Любой взлом веб-приложения на кибербитве начинается с разведки. nmap -sV -sC по хосту Messenger показал три открытых порта: SSH (22), nginx (80) и tcpwrapped (3000). На 80-м - самописный мессенджер на Python/FastAPI. На 3000-м порт отображался как tcpwrapped, но при обращении через браузер обнаружился Gitness - опенсорсная платформа для хостинга Git-репозиториев и CI/CD-пайплайнов.

Самописное приложение - известных CVE не будет. Уязвимости придётся копать вручную.

Фаззинг директорий через ffuf -w wordlist.txt -u http://target/api/FUZZ дал ключевой результат: Swagger-документация на /api/swagger раскрыла все эндпоинты API. Среди них - friendship summary, описанный как «Get Friendships Html Table Api View». Все прочие эндпоинты возвращали JSON, а этот - HTML. Если FastAPI-приложение рендерит HTML на стороне сервера, используется шаблонизатор. Для Python-стека это почти наверняка Jinja2.

Как отличить Server-Side Template Injection от обычного XSS? Согласно методологии PortSwigger, ключевой тест - отправить в контролируемое поле математическое выражение {{7*7}}. Сервер вернул 49 вместо текста - ввод обрабатывается как код шаблонизатора. Это SSTI - A03:2021 (Injection) по классификации OWASP Top 10, Exploit Public-Facing Application (T1190, Initial Access) по MITRE ATT&CK.

Дальше - развилка из HackTricks для определения движка: payload {{7*'7'}} возвращает 49 в Twig (PHP) и 7777777 в Jinja2 (Python). На Messenger вернулось 7777777 - Jinja2 подтверждён. В Burp Repeater такие тесты занимают минуту, но экономят час неверных payload'ов.

Цепочка атаки RCE: SSTI to RCE через Jinja2​

📚 Часть контента скрыта. Этот материал доступен участникам сообщества с рангом One Level или выше
Получить доступ просто — достаточно зарегистрироваться и проявить активность на форуме

, stty raw -echo; fg. Без этого интерактивные команды не работают, а на кибербитве терять время на нестабильный шелл - значит подставляться под SOC.

Повышение привилегий: кейс с кредами в конфигах​

Post-exploitation начинается с базовой разведки: whoami, id, uname -a - System Information Discovery (T1082). Затем - поиск privilege escalation techniques.

На машинах Standoff 365 мне встречались три вектора повышения привилегий:
  • SUID-бинарники - find / -perm -4000 -type f 2>/dev/null выдаёт бинарники с установленным SUID-битом. Нестандартные утилиты в списке - потенциальный вектор через GTFOBins.
  • Креды в файлах - Credentials In Files (T1552.001). Конфигурации .env, захардкоженные токены в исходниках, секреты в переменных окружения.
  • Docker-сокет - если приложение крутится в контейнере и сокет /var/run/docker.sock доступен непривилегированному пользователю, это часто прямой путь к root на хосте.
В кейсе Messenger повышение привилегий произошло через креды: JWT-секреты и API-токены Gitness лежали в конфигурационных файлах Python-приложения. Никакого kernel exploit - переиспользование легитимных учётных данных из файловой системы. Valid Accounts (T1078, Privilege Escalation) в чистом виде. Банально, но работает.

Для автоматизации поиска - linpeas.sh. Скрипт проходит по типичным мисконфигам: SUID, cron, writable paths, чувствительные файлы. На Standoff запускать сразу после стабилизации шелла - экономит время, которое на кибербитве критично. Но целиком полагаться на автоматику не стоит: grep -r "password\|secret\|token" /app/ 2>/dev/null иногда находит то, что linpeas пропускает. На одной машине именно ручной grep выдал токен в комментарии к коду - linpeas прошёл мимо.

Захват инфраструктуры: lateral movement и пентест соседних сервисов​

С добытыми токенами - этап захвата инфраструктуры. На Messenger рядом стоял Gitness на порту 3000. Токены из конфигов дали авторизацию и доступ к Git-репозиториям с исходниками приложения.

В репозиториях обнаружились дополнительные секреты: конфигурации RabbitMQ, адреса Redis, внутренние API-ключи. Через RabbitMQ удалось отправить crafted-сообщение, обработанное worker-процессом. Через Redis - перехватить данные из очереди задач. Exploitation of Remote Services (T1210, Lateral Movement). Каждый сервис - новая точка опоры.

На Standoff 365 каждый такой шаг - реализация бизнес-риска. На Messenger их два: «Получение доступа к корпоративной переписке разработчиков» и «Получение ключа шифрования мессенджера». Оба закрыты через описанную цепочку. Для SOC-команды на защите - два «недопустимых события», которые она не предотвратила.

Атака на веб-приложение пошагово: kill chain и MITRE ATT&CK​

Разбор CTF Standoff полезно маппить на kill chain - структурирует мышление и помогает не пропускать этапы на следующих машинах.

ЭтапДействиеMITRE ATT&CK
Initial AccessSSTI в Jinja2 через поле профиляT1190
ExecutionРеверс-шелл через os.popenT1059.004
Discoverywhoami, id, анализ файловой системыT1082
Credential AccessТокены в конфигах приложенияT1552.001
Privilege EscalationПереиспользование JWT-секретовT1078
Lateral MovementЗахват Gitness, RabbitMQ, RedisT1210
PersistenceWeb Shell при закрепленииT1505.003

Вся цепочка атаки RCE и последующая post-exploitation начинаются с одной точки - несанитизированного пользовательского ввода в функции render(). Одна ошибка разработчика - полная компрометация инфраструктуры.

Зачем это реальному злоумышленнику, а не CTF-игроку? Мотивация при SSTI to RCE в продакшене - получить foothold в корпоративной сети через публичное веб-приложение. Дальше - данные клиентов, финансовые системы, ransomware. На Standoff 365 эта логика моделируется через бизнес-риски: каждый флаг - конкретный ущерб для виртуальной компании.

Разбор атаки SSTI Standoff 365 вроде этого вызывает предсказуемый скепсис: «на реальной инфре WAF и сегментация, такое не пройдёт». Верно - наполовину. SSTI в продакшене встречается реже, чем на CTF-стендах. Но в моей практике на самописных Python-сервисах с шаблонизатором SSTI или соседняя инъекция находится нередко. Причина одна: разработчики не воспринимают шаблонизатор как поверхность атаки. Защищают SQL-запросы, проверяют JWT, ставят rate limit - и при этом передают пользовательский ввод напрямую в render(). Шаблонизатор для них - «просто рендер HTML», а не точка входа.

Privilege escalation через креды в файлах - не слабость CTF-стенда, а паттерн из реальных проектов. Захардкоженные секреты убивают инфраструктуру чаще, чем 0-day. Цепочка post-exploitation на Standoff и на пентесте отличается не техниками, а скоростью: на кибербитве SOC-команда, по моему опыту, может отрубить шелл за несколько минут. Полная цепочка - от SSTI до lateral movement - строится не из сложных эксплойтов, а из последовательных мелких ошибок. Тренировать навык сборки таких цепочек - единственный способ не зависать на каждом этапе в реальном проекте. На WAPT эту цепочку проходят в течение двух модулей с лабами.
Полезно

Комментарии

0

Ещё по теме