Beosin раскрывает критическую уязвимость инструкции IDL в устаревшей версии фреймворка Solana Anchor

iconMetaEra
Поделиться
AI summary iconСводка
Команда безопасности Beosin выявила критическую уязвимость в инструкции IDL в старых версиях фреймворков Solana Anchor. Уязвимость позволяет злоумышленникам перехватить аккаунты PDA, принадлежащие программе, путем эксплуатации объявлений AccountInfo, что позволяет похитить средства в два этапа без привилегий пользователя. Инцидент подчеркивает необходимость создания более надежной системы соответствия при разработке смарт-контрактов. С приближением MiCA (Европейский регламент рынков криптоактивов) такие уязвимости подчеркивают важность проведения реального времени аудита безопасности и соблюдения регуляторных стандартов.

Anchor — это наиболее популярная разработческая рамка в экосистеме Solana. Благодаря таким функциям, как декларативная проверка аккаунтов, автоматическая сериализация и встроенные проверки безопасности, она значительно снижает порог входа для разработчиков. Однако, предоставляя удобство, фреймворк в фоновом режиме вставляет в каждую программу некоторые внутренние инструкции, о которых разработчики могут не знать. Эти «скрытые» инструкции могут быть использованы злоумышленниками при определенных условиях, что приводит к серьезным потерям средств.

В этой статье команда безопасности Beosin раскроет ключевую модель уязвимости: в старых версиях Anchor, когда разработчики используют AccountInfo для объявления PDA-счетов, принадлежащих программе, злоумышленник может с помощью автоматически вставленной Anchor IDL-инструкции за две операции завладеть контролем над счетом и изъять все находящиеся на нем SOL, не требуя никаких привилегий.

I. Анализ команд IDL и связанных механизмов

1.1 IDL-инструкция

Anchor по умолчанию вставляет набор инструкций управления IDL (Interface Definition Language, язык определения интерфейсов) в каждую программу, если только функция no-idl не включена явно при сборке. Эти инструкции включают:

IdlCreateAccount: создание цепочечного IDL-счета

IdlWrite: запись данных в аккаунт IDL / буферный аккаунт

IdlSetAuthority: изменение authority (контроллера) аккаунта IDL

IdlCloseAccount: закрыть счет IDL и перевести все lamports указанному получателю

IdlResizeAccount: изменение размера счета IDL

IdlCreateBuffer: создание счета буфера IDL (IDL Buffer)

IdlSetBuffer: заменить данные в официальном IDL-счете данными из счета буфера

Эти инструкции были разработаны для управления IDL в цепочке, но они обладают особыми правами операций для учетных записей, принадлежащих программе (чтение и запись данных, изменение контроллера, закрытие и перевод lamports) — именно в этом и заключается суть уязвимости. Атакующему не нужно вызывать никакие бизнес-инструкции, написанные разработчиками — достаточно напрямую вызвать эти встроенные инструкции.

1.2 Буферный счет IDL

IDL-буфер — это временный аккаунт, введенный Anchor для пошаговой загрузки крупных IDL-данных. Поскольку полный IDL (после сжатия в JSON) может превышать лимит размера одной транзакции, Anchor позволяет сначала создать буфер с помощью IdlCreateBuffer, затем записать данные по частям с помощью нескольких команд IdlWrite и в конце一次性 отправить их с помощью IdlSetBuffer.

Ключевым моментом является структура данных IDL-счета / буферного счета: он начинается с заголовка с фиксированной структурой, содержащего поле authority: Pubkey (в тестовом выводе этой статьи оно называется controller). IDL-инструкция использует это поле для определения «кто имеет право управлять этим счетом».

Проблема заключается в том, что логика обработки IdlCreateBuffer в более ранних версиях Anchor рассматривает любой переданный аккаунт, принадлежащий данной программе, как буферный аккаунт и напрямую устанавливает authority в качестве подписавшего транзакцию лица. То есть, если владелец аккаунта — это сама программа (например, PDA-сокровищница программы), злоумышленник может назначить себя контроллером, а затем с помощью IdlCloseAccount легально перевести все SOL из аккаунта себе.

1.3 Условия срабатывания уязвимости

Для активации этой уязвимости необходимо одновременное выполнение следующих условий:

  • Использование старой версии Anchor: опасная инструкция IDL не имеет достаточной проверки учетных записей, что позволяет ошибочно обрабатывать бизнес-учетные записи как учетные записи IDL.
  • no-idl не включен: программа сохранила встроенные по умолчанию входные точки IDL при сборке, что открывает поверхность атаки.
  • Объявление PDA, принадлежащего программе, с помощью AccountInfo: разработчики используют сырой AccountInfo для передачи счетов с деньгами (например, PDA казны), но без типизированных счетов Anchor (например, Account), которые автоматически содержат discriminator / owner, что делает этот счет неразличимым для инструкций IDL по сравнению с учетной записью IDL.
  • Аккаунт принадлежит программе и содержит lamports: owner == эта программа — условие, при котором инструкции IDL могут с ним взаимодействовать; имеет смысл очистить, только если в нем есть SOL.

После выполнения вышеуказанных условий злоумышленнику достаточно совершить две обычные транзакции: сначала IdlCreateBuffer для захвата контроллера, затем IdlCloseAccount для перевода всех SOL, чтобы завершить атаку, не требуя никаких разрешений от целевой программы.

1.4 Цепочка атак

Ниже, с учетом внутренней реализации Anchor, пошагово разберем, как злоумышленник с помощью всего двух встроенных команд может «притворить» обычный волт с деньгами учетной записью IDL и опустошить его.

Обзор процесса атаки

Шаг 1: IdlCreateBuffer(treasury, signer = attacker)    
└─ treasury.controller ==> attacker (становится контроллером)  
Шаг 2: IdlCloseAccount(treasury, authority = attacker, dest = attacker)  
└─ treasury.lamports ==> attacker (опустошает казну)

Шаг 1: IdlCreateBuffer захватывает authority

Внутренняя реализация Anchor выглядит примерно так:

#[derive(Accounts)]pub struct IdlCreateBuffer {  
#[account(zero)]          // ← Key: аккаунт с его дискриминатором, состоящим из нулей  pub buffer: Account,  
pub authority: Signer,}    
pub fn idl_create_buffer(ctx: Context) -> Result 
{ 
 let idl = &mut ctx.accounts.buffer;  idl.authority = *ctx.accounts.authority.key; // установить attack в качестве authority  Ok(())}

Значение #[account(zero)]: принимает аккаунт с дискриминатором, состоящим из нулей и принадлежащий программе, как неинициализированный IDL-аккаунт для инициализации. Vault удовлетворяет обоим этим условиям:

Статус хранилища

Условия

принадлежит программе

После init_if_needed владелец установлен на эту программу.

дискриминатор полностью нулевой

Используя тип AccountInfo, Anchor не записывает дискриминатор, данные нулевые

Затем злоумышленник передает vault в IdlCreateBuffer:

  • Anchor записывает дискриминатор IdlAccount в первые 8 байтов перед vault;
  • Запишите открытый ключ атакующего в поле authority.

Таким образом, vault был «подделан» как IdlAccount с атакующим в качестве authority — при этом его внутренние SOL остались нетронутыми.

Шаг 2: IdlCloseAccount —— Очистка средств

#[derive(Accounts)]pub struct IdlCloseAccount {  #[account(mut, has_one = authority)]  // ← check authority == signer  pub account: Account,  pub authority: Signer,  #[account(mut)]  pub destination: AccountInfo,  // ← attack wallet}

В настоящее время хранилище полностью прошло проверку:

  • discriminator соответствует IdlAccount
  • поле authority = открытый ключ атакующего
  • has_one = authority проверка пройдена

Все lamports внутри vault (все внесенные пользователем SOL) были законно переведены на счет злоумышленника, иvault стал нулевым.

Основная причина:

Использование AccountInfo в deposit.rs вместо типизированного Anchor Account стало фатальной причиной этой уязвимости:

(1) При инициализации аккаунтов определенного типа (например, Account) записывается 8-байтовый дискриминатор, специфичный для этой структуры;

(2) После записи собственного дискриминатора Anchor больше не может рассматривать его как IdlAccount (дискриминатор не совпадает), и первый шаг IdlCreateBuffer завершится неудачей, а цепочка атаки будет прервана.

Два. Анализ кейсов

Тесты основаны на контракте моста PoC, построенном на Anchor 0.31.0. Тест имитирует реальный мостовой казначейство (Bridge Treasury), владельцем которого является сам мостовой процесс, содержащий 1,001281 SOL, внесенные пользователями. Кошелек атакующего изначально содержит 2 SOL и не имеет никаких привилегий.

Проект

Значение / Описание

Бридж-программа

CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE

Казначейский счет (Treasury)

DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM

Владелец KuCoin

= Программа, владеющая PDA

Начальный баланс кошелька

1,001281 SOL (средства пользователя)

Исходный контроллер

0x0000...0000 (все нули, не установлено)

Initial balance of attacker's wallet

2,000000 SOL

Требуемые привилегии

НЕТ (не требуется никакой авторизации)

Количество сделок

2 сделки

Финальный баланс кошелька

0,000000 SOL (очищено)

Атакующие получили прибыль

+1.001281 SOL

Скриншот выполнения PoC (tests/poc-idl-hijack.ts):

https://wdcdn.qpic.cn/MTMxMDI3MDE1MTgxMDU0NzA_812435_a37-yrU_dY02i1_I_1785900257?w=1080&h=1287

Как видно, IdlCreateBuffer в шаге 1 назначает атакующего контроллером хранилища (баланс не изменяется); IdlCloseAccount в шаге 2 переводит все 1,001281 SOL из хранилища на кошелек атакующего, после чего баланс хранилища становится нулевым.

Рекомендации по устранению/защите:

(1) Обновление версии Anchor: последняя версия устраняет эту проблему (IDL-инструкции строго разделяют IDL-аккаунты и бизнес-аккаунты); использование более старых версий Anchor — это самый прямой и фундаментальный способ защиты.

(2) Включить no-idl при сборке: явно отключить инъекцию инструкций IDL для программ в производственной среде, устраняя эту поверхность атаки на源头.

(3) Используйте типизированные аккаунты вместо простых AccountInfo: используйте типизированные аккаунты с проверкой discriminator и owner, такие как Account / SystemAccount, чтобы предотвратить ошибочное распознавание их инструкциями IDL.

(4) Минимизация счетов, которыми может распоряжаться программа: добавьте четкие ограничения owner / seeds / discriminator для PDA, хранящей средства, и проверяйте идентификаторы счетов в ключевых инструкциях.

Заключение

Уязвимость по сути представляет собой комбинацию «скрытых инструкций фреймворка + отсутствие проверки типа аккаунта». Разработчики должны понимать, что инструкции IDL, автоматически вставленные Anchor, являются реальной атакующей поверхностью; обновление версии фреймворка и применение строгой типизации для счетов с финансами позволят эффективно исключить риск неавторизованного изъятия средств.

Beosin — ведущая компания в области безопасности блокчейна и регуляторного соответствия, специализирующаяся на аудите смарт-контрактов до запуска проекта, мониторинге и блокировке рисков безопасности во время работы проекта, возврате украденных средств, противодействии отмыванию денег (AML) на виртуальных активах и расследованиях. Beosin предоставляет «все-in-one» решения для соблюдения нормативных требований и безопасности блокчейна более чем 200 сервисам виртуальных активов, 4500 проектам Web3 и регуляторным и правоохранительным органам более чем в 20 странах и регионах мира. Свяжитесь с нами через поле для сообщений в нашем канале.

Отказ от ответственности: Информация на этой странице может быть получена от третьих лиц и не обязательно отражает взгляды или мнения KuCoin. Данный контент предоставляется исключительно в общих информационных целях, без каких-либо заверений или гарантий, а также не может быть истолкован как финансовый или инвестиционный совет. KuCoin не несет ответственности за ошибки или упущения, а также за любые результаты, полученные в результате использования этой информации. Инвестиции в цифровые активы могут быть рискованными. Пожалуйста, тщательно оценивайте риски, связанные с продуктом, и свою устойчивость к риску, исходя из собственных финансовых обстоятельств. Для получения более подробной информации, пожалуйста, ознакомьтесь с нашими Условиями использования и Уведомлением о риске.