Beosin розкриває критичну вразливість інструкції IDL у старій версії фреймворку Solana Anchor

iconMetaEra
Поділитися
AI summary iconКороткий зміст
Команда безпеки Beosin виявила критичну вразливість інструкції IDL у старих версіях фреймворків Solana Anchor. Ця недолік дозволяє зловмисникам перехопити акаунти PDA, що належать програмі, шляхом експлуатації оголошень AccountInfo, що дозволяє викрасти кошти за два кроки без прав користувача. Цей інцидент підкреслює необхідність створення більш суворої системи відповідності при розробці смартконтрактів. З наближенням MiCA (Європейський регламент ринків криптоактивів) такі вразливості підкреслюють важливість проведення реального часу аудиту безпеки та дотримання регуляторних стандартів.

Anchor — це найпопулярніший фреймворк для розробки в екосистемі Solana. Він значно знижує бар’єри для входу завдяки декларативній перевірці облікових записів, автоматичній серіалізації та вбудованим перевіркам безпеки. Однак, надаючи зручність, фреймворк у тилі додає до кожної програми деякі внутрішні інструкції, яких розробники можуть не усвідомлювати. Ці «приховані» інструкції можуть бути використані зловмисниками в певних умовах, що призводить до серйозних втрат коштів.

Команда безпеки Beosin розкриє ключовий шаблон вразливості: у старих версіях Anchor, коли розробник використовує AccountInfo для оголошення PDA-рахунків, що належать програмі, зловмисник може за допомогою інструкцій IDL, автоматично вбудованих Anchor, за двома кроками заволодіти контролем над рахунком та вивести всі 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 Buffer — це тимчасовий обліковий запис, введений 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), що не містять discriminator / owner, які є в типізованих рахунках Anchor (наприклад, Account), що робить цей рахунок нерозрізнюваним з рахунками 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 викрадає повноваження

Внутрішня реалізація 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 як повноважену особу  Ok(())}

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

Стан vault

Умови

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

після init_if_needed власником встановлено цю програму

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

Використовуйте тип AccountInfo, Anchor не записує дискримінатор, дані нульові

Тоді атакуючий передає vault у IdlCreateBuffer:

  • Anchor записує discriminator IdlAccount у перші 8 байтів vault;
  • Запишіть публічний ключ атакувача у поле authority.

На цьому vault був «підімнений» під IdlAccount із атакувачем як authority — а всередині нього SOL залишився незчепленим.

Другий крок: 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}

В цей момент vault повністю пройшов перевірку:

  • дискримінатор відповідає IdlAccount
  • поле authority = публічний ключ атакувача
  • has_one = authority перевірка пройдена

Отже, всі lamports (усі 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 і не мав жодних привілеїв.

Проект

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

Мостова програма (Bridge Program)

CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE

Казначейство (Treasury)

DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM

Власник KuCoin

= Програма-власник PDA

Початковий баланс коштовностей

1,001281 SOL (кошти користувача)

Початковий контролер

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

Початковий баланс гаманця атакувача

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: використовуйте типи Account / SystemAccount з перевіркою discriminator та owner для зберігання коштів, щоб уникнути помилкового визначення цих акаунтів інструкціями IDL.

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

Висновок

Сутністю цієї вразливості є поєднання «прихованих інструкцій фреймворку + відсутності перевірки типу облікового запису». Розробники повинні усвідомити, що інструкції IDL, які за замовчуванням вставляються Anchor, є реальними векторами атаки; оновлення версії фреймворку та застосування суворих типів до облікових записів з коштами дозволить ефективно запобігти такому ризику неавторизованого зняття коштів.

Beosin — це лідируюча компанія у сфері безпеки блокчейну та відповідності регуляторним вимогам, яка спеціалізується на аудиті безпеки смарт-контрактів до запуску проекту, моніторингу та блокування ризиків безпеки під час роботи проекту, відновленні вкрадених коштів, протидії відмиванню коштів (AML) та слідчим діям щодо віртуальних активів. Beosin надає «однохвилинні» рішення для відповідності блокчейну та послуги безпеки для 200+ постачальників віртуальних активів, 4500+ проектів Web3 та регуляторних та правоохоронних органів у понад 20 країнах та регіонах світу. Будь ласка, зв’яжіться з нами через поле для повідомлень у нашому каналі.

Відмова від відповідальності: Інформація на цій сторінці може бути отримана від третіх осіб і не обов'язково відображає погляди або думки KuCoin. Цей контент надається лише для загального інформування, без будь-яких запевнень або гарантій, а також не може розглядатися як фінансова або інвестиційна порада. KuCoin не несе відповідальності за будь-які помилки або упущення, а також за будь-які результати, отримані в результаті використання цієї інформації. Інвестиції в цифрові активи можуть бути ризикованими. Будь ласка, ретельно оцініть ризики продукту та свою толерантність до ризику, виходячи з ваших власних фінансових обставин. Для отримання додаткової інформації, будь ласка, зверніться до наших Умов використання та Розкриття інформації про ризики.