Beosin Mengungkap Kerentanan Instruksi IDL Kritis pada Framework Solana Anchor Versi Lama

iconMetaEra
Bagikan
AI summary iconRingkasan
Tim keamanan Beosin telah mengidentifikasi kerentanan instruksi IDL kritis pada kerangka kerja Solana Anchor versi lama. Kelemahan ini memungkinkan penyerang merebut akun PDA yang dimiliki program dengan memanfaatkan deklarasi AccountInfo, memungkinkan pencurian dana dalam dua langkah tanpa izin pengguna. Kejadian ini menyoroti perlunya kerangka kepatuhan yang lebih kuat dalam pengembangan kontrak pintar. Dengan mendekatnya MiCA (Peraturan Pasar Aset Kripto UE), kerentanan semacam ini menekankan pentingnya audit keamanan real-time dan kepatuhan terhadap standar regulasi.

Anchor adalah kerangka pengembangan paling utama di ekosistem Solana. Dengan fitur-fitur seperti verifikasi akun deklaratif, serialisasi otomatis, dan pemeriksaan keamanan bawaan, ia secara signifikan menurunkan hambatan untuk berkembang. Namun, sambil menyediakan kemudahan, kerangka ini secara diam-diam menyisipkan beberapa instruksi internal ke setiap program yang mungkin tidak diketahui oleh pengembang. Instruksi "tersembunyi" ini dapat dimanfaatkan oleh penyerang dalam kondisi tertentu, menyebabkan kerugian dana yang serius.

Tim keamanan Beosin akan mengungkap pola kerentanan kunci: di versi Anchor yang lebih lama, ketika pengembang menggunakan AccountInfo untuk mendeklarasikan akun PDA yang dimiliki program, penyerang dapat mengambil kendali akun dan mengosongkan semua SOL di dalamnya hanya dengan dua langkah melalui instruksi IDL yang diinject secara otomatis oleh Anchor, tanpa memerlukan hak istimewa apa pun.

Satu, Analisis Instruksi IDL dan Mekanisme Terkait

1.1 Perintah IDL

Anchor secara default akan menyisipkan sekelompok instruksi IDL (Interface Definition Language) ke dalam setiap program, kecuali fitur no-idl diaktifkan secara eksplisit saat build. Instruksi-instruksi ini mencakup:

IdlCreateAccount: Membuat akun IDL di rantai

IdlWrite: Menulis data ke akun IDL / akun buffer

IdlSetAuthority: Mengubah authority akun IDL

IdlCloseAccount: Menutup akun IDL dan mentransfer seluruh lamports ke penerima yang ditentukan

IdlResizeAccount: Menyesuaikan ukuran akun IDL

IdlCreateBuffer: Membuat akun buffer IDL (IDL Buffer)

IdlSetBuffer: Menimpa akun IDL resmi dengan data dari akun buffer

Instruksi-instruksi ini dirancang awalnya untuk manajemen IDL on-chain, tetapi mereka memiliki kemampuan khusus terhadap akun yang dimiliki program (membaca dan menulis data, mengubah pemilik, menutup, dan memindahkan lamports)—inilah inti dari kerentanan tersebut. Penyerang tidak perlu memanggil instruksi bisnis apa pun yang ditulis oleh pengembang; mereka cukup memanggil instruksi bawaan ini secara langsung.

1.2 Akun buffer IDL

Akun buffer IDL (IDL Buffer) adalah akun sementara yang diperkenalkan oleh Anchor untuk mengunggah data IDL besar secara bertahap. Karena IDL lengkap (setelah dikompresi dalam format JSON) mungkin melebihi batas ukuran satu transaksi, Anchor memungkinkan pembuatan buffer terlebih dahulu melalui IdlCreateBuffer, kemudian menulisnya secara bertahap menggunakan beberapa IdlWrite, dan akhirnya mengirimkan semuanya sekaligus melalui IdlSetBuffer.

Poin kuncinya adalah struktur data akun IDL / akun buffer: ia dimulai dengan header dengan tata letak tetap, yang berisi bidang authority: Pubkey (disebut controller dalam output pengujian artikel ini). Instruksi IDL menggunakan bidang ini untuk menentukan "siapa yang berwenang mengoperasikan akun tersebut".

Namun, masalahnya adalah: logika pemrosesan IdlCreateBuffer pada versi Anchor yang lebih lama akan menginisialisasi setiap akun yang diteruskan dan dimiliki oleh program ini sebagai akun buffer, serta langsung menetapkan otoritas sebagai penandatangan transaksi. Artinya, selama pemilik suatu akun adalah program ini (misalnya, PDA vault program), penyerang dapat menetapkan dirinya sendiri sebagai pengendali akun tersebut, lalu secara sah memindahkan seluruh SOL dari akun tersebut menggunakan IdlCloseAccount.

1.3 Kondisi Pemicu Kerentanan

Pemicu kerentanan ini memerlukan pemenuhan simultan terhadap kondisi berikut:

  • Menggunakan versi Anchor yang lebih lama: Instruksi IDL yang tidak aman tidak melakukan verifikasi akun yang cukup, memungkinkan akun bisnis salah dianggap sebagai akun IDL.
  • no-idl tidak diaktifkan: program mempertahankan entri instruksi IDL yang disuntikkan secara default saat dibangun, sehingga permukaan serangan terbuka ke luar.
  • Mendeklarasikan PDA yang dimiliki program dengan AccountInfo: Pengembang menggunakan AccountInfo mentah untuk membawa akun dana (misalnya, PDA gudang), tanpa discriminator / pemilik yang disediakan oleh akun berjenis Anchor (seperti Account), sehingga akun tersebut tampak tidak dapat dibedakan dari akun IDL menurut instruksi IDL.
  • Akun dimiliki dan memegang lamports oleh program: owner == program ini adalah prasyarat agar instruksi IDL dapat mengoperasikannya; hanya memiliki SOL yang memberikan nilai untuk dikosongkan.

Setelah memenuhi syarat di atas, penyerang hanya memerlukan dua transaksi biasa: pertama IdlCreateBuffer untuk merebut controller, lalu IdlCloseAccount untuk memindahkan seluruh SOL, sehingga serangan dapat dilakukan tanpa memerlukan izin apa pun dari program target.

1.4 Chain of Attack

Berikut ini, dengan menggabungkan implementasi internal Anchor, secara bertahap diuraikan bagaimana penyerang hanya menggunakan dua instruksi bawaan untuk "menyamar" sebuah vault dana biasa sebagai akun IDL dan mengosongkannya.

Ringkasan alur serangan

Langkah 1  IdlCreateBuffer(treasury, signer = attacker)    
└─ treasury.controller  ==>  attacker   (menjadi controller) 
Langkah 2  IdlCloseAccount(treasury, authority = attacker, dest = attacker)  
  └─ treasury.lamports    ==>  attacker   (mengosongkan treasury)

Langkah pertama: IdlCreateBuffer merebut otoritas

Implementasi internal Anchor kira-kira sebagai berikut:

#[derive(Accounts)]pub struct IdlCreateBuffer {  
#[account(zero)]          // ← Key: akun dengan discriminator-nya semua 0  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; // set attack sebagai otoritas  Ok(())}

Arti dari #[account(zero)] adalah: menerima akun dengan discriminator yang seluruhnya nol dan dimiliki oleh program, sebagai akun IDL yang belum diinisialisasi untuk diinisialisasi. Dan vault secara tepat memenuhi kedua kondisi ini:

Status vault

Syarat

dimiliki oleh program

setelah init_if_needed, owner diatur menjadi program ini

discriminator semuanya nol

Menggunakan tipe AccountInfo, Anchor tidak menulis discriminator, data semuanya nol

Kemudian penyerang memasukkan vault ke dalam IdlCreateBuffer:

  • Anchor menulis discriminator IdlAccount ke 8 byte pertama sebelum vault;
  • Tulis kunci publik penyerang ke bidang authority.

Dengan demikian, vault telah "disamarkan" sebagai IdlAccount dengan penyerang sebagai otoritas—sedangkan SOL di dalamnya tidak bergerak sama sekali.

Langkah kedua: IdlCloseAccount —— Kosongkan dana

#[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 saat ini telah sepenuhnya lolos verifikasi:

  • discriminator cocok dengan IdlAccount
  • Field authority = kunci publik penyerang
  • has_one = otoritas verifikasi berhasil

Dengan demikian, semua lamports (SOL yang disetorkan pengguna) dalam vault secara sah ditransfer ke akun penyerang, dan vault menjadi nol.

Penyebab utama:

Menggunakan AccountInfo di deposit.rs alih-alih Anchor Account yang bertipe adalah akar utama kerentanan ini:

(1) Akun dengan tipe (seperti Account) akan menulis discriminator 8 byte khusus struktur tersebut saat inisialisasi;

(2) Setelah discriminator sendiri ditulis, Anchor tidak lagi dapat menganggapnya sebagai IdlAccount (discriminator tidak cocok), sehingga IdlCreateBuffer pada langkah pertama akan gagal, dan rantai serangan terputus.

Dua, Analisis Kasus

Pengujian berikut didasarkan pada kontrak jembatan lintas rantai PoC yang dibangun dengan Anchor 0.31.0. Pengujian mensimulasikan sebuah brankas jembatan (Bridge Treasury) nyata, dengan owner adalah program jembatan itu sendiri, yang berisi 1.001281 SOL yang disetorkan oleh pengguna. Dompet penyerang awalnya memegang 2 SOL dan tidak memiliki hak istimewa apa pun.

Proyek

Nilai / Keterangan

Bridge Program

CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE

Akun Kas (Treasury)

DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM

Pemilik Vault

= Program-owned PDA

Saldo awal Vault

1.001281 SOL (dana yang disetorkan pengguna)

Controller Awal

0x0000...0000 (semua nol, tidak diatur)

Initial balance of attacker's wallet

2.000000 SOL

Privilege yang diperlukan

TIDAK ADA (tidak memerlukan otorisasi apa pun)

Jumlah transaksi

2 transaksi

Saldo akhir Vault

0.000000 SOL (dikosongkan)

Penyerang mendapat keuntungan

+1.001281 SOL

Screenshot running PoC (tests/poc-idl-hijack.ts):

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

Dapat dilihat, IdlCreateBuffer pada Langkah 1 menetapkan penyerang sebagai controller gudang (saldo tidak berubah); IdlCloseAccount pada Langkah 2 mentransfer seluruh 1.001281 SOL dari gudang ke dompet penyerang, sehingga saldo gudang menjadi nol.

Saran perbaikan/pelindungan:

(1) Tingkatkan versi Anchor: versi terbaru telah memperbaiki masalah ini (perintah IDL akan secara ketat membedakan akun IDL dan akun bisnis), menghindari penggunaan versi Anchor yang lebih lama adalah cara paling langsung dan mendasar untuk pertahanan.

(2) Aktifkan no-idl saat membangun: Nonaktifkan secara eksplisit injeksi instruksi IDL untuk program lingkungan produksi, menghilangkan vektor serangan ini dari sumbernya.

(3) Gunakan akun bertipe daripada AccountInfo kosong: gunakan akun bertipe seperti Account / SystemAccount dengan validasi discriminator dan owner untuk menampung akun dana, sehingga tidak dapat salah diidentifikasi oleh instruksi IDL.

(4) Akun yang dapat dikosongkan yang dimiliki oleh program: tambahkan batasan owner/seeds/discriminator yang jelas pada PDA yang menyimpan dana, dan verifikasi discriminator akun dalam instruksi kunci.

Penutup

Kerentanan ini pada dasarnya adalah kombinasi dari “perintah tersembunyi kerangka kerja + kehilangan identifikasi jenis akun”. Pengembang harus menyadari bahwa perintah IDL yang disuntikkan secara default oleh Anchor merupakan permukaan serangan yang nyata; dengan memperbarui versi kerangka kerja dan menerapkan pembatasan tipe kuat pada akun dana, risiko pengosongan dana tanpa otorisasi semacam ini dapat dicegah secara efektif.

Beosin adalah perusahaan teknologi keamanan blockchain dan kepatuhan regulasi terkemuka yang berfokus pada audit keamanan kontrak cerdas sebelum peluncuran proyek, pemantauan dan pencegahan risiko keamanan selama operasi proyek, pemulihan aset yang dicuri, anti-pencucian uang (AML) untuk aset virtual, serta investigasi dan pelacakan. Beosin telah menyediakan produk kepatuhan blockchain "all-in-one" dan layanan keamanan kepada 200+ penyedia aset virtual, 4.500+ proyek Web3, serta lembaga regulasi dan penegak hukum di lebih dari 20 negara dan wilayah di seluruh dunia. Silakan klik kotak pesan di公众号 untuk menghubungi kami.

Penafian: Informasi pada halaman ini mungkin telah diperoleh dari pihak ketiga dan tidak mencerminkan pandangan atau opini KuCoin. Konten ini disediakan hanya untuk tujuan informasi umum, tanpa representasi atau jaminan apa pun, dan tidak dapat ditafsirkan sebagai saran keuangan atau investasi. KuCoin tidak bertanggung jawab terhadap segala kesalahan atau kelalaian, atau hasil apa pun yang keluar dari penggunaan informasi ini. Berinvestasi di aset digital dapat berisiko. Harap mengevaluasi risiko produk dan toleransi risiko Anda secara cermat berdasarkan situasi keuangan Anda sendiri. Untuk informasi lebih lanjut, silakan lihat Ketentuan Penggunaan dan Pengungkapan Risiko.