Обзор
Уязвимость проверки происхождения в BeyondTrust Remote Support позволяет неаутентифицированному злоумышленнику записывать файлы на устройство и выполнять код с правами пользователя операционной системы сайта. Точкой входа является общедоступное клиентское подключение, используемое для запуска сеанса поддержки. Никакие учетные данные или взаимодействие с пользователем не требуются.
Уязвимость находится между внутренним маршрутизатором сообщений brain и клиентским рабочим процессом на стороне устройства thin-scc. Злоумышленник может поместить зарезервированный локальный идентификатор маршрутизатора в адрес отправителя сообщения. Маршрутизатор пересылает сообщение, а рабочий процесс принимает этот адрес в качестве доказательства того, что запрос поступил от локального компонента. Это дает злоумышленнику доступ к внутренней службе передачи файлов, включая ее собственный провайдер файловой системы.
Сначала мы подтвердили возможность записи файлов, а затем использовали этот примитив для загрузки PHP-файла в общедоступный каталог сайта. Запрос этого файла предоставил нам выполнение кода от имени пользователя сайта. Модуль Metasploit теперь реализует эту цепочку с целями PHP и Unix Command.
Мы протестировали Remote Support 24.1.2, сборка 1524046, ревизия 4208-ddcf90715106aa0c0e19c103f8d2c412af4beff3. На тестируемом устройстве были включены запуск сеансов из публичного меню проблем и тонкий чат (по умолчанию). Другие затронутые версии и исправленные сборки не установлены. Базовая версия виртуальной машины для Secure Remote Access, 7.1.0, отделена от версии приложения Remote Support.
Влияние
Злоумышленник контролирует как содержимое загружаемого файла, так и путь его назначения с учетом разрешений файловой системы рабочего процесса. Та же подделка происхождения применяется и к операции отправки файлов. Мы использовали ее для получения /proc/self/environ, чтобы определить базовый путь установки Remote Support перед сбросом полезной нагрузки.
На тестируемом устройстве собственная файловая служба могла осуществлять запись в корневой каталог документов PHP. Модуль Metasploit возвращает выполнение кода в контексте msfuser (UID 1000), что обеспечивает неаутентифицированное удаленное выполнение кода от имени пользователя сайта.
Технический анализ
Мы продемонстрируем, как злоумышленник может начать анонимный сеанс, а затем подделать сообщение, которое устройство примет за локальное. Затем мы опишем, как получить доступ к службе передачи файлов, выбрать содержимое и назначение загружаемого файла, а также превратить запись файла в выполнение PHP.
Адреса используют базовый адрес образа Ghidra 0x100000. Вычтите это значение для получения исходного виртуального адреса ELF. Имена функций, начинающиеся с FUN_, являются метками Ghidra; описательные имена взяты из диагностических строк, если таковые имеются.
Запуск сеанса анонимного клиента
Мы начинаем с получения двух клиентских ключей из POST /api/start_session. Это новые общедоступные гостевые сеансы. Ключи позволяют клиенту использовать клиентский транспорт, но не идентифицируют вошедшую в систему учетную запись. Каждый гость подключается через конечную точку WebSocket /nw, используя либо родной (native), либо тонкий (thin) клиентский протокол.
source/app/write_app_chooser_config:
app/write_app_chooser_config показывает, куда направляются два клиентских протокола. Родной протокол достигает сокета brain в [A1]; тонкий протокол запускает обертку в [A2].
source/app/thin-scc-wrapper:
app/thin-scc-wrapper проверяет, что ключ тонкого клиента принадлежит неистекшему сеансу sdcust в [A3]. Ключ, полученный через общедоступную конечную точку запуска сеанса, проходит эту проверку.
app/thin-scc-wrapper, после проверки сеанса:
Та же обертка затем подготавливает рабочий каталог и запускает thin-scc в [A4]. Этот процесс выполняется на устройстве, даже если он обрабатывает тонкий сеанс анонимного клиента.
Родное подключение предоставляет нам клиента для внутренней системы маршрутизации сообщений, именуемой здесь MRS. Тонкое подключение предоставляет рабочий процесс на стороне устройства для адресации. Ни один из этапов не требует присутствия представителя для принятия сеанса.
Сетевое сообщение может заявлять, что оно является локальным
Адреса MRS состоят из трех частей: node:router:handler. Каждое сообщение содержит адрес источника и назначения в 36-байтном заголовке, состоящем из девяти 32-битных полей в формате little-endian.
Смещение заголовка
Поле
Значение, используемое эксплойтом
0x04
Узел источника (Source node)
Обычный узел клиента
0x08
Маршрутизатор источника (Source router)
Изменено на зарезервированное значение 1
0x0c
Узел назначения (Destination node)
Узел рабочего процесса
0x10
Маршрутизатор назначения (Destination router)
Маршрутизатор рабочего процесса
0x14
Обработчик назначения (Destination handler)
Основной диспетчер, 2048, в протестированной сборке
0x24
Полезная нагрузка (Payload)
Имя службы, символ новой строки, затем упакованные поля
app/brain — проверка входящих подключений (ingress check):
app/brain выполняет проверку защиты от подделки в FUN_01566ae0, определяемую по диагностическому сообщению handleMessageFromOtherRouter. В [B1] она отклоняет сетевой пакет, чей исходный маршрутизатор совпадает с фактическим идентификатором принимающего маршрутизатора.
Здесь param_1 — принимающий маршрутизатор, param_2 — пакет, а param_3 — входящее соединение. Поскольку param_2 указывает на элементы размером в восемь байт, выражение *(int *)(param_2 + 1) считывает исходный маршрутизатор по смещению 0x08.
Здесь остается пробел: проверка сопоставляется с фактическим идентификатором маршрутизатора, в то время как нижестоящий код также распознает зарезервированный локальный псевдоним. На этом устройстве псевдоним равен 1.
Путь пересылки в той же функции app/brain передает пакет дальше в [B2]. Он не удаляет утверждение об источнике, которое важно для проверки локального происхождения на стороне рабочего процесса.
app/brain — предикат локального адреса:
Предикат локального адреса делает несоответствие очевидным. В app/brain функция FUN_0155ad40 сначала проверяет узел в [C1]. Если он совпадает, маршрутизатора 1 достаточно для возврата true в [C2]; сравнение с фактическим локальным маршрутизатором пропускается.
Это правило фактически выглядит как узел AND (маршрутизатор == 1 OR маршрутизатор == фактический локальный маршрутизатор). Адрес, предоставленный сетевым клиентом, используется для принятия этого решения.
app/thin-scc — обертка происхождения сообщения:
Соответствующая обертка сообщений в app/thin-scc передает адрес отправителя сообщения в предикат локального адреса своего объекта маршрутизатора в [C3]. Ghidra не смогла восстановить надежный прототип для этого косвенного вызова; вызывающий код использует его результат в качестве булева значения.
Мы проверили это поведение с помощью того же запроса к файловой службе с двумя источниками. Обычный источник клиента не создавал подключения к файловой службе в ходе двухсекундного наблюдения. Изменение исходного маршрутизатора на 1 заставило рабочий процесс принять его.
Доступ к службе передачи файлов
В app/thin-scc функция FUN_0047a850 распределяет сообщения передачи файлов.
app/thin-scc — диспетчер передачи файлов:
Она вызывает обертку происхождения в [D1] и отбрасывает сообщение, если эта обертка говорит, что отправитель не является локальным. Как только эта проверка проходит, [D2] сопоставляет ftsName с именем экземпляра службы.
Та же функция проверяет responseAddress только тогда, когда поле не пустое. Эксплойт пропускает его, выбирая ветвление в [D3].
app/thin-scc, обработка адреса ответа в FUN_0047a850:
Валидатор, вызываемый в [D4], сравнивает адрес ответа с адресом отправителя сообщения. Он независимо не устанавливает, откуда пришел этот отправитель, поэтому сделать это поле обязательным не решило бы проблему подделки происхождения.
app/thin-scc, обработка действий в FUN_0047a850:
Затем диспетчер предоставляет DownloadAnonymousFiles в [D5] и UploadFiles в [D6].
Названия действий описывают ту сторону передачи, которая относится к воркеру. DownloadAnonymousFiles принимает байты на устройство; UploadFiles отправляет файл наружу. Чтобы загрузить наш файл, мы просим воркер скачать его с контролируемого нами MRS-прослушивателя.
Важно отметить, что у воркера имеется более одного провайдера файловой системы. Служба сеансов делегирует работу с файловой системой браузеру. Успешная передача через эту службу не доказывает, что файл был записан на устройство. Вместо этого локальная служба использует нативные файловые объекты.
app/thin-scc — создание нативной службы:
Конструктор ServerContext в app/thin-scc (FUN_002ae620) создает этот нативный провайдер на [E1]. Он формирует имя local на [E2] и передает оба компонента конструктору службы передачи файлов на [E3].
Поэтому в эксплойте используется ftsName=local. Анализ фабрики нативных файлов приводит от этого провайдера к конструкторам нативного файла и информации о файловой системе.
Выбор содержимого и назначения файла
app/thin-scc, FUN_00475820, обрабатывает DownloadAnonymousFiles.
app/thin-scc — запрос анонимной загрузки:
Функция считывает токен передачи запроса на [F1], адрес MRS-сервера на [F2] и целевой путь на [F3]. Все три параметра передаются в процедуру загрузки на [F4].
В этом фрагменте lVar12 указывает на карту полей запроса. param_2 уже смещен до своего ограничителя конца карты; 0x9c3700 представляет отсутствующее значение.
app/thin-scc — MRS-соединение для загрузки:
Процедура загрузки по адресу 0x004a21c0 открывает MRS-поток на наш адрес на [G1]. Она отправляет запрос на получение анонимного файла на [G2] вместе с нашим токеном на [G3]. Токен может быть случайной 24-символьной строкой, поскольку мы контролируем прослушиватель.
Та же процедура app/thin-scc помещает целевой путь в поток приема файлов на [G4] и запускает его на [G5].
app/thin-scc — цикл приема файлов:
Цикл приема по адресу 0x001d6430 обрабатывает метаданные файла, которые мы отправляем по этому потоку. Он копирует dest_filename на [H1]. В зависимости от информации о файле от провайдера, прием осуществляется либо в этот путь, либо во временный путь, заканчивающийся на .n$t на [H2].
После выполнения опущенного кода инициализации файла тот же приемник app/thin-scc открывает файл на [H3] и записывает поступающие данные на [H4].
По завершении приемник закрывает файл и, если использовался временный путь, переименует его в конечное место назначения. Служба изначально принимает абсолютный путь назначения, поэтому обход каталога (directory traversal) не требуется. Воркер разрешает запрос, так как доверяет поддельному локальному исходному адресу.
От загрузки файла к выполнению PHP
Следующим шагом является выбор доступного для записи расположения, которое веб-сервер мог бы выполнить. Указанный файл config/apache_vhost.conf устанавливает корневой каталог документа (document root) в следующее значение:
Абсолютная загрузка в этот каталог завершилась успехом. При запросе файла есть небольшой нюанс: URL, заканчивающийся на .php, возвращает 404, даже если файл существовал. app/www/public/.htaccess объясняет почему.
source/app/www/public/.htaccess:
Правило на [I1] отклоняет прямой запрос .php. Правило на [I2] сопоставляет URL без расширения с существующим PHP-файлом.
Запрос загруженного базового имени без .php выполнил нашу полезную нагрузку. Он вернул свое абсолютное имя файла, EUID 1000 и вывод команды id.
Мы также хотели избежать угадывания рабочей директории Remote Support или имени установки с версией. Та же нативная служба может отправить нам /proc/self/environ воркера с помощью UploadFiles. На этом устройстве он содержит:
BG_app_root сразу дает нам каталог установки. Добавление /app/www/public дает место назначения для загрузки; путь к сокету предоставляет еще один способ определить тот же корень. Есть одна особенность протокола: procfs сообщает размер файла окружения как нулевой, но воркер все равно передает его содержимое потоком. Мы должны считывать данные до конца файла (EOF) потока данных, вместо того чтобы останавливаться на заявленном размере.
Эксплойт
Модуль Metasploit реализует эту цепочку, используя два новых общедоступных сеанса клиента. Он обнаруживает thin-воркер через Director.GetGatewaySessionValues для собственного thin-гостя, отправляет поддельный запрос LocalFileTransfer главному диспетчеру воркера и передает байты PHP через существующее MRS-соединение. Отдельный сервер загрузки не требуется.
Модуль записывает случайное имя PHP-файла в публичный каталог сайта, а затем отправляет POST-запрос с токеном выполнения на URL без расширения. PHP-файл проверяет этот токен и удаляет сам себя перед выполнением полезной нагрузки. Цель 0 запускает PHP-нагрузку; цель 1 оборачивает команду Unix в PHP.
Для полезной нагрузки PHP Meterpreter базовая конфигурация имеет следующий вид:
Выберите цель 0 и совместимую командную полезную нагрузку, такую как php/meterpreter/reverse_tcp. Документация модуля охватывает переопределение каталога и другие параметры.
Рисунок 1: Эксплуатация службы анонимной передачи файлов с помощью Metasploit.
Тестирование привело к созданию двух сеансов PHP Meterpreter, сеанса PHP Meterpreter с оберткой команды и оболочки Bash. Обе цели также запускались с отключенной функцией AutoCheck, и немедленная повторная эксплуатация прошла успешно. Оболочка Bash вернула:
Индикаторы компрометации (IOC)
Приведенные ниже фрагменты охватывают HTTP-запросы, активность нативной передачи файлов в thin-scc и клиентские соединения, записанные brain. Они получены в результате наших лабораторных тестов 28–29 октября 2026 года. Исходные метки времени, адреса и пути сохранены; сеансовые токены и токены передачи замаскированы там, где это указано. Нерелевантные записи опущены.
Логи сайта хранятся в $BG_app_root/log/, где BG_app_root обозначает каталог установки Remote Support:
Лог
Представленные ниже данные
httpd_access.log
Создание публичных сеансов и запросы к загруженным конечным точкам PHP
thin-scc.log
Создание потока нативной передачи файлов и ошибки маршрутизации
thin-scc-errors.log
Отклоненные запросы передачи файлов, включая действие и целевой путь
brain.log
Записи о подключениях клиентов с адресами клиентов и метками времени
Активные воркеры также хранят логи в $BG_app_root/data/tmp/sdcust_thin_client/<thinMint>/; детали сбора данных ниже объясняют, как они попадают в каталог логов сайта. Путь установки различается в зависимости от устройства.
Запросы к загруженной конечной точке PHP
Запуск Metasploit, содержащий только команды, создал два публичных сеанса клиента, вызвал загруженную конечную точку PHP, а затем проверил, что конечная точка исчезла. Соответствующие строки лога доступа:
Здесь /qdgusthrmlirprls сопоставлялся с qdgusthrmlirprls.php в публичном каталоге. Первый POST-запрос выполнил команду и вернул 200. Последующий POST-запрос был запросом модуля на очистку; GET-запрос независимо проверял конечную точку. Оба получили 404 после того, как файл удалил сам себя.
Это дает защитникам последовательность для расследования: создание публичного сеанса, за которым следует запрос к новой, незнакомой конечной точке, потенциально с последующими ошибками 404 для того же пути. Само по себе создание сеанса является нормальной активностью Remote Support. Код 200, за которым следует 404, также сам по себе недостаточен; сопоставляйте запросы с изменениями файлов, записями сеансов и любыми необычными исходящими соединениями.
URL может не содержать .php
Предыдущая PHP-канарейка сформировала эти строки:
Прямой запрос .php завершился с ошибкой, в то время как запрос без расширения выполнил тот же файл. Окончательная ошибка 404 последовала за самоудалением. Обнаружение, которое ищет только запросы, заканчивающиеся на .php, пропустило бы успешный запрос в этом примере.
Собственная передача данных происходит внутри клиентского соединения MRS. Эти записи журнала доступа показывают HTTP-триггер, а не содержимое файла или поддельный идентификатор маршрутизатора. Перехваченный HTTP-журнал не раскрывает внутренние сообщения MRS, поэтому он не может сам по себе установить обход источника.
Файлы и соединения
Ищите неожиданные PHP-файлы в общедоступном каталоге сайта и получайте файлы, заканчивающиеся на .php.n$t. Прерванная передача может оставить временный файл, даже если окончательный URL возвращает 404. Суффикс .n$t также относится к стандартной реализации передачи, поэтому его наличие требует контекста.
Трассировки собственной передачи файлов
Файлы thin-scc.log и thin-scc-errors.log содержат более прямые доказательства запросов службы файлов. Например, эта строка из thin-scc-errors.log указывает действие, собственный провайдер и предполагаемый PHP-пункт назначения. Закрашены только ckey и downloadToken:
Это был наш отрицательный контроль: запрос использовал обычный исходный адрес клиента и был отклонен. Это свидетельство попытки вызова, а не успешной загрузки. Поддельный локальный источник проходит проверку, которая выдает это предупреждение, поэтому успешный эксплойт не обязательно должен оставлять запись DISCARDING NONLOCAL MESSAGE.
Успешный запуск модуля PHP оставил эти строки в thin-scc.log:
В наших запросах отсутствует responseAddress. Обработчик запускает передачу, а затем пытается отправить ответ на свое действие по пустому адресу. Это приводит к ошибке 0:0:0, не препятствуя записи файла. Обратный вызов полезной нагрузки независимо подтверждает выполнение в 17:06:37.
Ищите эту комбинацию в контексте proc{LocalFileTransfer}, особенно рядом с незнакомыми PHP-запросами или исходящими соединениями. Значение 1:1129726787:1031 идентифицирует внутренний почтовый ящик MRS; используйте приведенные ниже записи соединения brain для IP-адреса клиента. Эти записи не идентифицируют записанное имя файла и не доказывают выполнение сами по себе. Ошибки пустого адреса также зависят от того, как сформирован запрос эксплойта; другая реализация может оставить другие следы.
Записи соединения Brain
Файл brain.log помогает связать активность с адресом клиента. Этим двум записям предшествовал один и тот же запуск модуля:
Они полезны для составления хронологии, но клиентские соединения — это нормальная активность. Мы не нашли записей о действиях UploadFiles или DownloadAnonymousFiles в перехваченных журналах brain. Также появлялись общие ошибки EOF, сброса соединения и конференции; они слишком общие, чтобы рассматривать их как индикаторы эксплойта сами по себе.
Где собирать собственные журналы
Сводные журналы находятся в каталоге $BG_app_root/log/. Пока воркер работает, его собственные журналы находятся в $BG_app_root/data/tmp/sdcust_thin_client/<thinMint>/. Обертка добавляет эти журналы в каталог журналов сайта при выходе воркера, а затем очищает копии для каждого воркера. Собирайте данные из обоих мест при расследовании активной сессии.
В перехваченных файлах brain.ini и thin-scc.ini вывод отладки был отключен с помощью enabled=0. Журнал отладки не перехватывался, и мы не меняли конфигурацию ведения журналов. Конфигурация отладки Brain также ограничивает категории MRS_* уровнем Info, поэтому включение только ее отладочного вывода не гарантирует подробные полезные нагрузки сообщений.
Такие имена, как UploadFileStarted и DownloadFileEnded, встречаются в коде обратного вызова собственного протокола, но мы не нашли их в собранных журналах. Их не следует представлять в качестве стандартных событий аудита












