Anchor, Solana ekosisteminde en yaygın geliştirme çerçevesidir. Deklaratif hesap doğrulama, otomatik serileştirme ve yerleşik güvenlik kontrolleri gibi özelliklerle geliştirme engellerini büyük ölçüde düşürür. Ancak çerçeve, kolaylık sağlarken, arka planda her programın içine geliştiricilerin bilmediği bazı dahili komutlar ekler. Bu "gizli" komutlar, belirli koşullar altında saldırganlar tarafından kullanılarak ciddi finansal kayıplara neden olabilir.
Beosin güvenlik ekibi, düşük sürümlü Anchor'da geliştiricilerin Program-owned PDA hesaplarını belirtmek için AccountInfo kullandığında, saldırganların Anchor'un otomatik olarak enjekte ettiği IDL komutuyla yalnızca iki adımda hesap kontrolünü ele geçirebileceğini ve içindeki tüm SOL'ları boşaltabileceğini ortaya koyacak; bu süreçte herhangi bir ayrıcalığa gerek yoktur.
Birinci: IDL Talimatları ve İlgili Mekanizma Analizi
1.1 IDL Talimatı
Anchor, inşa sırasında no-idl özelliği açıkça etkinleştirilmediği sürece, her programda bir dizi IDL (Interface Definition Language, Arayüz Tanım Dili) yönetim komutu enjekte eder. Bu komutlar şunlardır:
IdlCreateAccount: Zincir üzerinde bir IDL hesabı oluşturun
IdlWrite: IDL hesabına / önbellek hesabına veri yazma
IdlSetAuthority: IDL hesabının yetkilisini değiştirin
IdlCloseAccount: IDL hesabını kapatın ve tüm lamports'larını belirtilen alıcıya aktarın
IdlResizeAccount: IDL hesap boyutunu ayarla
IdlCreateBuffer: IDL Buffer hesabını oluşturun
IdlSetBuffer: Önbellek hesabındaki verilerle resmi IDL hesabını üzerine yazın
Bu komutlar, zincir üstü IDL yönetimi için tasarlanmıştır, ancak programın sahip olduğu hesaplar üzerinde özel yetkilere sahiptir (verileri okuma/yazma, kontrol edeni değiştirme, kapatma ve lamports’ları aktarma) — bu da açıklığın temelidir. Saldırgan, geliştiricilerin yazdığı herhangi bir iş komutunu çağırmak zorunda değildir; doğrudan bu yerleşik komutları çağırabilir.
1.2 IDL arabellek hesabı
IDL tampon hesabı (IDL Buffer), Anchor tarafından büyük IDL verilerini parçalı olarak yüklemek için kullanılan geçici bir hesaptır. Tam IDL (JSON ile sıkıştırılmış) tek bir işlem boyutu sınırını aşabileceğinden, Anchor önce IdlCreateBuffer ile bir tampon oluşturmayı, ardından birden fazla IdlWrite ile veriyi parçalı olarak yazmayı ve nihayetinde IdlSetBuffer ile tek seferde göndermeyi sağlar.
IDL hesabının / tampon hesabının veri yapısı anahtar noktadır: Sabit bir düzenle başlayan bir başlıkla başlar ve bu başlıkta authority: Pubkey alanı bulunur (bu test çıktısında controller olarak adlandırılır). IDL komutu, bu alan aracılığıyla “hangi kişinin bu hesapla işlem yapma yetkisi olduğuna” karar verir.
Ancak sorun şudur: düşük sürüm Anchor'un IdlCreateBuffer işleme mantığı, bu programın sahibi olduğu herhangi bir hesabı giriş olarak kabul edip yetkiyi doğrudan işlem imzalayıcısına atar. Yani bir hesabın sahibi bu programsa (örneğin programın PDA kasanı), saldırgan bu hesabın kontrolcüsü olarak kendini yazabilir ve ardından IdlCloseAccount ile hesaptaki tüm SOL'ları meşru bir şekilde aktarabilir.
1.3 Güvenlik açığı tetikleme koşulları
Bu zafiyetin tetiklenmesi için aşağıdaki koşulların aynı anda karşılanması gerekir:
- Eski sürüme sahip Anchor kullanın: Yetersiz hesap doğrulaması nedeniyle tehlikeli IDL komutları, iş hesaplarını IDL hesapları olarak yanlış işleme izin verir.
- no-idl etkinleştirilmedi: Program, inşa sırasında varsayılan olarak enjekte edilen IDL yönerge giriş noktasını korudu ve saldırı yüzeyi dışa açıldı.
- AccountInfo ile programın sahip olduğu PDA'yı bildirin: Geliştiriciler,资金 hesabı (örneğin, kasa PDA) taşımak için doğrudan AccountInfo kullanır; bu, Anchor tipleştirilmiş hesaplarının (örneğin, Account) sunduğu discriminator/owner ayrımını içermez ve bu hesap, IDL komutu için IDL hesabından "ayırt edilemez" hale gelir.
- Hesap programa aittir ve lamports tutar: owner == bu program, IDL komutunun bunu işlemesi için önkoşuldur; SOL tutulması durumunda boşaltılma değeri vardır.
Yukarıdaki koşullar karşılandığında, saldırganın yalnızca iki normal işlem yapması yeterlidir: önce IdlCreateBuffer ile controller’ı ele geçirip, ardından IdlCloseAccount ile tüm SOL’u aktararak saldırıyı tamamlayabilir; bu süreçte hedef programdan herhangi bir izin verilmesine gerek yoktur.
1.4 Saldırı Zinciri
Aşağıda, Anchor'un dahili uygulamasını birlikte inceleyerek, saldırganın nasıl sadece iki dahili komut kullanarak sıradan bir fon kasanızı bir IDL hesabına "gizleyip" boşalttığını adım adım açıklayacağız.
Saldırı akışının genel bakışı
Adım 1: IdlCreateBuffer(treasury, signer = attacker)
└─ treasury.controller ==> attacker (kontrolcü olur)
Adım 2: IdlCloseAccount(treasury, authority = attacker, dest = attacker)
└─ treasury.lamports ==> attacker (hazine boşaltılır)
Adım 1: IdlCreateBuffer ile yetkiyi ele geçirin
Anchor içsel implementasyonu yaklaşık olarak şöyledir:
#[derive(Accounts)]pub struct IdlCreateBuffer {
#[account(zero)] // ← Key: bir discriminator değeri tümüyle 0 olan hesap 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 as authority Ok(())}
#[account(zero)] anlamı: Programın sahibi olan ve discriminator'ı tamamen sıfır olan bir hesabı, başlatılmamış bir IDL hesabı olarak başlatmaktır. Vault ise tam olarak bu iki koşulu aynı anda karşılar:
Kasanın durumu
Şartlar
program tarafından sahiplenildi
init_if_needed sonrası sahip, bu programa atanmıştır
diskriminatör tamamen sıfır
AccountInfo türü kullanılır, Anchor discriminator yazmaz, veriler sıfırdır.
Saldırgan, vault'ı IdlCreateBuffer'a aktardıktan sonra:
- Anchor, vault öncesinde ilk 8 baytına IdlAccount ayrıştırıcısını yazın;
- Saldırganın genel anahtarını authority alanına yazın.
Bu şekilde, kasa, saldırganın yetkili olduğu bir IdlAccount olarak "sahte" hale getirildi — içeriğindeki SOL miktarı ise tamamen değişmeden kaldı.
İkinci adım: IdlCloseAccount —— Fonları boşaltın
#[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}
Şu anda kasa tamamen doğrulandı:
- discriminator IdlAccount ile eşleştirilir
- authority alanı = Saldırganın genel anahtarı
- has_one = authority doğrulaması başarılı
Bu nedenle kasanın içindeki tüm lamports (kullanıcının yatırdığı tüm SOL), yasadışı olarak saldırganın hesabına aktarıldı ve kasa sıfırlandı.
Temel neden:
deposit.rs içinde türlenmiş bir Anchor Hesabı yerine AccountInfo kullanılması, bu güvenlik açığının kritik nedenidir:
(1) Türü olan hesaplar (örneğin Account) başlatıldığında bu yapıya özel 8 bayt discriminator yazılır;
(2) Kendi discriminator'ı yazıldıktan sonra, Anchor, bunu IdlAccount (discriminator uyumsuz) olarak kullanamaz, bu nedenle ilk adım olan IdlCreateBuffer başarısız olur ve saldırı zinciri kırılır.
İkinci: Vaka analizi
Aşağıdaki test, Anchor 0.31.0 ile oluşturulmuş bir çapraz zincir köprü sözleşmesinin PoC'sidir. Test, sahibi köprü uygulaması olan ve kullanıcıların 1.001281 SOL yatırdığı gerçek bir köprü kasanını simüle eder. Saldırgan cüzdanı başlangıçta 2 SOL'a sahiptir ve herhangi bir ayrıcalığa sahip değildir.
Proje
Sayı / Açıklama
Köprü Programı
CJutNlr8d3oxdb11ReOLaZd5jPsqUxgwt2mv5e7equtE
Kasayı (Treasury)
DxGkzaMhP5Wy824GhoehErdD6MudfuEPGPwaV2s77FsM
KuCoin Sahibi
= Köprü programı (programa ait PDA)
KuCoin başlangıç bakiyesi
1.001281 SOL (Kullanıcı tarafından yatırılan tutar)
Başlangıç Controller
0x0000...0000 (tümü sıfır, ayarlanmamış)
Saldırganın cüzdanı başlangıç bakiyesi
2.000000 SOL
Gerekli yetkiler
YOK (Herhangi bir yetki gerekmiyor)
İşlem sayısı
2 adet
KuCoin Son Bakiye
0,000000 SOL (boşaltıldı)
Saldırgan kazanç elde etti
+1.001281 SOL
PoC çalışma ekran görüntüsü (tests/poc-idl-hijack.ts):
Görüldüğü gibi, Adım 1'deki IdlCreateBuffer, saldırganı kasa kontrolörü olarak ayarlıyor (bakiye değişmiyor); Adım 2'deki IdlCloseAccount, kasanın 1,001281 SOL'ünü tamamen saldırganın cüzdanına aktarıyor ve kasa bakiyesini sıfırlıyor.
Düzeltme / Koruma Önerileri:
(1) Anchor sürümünü yükseltin: En son sürüm bu sorunu düzeltti (IDL komutları, IDL hesapları ile iş hesapları arasında sıkı bir ayrım yapar); düşük sürüm Anchor kullanmaktan kaçınmak en doğrudan ve temel savunma yöntemidir.
(2) no-idl etkinleştirilerek yapılandırın: Üretim ortamı programlarında IDL komut enjeksiyonunu açıkça kapatın ve bu saldırı yüzeyini kökten ortadan kaldırın.
(3) Diskriminatörlü ve sahiplik doğrulaması içeren Account / SystemAccount gibi tipleştirilmiş hesapları, boş AccountInfo yerine finansal hesapları taşımak için kullanın, böylece IDL komutları bunları yanlışlıkla tanımlamaz.
(4) Temizlenebilir hesapları sahiplenen minimum program: Fonların depolandığı PDA'ya açık bir sahip/seeds/discriminator kısıtlaması ekleyin ve kritik komutlarda hesap ayrıştırıcısını doğrulayın.
Sonuç
Bu açıklık, "çerçeve gizli komutu + hesap türü tanımlama eksikliği" kombinasyonudur. Geliştiriciler, Anchor'ın varsayılan olarak enjekte ettiği IDL komutlarının gerçek bir saldırı yüzeyi olduğunu anlamalı ve çerçeveyi güncellemeli, ayrıca fon hesaplarında güçlü tip sınırlamaları uygulamalıdır; bu şekilde yetkisiz fon boşaltma riski etkili bir şekilde önlenir.
Beosin, projelerin başlatılmasından önceki akıllı sözleşme güvenlik denetimleri, projelerin çalışması sırasında güvenlik risklerinin izlenmesi ve engellenmesi, çalınan varlıkların geri kazanılması, sanal varlıkların para aklama (AML) önleme ve soruşturma takibi konularında uzmanlaşmış önde gelen bir blockchain güvenlik ve düzenleyici uyumluluk teknolojisi şirketidir. Beosin, dünya çapında 20'den fazla ülke ve bölgedeki düzenleyici ve uygulama kurumlarına, 200'den fazla sanal varlık hizmet sağlayıcısına ve 4.500'den fazla Web3 projesine “tek duraklı” blockchain uyumluluk ürünleri ve güvenlik hizmetleri sunmaktadır. Lütfen公众号 mesaj kutusuna tıklayarak bize ulaşın.

