Вредоносный pull request в Understand-Anything: скрытая загрузка через Astro и блокчейн-управление

security

В середине мая 2026 года открытый репозиторий Egonex-AI/Understand-Anything, насчитывающий более 57 тыс. звёзд на GitHub, стал целью атаки через pull request. Злоумышленник под псевдонимом AsimRaza10 подал три почти идентичных запроса на слияние с разными описаниями, но одинаковым вредоносным содержимым. Один из них, PR #206, привлёк внимание сообщества из-за несоответствия между заявленными изменениями и фактическими правками. В результате расследования выяснилось, что в файл homepage/astro.config.mjs был внедрён обфусцированный код, который исполняется при каждом запуске сборки Astro, а управление атакой осуществляется через цепочку блокчейнов Tron и Binance Smart Chain.

Описание

Инструмент Understand-Anything преобразует исходный код в граф знаний и широко используется разработчиками в локальных средах и CI-пайплайнах. Именно эта широкая распространённость и особенности конфигурационного файла Astro сделали репозиторий удобной целью. Автор атаки подал заявки #198, #206 и #261 в течение 48 часов. В каждом случае описание изменений не соответствовало фактическому diff: PR #198 якобы добавлял четырёхстрочную правку в README, а на деле модифицировал .gitignore и astro.config.mjs. PR #261 представлялся как набор рутинных улучшений, но снова трогал только те же два файла. Ни одна из заявок не была принята - все закрыты без слияния. Однако сама структура атаки демонстрирует высокий уровень подготовки: злоумышленник рассчитывал, что хотя бы один из правдоподобных предлогов пройдёт ревью.

Реальная угроза заключается в том, как Astro обрабатывает свой конфигурационный файл. Каждый вызов команд astro build, astro dev или astro preview приводит к выполнению astro.config.mjs как модуля Node.js. Это происходит до загрузки любого пользовательского кода, с полным доступом к переменным окружения и файловой системе. Таким образом, любой разработчик, клонировавший ветку с вредоносным коммитом и запустивший Astro, непреднамеренно активировал бы вредоносную нагрузку. Для скрытия кода в diff атакующий использовал приём с длинной строкой горизонтальных пробелов: обфусцированный IIFE (немедленно вызываемое функциональное выражение) был дописан в конец строки после сотен пробелов. При просмотре diff в GitHub эта часть выходит за пределы видимой области, и рецензент видит лишь безобидную строку с закрывающей скобкой.

Согласно анализу, проведённому на основе статического восстановления полезной нагрузки, код реализует двухэтапную схему связи с командным центром. Первый этап - так называемый Stage B. Он проверяет кампанийный маркер, записанный в глобальную переменную, и на его основе выбирает один из трёх жёстко заданных IP-адресов C2. Затем отправляется HTTP GET-запрос к ресурсу /$/boot, в котором через заголовок Sec-V передаётся маркер. Ответ сервера расшифровывается статическим XOR-ключом "ThZG+0jfXE6VAGOJ" и передаётся в eval(). Таким образом, злоумышленник может в любой момент обновить исполняемый код бот-клиента, просто изменив ответ на C2, без каких-либо правок в репозитории.

Второй этап - это блокчейн-реле, названный Stage A. Параллельно с boots-запросом код обращается к публичному API Tron (api.trongrid.io) для получения последней подтверждённой транзакции с адреса TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP. Шестнадцатеричное поле данных транзакции декодируется в UTF-8 и переворачивается - полученная строка оказывается хешем транзакции Binance Smart Chain (BSC). Если запрос к Tron не удаётся, используется запасной вариант: запрос к сети Aptos через fullnode.mainnet.aptoslabs.com, где последняя транзакция содержит хеш BSC напрямую. Затем код обращается к публичному RPC BSC (bsc-dataseed.binance.org) и извлекает из поля input транзакции зашифрованный командный блок, разделённый меткой ?.?. После XOR-расшифровки ключом "2[gWfGj;<:-93Z^C" команда выполняется через eval(). Такая архитектура делает второй канал управления практически неблокируемым: публичные блокчейн-ноды не являются частью инфраструктуры атакующего, и их блокировка приведёт к нарушению работы легитимных инструментов.

Уязвимость использует тот же набор криптографических маркеров, что и кампания PolinRider, ранее атрибутированная северокорейским субъектам (DPRK). Сравнение с данными OpenSourceMalware показывает идентичный генератор обфускации (_$_1e42), одинаковые ключи и адрес Tron dead drop. Однако вектор доставки отличается от прежних методов PolinRider: раньше атакующие распространяли вредонос через ядовитые npm-пакеты или поддельные тестовые задания, а здесь задействован pull request в открытый репозиторий с последующим выполнением через инструмент сборки.

Для защиты разработчикам и администраторам репозиториев следует обращать внимание на несоответствие между описанием PR и реальным списком изменённых файлов. Быстрый индикатор - если заявлено исправление React-компонента, а diff содержит изменения в astro.config.mjs и .gitignore, это должно вызвать подозрение. На уровне автоматизации сканирование конфигурационных файлов на наличие вызовов createRequire в сочетании с любыми сетевыми запросами или eval() позволяет выявить подобный загрузчик. Если ветка с вредоносным коммитом была когда-либо собрана локально или в CI, необходимо считать такую среду скомпрометированной, сменить все токены и ключи, а также проверить сетевые логи на предмет соединений с указанными C2-адресами.

Данный инцидент демонстрирует новое сочетание известных тактик: фиктивное описание PR с вымышленными тестовыми результатами, скрытие diff в горизонтальных пробелах и использование публичных блокчейнов как неубиваемого канала связи. Вредоносные pull request становятся всё более изощрёнными, и для сообщества open-source критически важно повышать бдительность при ревью даже самых безобидных на вид предложений.

Индикаторы компрометации

IPv4

  • 166.88.54.158
  • 198.105.127.210

IPv4 Port Combination

  • 23.27.202.27:27017

URL

  • http://166.88.54.158
  • http://198.105.127.210
  • http://23.27.202.27:27017

Комментарии: 0