Back to Blog

~$7.04M Kayıp: GiddyDefi, Volo Vault ve Daha Fazlası | BlockSec Haftalık

Code Auditing
April 29, 2026
22 min read
Key Insights

Geçtiğimiz hafta boyunca (2026/04/20 - 2026/04/26), BlockSec sekiz saldırı olayını tespit edip analiz etti; toplam tahmini kayıplar yaklaşık 7,04 milyon dolar oldu. Aşağıdaki tablo bu olayları özetlemekte olup her bir olaya ilişkin ayrıntılı analizler sonraki alt bölümlerde sunulmaktadır.

Tarih Olay Tür Tahmini Kayıp
2026/04/19* Özel Dengeleyici Sözleşmesi Keyfi Çağrı ~$64K
2026/04/20 REVLoans (Juicebox) Hatalı Doğrulama ~$50,7K
2026/04/22 Volo Vault / Navi Anahtar Ele Geçirme ~$3,5M
2026/04/22 Kipseli Router Hatalı Doğrulama ~$72,35K
2026/04/23 GiddyDefi Eksik İmza Doğrulaması ~$1,3M
2026/04/25 Purrlend Anahtar Ele Geçirme ~$1,5M
2026/04/26 SingularityFinance Oracle Yanlış Yapılandırması ~$413K
2026/04/26 Scallop Muhasebe Hatası ~$142,7K

*Özel Dengeleyici Sözleşmesi olayı geçen haftaki raporda yer almamıştı ve bütünlük açısından buraya eklenmiştir.

Phalcon Explorer ile Başlayın

Akıllıca Hareket Etmek için İşlemleri İnceleyin

Şimdi ücretsiz deneyin

Haftanın Öne Çıkanı: GiddyDefi

Saldırgan imzayı kırmadı, flaş kredi kullanmadı ve herhangi bir fiyatı manipüle etmedi. İmzasız alanlarını kendi sözleşmesiyle değiştirerek meşru bir imzayı yeniden oynadı. "Kendi imzanı sana karşı kullan" ifadesi, kısmi EIP-712 kapsamının geçerli bir imzayı genel bir izne nasıl dönüştürdünün en saf göstergesidir.

23 Nisan 2026'da Ethereum üzerindeki GiddyVaultV3, yaklaşık 1,3 milyon dolar zarara uğratıldı. İmza şeması yalnızca SwapInfo.data'yı kapsıyor; aggregator, fromToken, toToken ve amount alanlarını EIP-712 hash'inin dışında bırakıyordu. Bu nedenle geçerli bir imza, söz konusu alanlar değiştirilerek yeniden oynanabiliyordu. Saldırgan, aggregator'ı kötü amaçlı bir sözleşmeye ve fromToken'ı stratejinin LP token'ına yönlendirerek yaklaşık 1,3 milyon doları boşalttı.

Arka Plan

GiddyVaultV3 (0x5f0a...4318), kullanıcıların deposit() ve withdraw() aracılığıyla fon yatırıp çektiği bir getiri çiftçiliği vault sözleşmesidir. Her işlemin, arka uç tarafından imzalanmış bir EIP-712 imzasını ve token takas rotalarını açıklayan bir SwapInfo[ ] dizisini içeren VaultAuth yetkilendirme yapısını taşıması gerekir. Bir takas gerçekleştirilirken sözleşme GiddyLibraryV3.executeSwap() fonksiyonunu çağırır; bu fonksiyon swap.fromToken üzerinde forceApprove uygulayarak swap.aggregator'a izin verir ve ardından takası aggregator.call(swap.data) aracılığıyla yürütür. Strateji sözleşmesi daha sonra yapılandırılmış stratejisine göre fonları yönetir.

EIP-712, zincir dışı yapılandırılmış verileri imzalamak için bir standarttır: imzayı kullanan protokol aynı yapıyı zincir üzerinde yeniden oluşturur, kararlaştırılmış bir alan ayırıcısı altında hash'ler ve imzalayanın adresini kurtarır. Bu nedenle herhangi bir EIP-712 akışının güvenliği, zincir üzerindeki hash'in yürütmeyi etkileyen her alanı kapsamasına bağlıdır. Giddy'nin tasarımında arka uç, hem kullanıcının niyetini hem de gerekli takaslara ilişkin yönlendirme talimatlarını içeren bir VaultAuth imzalar; _validateAuthorization() ise stratejinin fon hareket ettirmesine izin vermeden önce imzayı doğrulamak için bu yapıyı yeniden oluşturur.

Güvenlik Açığı Analizi

Güvenlik açığı, GiddyVaultV3'ün _validateAuthorization() fonksiyonunda yer almaktadır. İmzalanan yük oluşturulurken her SwapInfo'nun yalnızca data alanı hash'lenmekte; aggregator, fromToken, toToken ve amount imzanın dışında bırakılmaktadır. Bu durum, geçerli bir imzaya sahip olan herhangi birinin imza doğrulamasını geçmeye devam ederken SwapInfo'nun kalan alanlarını serbestçe değiştirebileceği anlamına gelmektedir.

Dışarıda bırakılan her alan, imzanın serbest bıraktığı ayrı bir kaldıraçtır: aggregator, forceApprove ve aggregator.call(swap.data) aracılığıyla hem harcamacı hem de çağrı hedefi haline gelir; fromToken hangi strateji varlığının onaylanacağını belirler; amount izin üst sınırını ayarlar; toToken yalnızca returnAmount > 0 kontrolüne veri sağlar. İmzalanan data, bunların hiçbirini kısıtlamaz çünkü bu hedeflerin hiçbirine içeride atıfta bulunulmaz.

Saldırı Analizi

Aşağıdaki analiz, 0x5edb66...5482e5 işlemine dayanmaktadır.

  • Adım 1: Saldırgan, data alanını olduğu gibi koruyarak zincir üzerinden meşru olarak arka uç tarafından yetkilendirilmiş bir VaultAuth imzası elde etti. Her önceki deposit() veya withdraw() çağrısı tam VaultAuth yükünü zincir üzerinde yayımladığından, herhangi bir geçmiş işlem yeniden kullanılabilir bir imzanın ücretsiz kaynağıydı; saldırganın yalnızca data alanı amaçlanan takas çağrısı için uygun olan bir imzaya ihtiyacı vardı.

  • Adım 2: Elde edilen imzayı kullanarak saldırgan, orijinal signature, nonce ve data'yı değiştirmeden tutarken kalan alanlarla oynadı. fromToken, strateji sözleşmesi tarafından tutulan LP Token'a (gerçek bir varlık) ayarlandı, böylece forceApprove protokolün gerçekte tuttuğu bir token üzerinde izin verdi. aggregator, saldırganın kötü amaçlı sözleşmesiyle değiştirildi; böylece hem onay hem de ardından gelen aggregator.call() saldırgana ait koda yönlendirildi. Bu alanlar imza doğrulama kapsamının dışında olduğundan, _validateAuthorization() değiştirilmiş yapıyı olduğu gibi kabul etti. Son require(returnAmount > 0, "SWAP_NO_TOKENS_RECEIVED") kontrolünü atlatmak için kötü amaçlı aggregator, protokole sahte token'lar geri basan ve gerçek bir takas gerçekleştirmeksizin kontrolü karşılayan bir mint fonksiyonu uyguladı.

  • Adım 3: Kötü amaçlı aggregator 2. adımda onay aldığından, saldırgan vault'un LP token'larını doğrudan kötü amaçlı aggregator'a taşımak için transferFrom çağırdı ve hırsızlığı tamamladı. Bu adım, protokolün korumalı yürütme yolunun tamamen dışında kalıyordu; executeSwap() döndüğünde, izin zaten yazılmış ve çağrı sonrası bakiye kontrolü zaten geçmişti, dolayısıyla protokolün müdahale etmek için başka bir fırsatı kalmamıştı.

Sonuç

Bu saldırının temel nedeni, eksik EIP-712 imza kapsamıydı. Fon akışını doğrudan yöneten SwapInfo'nun temel alanları korumasız bırakılmış; bu durum saldırganın geçerli bir imza sunarken takas rotasını ve aggregator adresini değiştirmesine olanak tanıdı. Harici aggregator'ları entegre eden geliştiriciler şunları yapmalıdır:

  • EIP-712 imzalarının aggregator, fromToken, toToken ve amount dahil olmak üzere yürütme sonuçlarını etkileyen tüm alanları kapsadığından emin olun.

  • Denetlenmemiş harici sözleşmelere yapılan çağrıları önlemek için bir aggregator beyaz listesi uygulayın.

  • toToken'ı, sahte token'ların bakiye kontrollerini atlamasını önlemek amacıyla beklenen temel token'larla sınırlı tutun.

Daha genel bir perspektiften, onay-sonra-çağrı mimarisine sahip herhangi bir yapıda EIP-712, yalnızca kullanıcıya yönelik niyeti değil, ortaya çıkan zincir üstü durumu etkileyen her alanı hash'lemelidir. Arka uç imzasının kullanıcı tarafından sağlanan parametreler ile ayrıcalıklı bir sözleşme eylemi arasındaki tek kapı bekçisi olduğu her durumda, o eyleme akan her parametre (çağrı hedefi, varlık, miktar, alıcı) imzalı yapının içinde yer almalıdır. data'yı çağrının kimliği için bir vekil olarak ele almak kategorik bir hatadır: çağrının kimliği tüm parametrelerinin bir demetidir ve imzanın dışında bırakılan her parametre, tanım gereği, işlemi gönderen kişi tarafından kontrol edilir.

Web3 için En İyi Güvenlik Denetçisi

Lansmandan önce tasarımı, kodu ve iş mantığını doğrulayın


Bu Haftaki Diğer Olaylar


Özel Dengeleyici Sözleşmesi

19 Nisan 2026'da Avalanche üzerindeki bir sAVAX dengeleyici sözleşmesi, bir kullanıcının Aave V3 kredi delegasyonundan yaklaşık 64.000 dolar (~7.000 WAVAX) çekmek amacıyla istismar edildi. Herkese açık bir fonksiyon, kullanıcının delegasyonunu hâlâ elinde bulundururken keyfi bir target.call(data) çalıştırıyordu; bu sayede saldırgan, Aave'nin borrow() fonksiyonunu onBehalfOf parametresi kurban olarak ayarlanmış şekilde çağırabiliyordu. Bir beyaz şapkalı bot, saldırıyı öne geçerek gerçekleştirdi ve fonları geri kurtardı.

Arka Plan

Dengeleyici sözleşmesi (0x7a7b...a8c9), Aave üzerinde bir kullanıcının kaldıraçlı pozisyonunu yeniden dengelemek amacıyla tasarlanmış b2a13230() fonksiyonunu açığa çıkarmaktadır. Fonksiyon, kullanıcı adına Aave V3 kredi delegasyonu aracılığıyla çalışır: kullanıcı dengeleyiciye kendi adına borçlanma yetkisi verir ve dengeleyici bu borçlanmaları kullanıcı tarafından sağlanan fonlarla birleştirerek pozisyonu ayarlar (örneğin borçlan + temin et iş akışı).

Güvenlik Açığı Analizi

Temel neden, b2a13230() fonksiyonunun hem hedefi hem de çağrı verisinin arayanın kontrolünde olduğu bir target.call(data) adımı içermesidir. Bu çağrı, sözleşme kullanıcının Aave V3 kredi delegasyonu altında çalışırken gerçekleşir; dolayısıyla bu adım sırasında çağrılan herhangi bir mantık, kullanıcının borçlanma gücünü devralır. İzin verilen hedeflerin bir beyaz listesi ve çağrı verisi üzerinde herhangi bir şekil kısıtlaması yoktur; bu nedenle çağrı, onBehalfOf parametresi kullanıcıya ayarlanmış Aave'nin borrow() fonksiyonu dahil olmak üzere herhangi bir sözleşme yöntemini çağırabilir.

Saldırı Analizi

Aşağıdaki analiz şu işleme dayanmaktadır: 0xaaa1b2...35001b.

  • Adım 1: Saldırgan, belirli miktarda sAVAX ve USDC için flaş kredi kullandı. Ardından borçlanılan USDC'yi, yeterli teminat oluşturmak amacıyla dengeleyici sözleşme aracılığıyla Aave V3'e yatırdı. Bu sırada borçlanılan sAVAX, borçlanma sonrası temin adımına hazırlık olarak doğrudan dengeleyici sözleşmeye aktarıldı.

  • Adım 2: Saldırgan b2a13230() fonksiyonunu çağırdı. Fonksiyon önce normal bir borçlanma işlemi gerçekleştirdi, ardından keyfi çağrı bölümüne ulaştı. Bu noktada saldırgan, onBehalfOf parametresi kurban adresine ayarlanmış şekilde Aave V3'ün borrow() fonksiyonunu doğrudan çağırmak için çağrıyı özellikle hazırladı. Kurban dengeleyici sözleşmeye kredi delegasyonu verdiğinden borçlanma başarılı oldu ve borçlanılan WAVAX dengeleyici sözleşmeye aktarıldı.

  • Adım 3: Saldırgan b2a13230() fonksiyonunu bu kez dengeleyiciyi kullanarak kendi adına WAVAX borçlanmak amacıyla tekrar çağırdı. Sözleşme daha önce borçlanılan WAVAX'ı (kurbanın pozisyonundan kaynaklanan) saldırganın pozisyonuna temin etmek ve geri ödemek için kullandı; bu sayede saldırgan kâr elde edebildi.

Sonuç

Kusur, delegasyon yetkisi taşıyan ayrıcalıklı bir bağlam içindeki keyfi bir harici çağrı ile bu bağlamın birleşiminden kaynaklanmaktadır. Her iki katman tek başına güvenli olurdu: kısıtlı bir harici çağrı delegasyonu kötüye kullanamaz; delegasyonsuz keyfi bir çağrı ise kullanıcının fonlarını taşıyamaz. Kredi delegasyonu tutan sözleşmeler asla keyfi bir harici çağrı barındırmamalıdır; böyle çağrılar zorunluysa hedefler bir beyaz listeye sabitlenmeli ve çağrı verisi şekil kontrolünden geçirilmelidir.


REVLoans (Juicebox)

20 Nisan 2026'da Juicebox üzerinde bir borçlanma eklentisi olan REVLoans, Ethereum'da yaklaşık 50.700 dolar zarara uğratıldı. borrowFrom(), bir arayanın sağladığı muhasebe kaynağını protokole kayıtlı olup olmadığını doğrulamadan kabul ediyordu; 36 ondalıklı sahte bir bağlam, aynı para birimi kısayolunu tetikleyerek bakiyeleri 1e18 oranında hatalı ölçeklendirdi. Biri şişirilmiş muhasebe girişini tohumlamak, diğeri meşru havuza karşı şişirilmiş hisse fiyatından borçlanmak olmak üzere iki işlemde 21,77 ETH boşaltıldı.

Arka Plan

Juicebox, Ethereum üzerinde hibrit bir bağış toplama ve borç verme protokolüdür. Her projenin kendi ERC20 hisse token'ı (burada REV olarak anılmaktadır) ve bir veya daha fazla terminal genelinde bölünmüş bir hazinesi vardır; terminal, projenin varlıklarının bir alt kümesini fiziksel olarak saklayan ve kullanıcıya yönelik giriş/çıkış noktası işlevi gören sözleşmedir. Bir projenin JBDirectory'de kayıtlı birden fazla terminali olabilir ve her (terminal, proje, token) üçlüsü, o terminal içinde söz konusu token'ın muhasebesi için kullanılan (ondalık, para birimi) değerlerini bildiren bir JBAccountingContext taşır. REV bu nedenle tek bir terminale karşı değil, projenin tüm terminallerindeki fazlalar topluluğuna karşı bir taleptir.

Bir kullanıcı yeni basılmış REV almak için bir terminale varlık yatırabilir ya da REV'i bir terminalde fazlasının orantılı payı karşılığında kullanabilir (kalan sahipler için bir miktar değeri geride bırakan yapılandırılabilir bir nakit çıkış vergisi hariç). REVLoans (0x2db6...1846), üzerine kurulu ayrı bir sözleşme olup bir borçlanma kolaylığı ekler: kullanıcı teminat olarak REV yakar ve projenin terminallerinden birine karşı kredi alır; kredi daha sonra teminatın yeniden basılması karşılığında geri ödenebilir. Borçlanma tutarı, tam olarak bir kullanma işlemiyle aynı matematikle fiyatlandırılır; dolayısıyla bir borçlanma ekonomik olarak aynı teminatın kullanılmasıyla eşdeğerdir.

REV'in hisse fiyatı (toplamFazla + toplamBorçlanan) / (REV.toplamArz + toplamTeminat) formülüyle hesaplanır. toplamBorçlanan'ın paydaya dahil edilmesi borçlanma/geri ödemeyi fiyat nötr tutar; aynı zamanda şişirilmiş bir toplamBorçlanan'ın doğrudan hisse fiyatını yükselttiği ve küçük bir teminatın orantısız biçimde nakit çıkışı yapmasına olanak tanıdığı anlamına gelir.

Güvenlik Açığı Analizi

Temel neden, source parametresinde doğrulanmamış giriştir. borrowFrom(), .terminal ve .token alanlarına sahip arayanın sağladığı bir REVLoanSource source yapısını, bu çiftin verilen revnetId için kayıtlı olup olmadığını kontrol etmeksizin kabul eder. Her iki alan da nakit çıkış matematiğine doğrudan akar; bu nedenle source.terminal tarafından döndürülen muhasebe bağlamı tamamen arayanın kontrolündedir. Söz konusu bağlamın currency alanı hedef terminalin alanıyla eşleştiğinde protokol bir aynı para birimi kısayolu alır, fiyat oracle'ını atlar ve sağlanan ondalık ve bakiye verilerini yetkili olarak kabul eder.

Doğrulanmamış source ardından _addTo() tarafından _loanSourcesOf[revnetId] ve totalBorrowedFrom[revnetId][source.terminal][source.token]'a yazılır; bu fonksiyon da kayıt kontrolü gerçekleştirmez.

(source, revnetId) muhasebeye girdikten sonra, _borrowableAmountFrom() bir borçlanma talebini ödenebilir bir tutara çeviren fonksiyondur. _totalBorrowedFrom() üzerinden surplus = toplamFazla + toplamBorçlanan'ı oluşturur, ardından bu fazlayı arayanın teminat sayısı ve hisse arzıyla birlikte JBCashOuts.cashOutFrom() fonksiyonuna iletir.

Ondalık hatası bir katman daha derinde, _totalBorrowedFrom() içinde yaşanır. _loanSourcesOf üzerinde yineleme yapar ve her girişi mulDiv(ödünçVerilen Token'lar, 10**ondalık, birimBaşıFiyat) aracılığıyla katar. Aynı para birimi yolunda birimBaşıFiyat = 10**ondalık (hedefin 18 ondalıklı hassasiyeti), dolayısıyla formül ödünçVerilen Token'lar'a indirgenir ve 36 ondalıklı muhasebe altında saklanan bir bakiye 18 ondalıklı ETH toplamına 1e18 kat büyük olarak düşer.

Büyütme cashOutFrom() içinde gerçekleşir. taban = mulDiv(fazla, nakit_çıkış_sayısı, toplamArz): fazla, şişirilmiş toplamBorçlanan tarafından domine edildiğinde, çok küçük bir nakit_çıkış_sayısı (teminat) bile orantısız biçimde büyük bir ödemeyle eşleşir.

Saldırı Analizi

Saldırı iki işlem kullanır. Birincisi REVLoans'ın muhasebesini kirletir: 0xc46cb7...dead1f. İkincisi meşru bir terminal karşısında havuzu boşaltır: 0x9adbd6...a8f938.

  • Adım 1: Saldırgan, kredi kaynağındaki hem terminal hem de token'ı sahte bir sözleşmeye yönlendirerek küçük miktarda REV teminat koyarak borrowFrom()'u çağırdı. REVLoans, sağlanan terminalin revnet için kayıtlı olup olmadığını ya da token'ın terminal tarafından tanınıp tanınmadığını kontrol etmedi.
  • Adım 2: REVLoans, muhasebe bağlamı için sahte terminali sorguladı; terminal (ondalık=36, para birimi=ETH-kodu(61166)) sahte bir yanıt döndürdü. Kaynak ve hedef para birimleri eşleştiğinden REVLoans aynı para birimi kısayolunu aldı ve fiyat oracle'ını atladı, ardından meşru terminallerin gerçek ETH fazlalarını saldırganın 36 ondalıklı hedef birimiyle yeniden ifade ederek nakit çıkış matematiğini çalıştırdı; bu da rakamı 1e18 oranında şişirdi.
  • Adım 3: REVLoans, (sahte terminal, sahte token) çiftini _loanSourcesOf'a kaydetti ve şişirilmiş rakamı totalBorrowedFrom'a yazdı. Sahte terminal yalnızca alındığını doğrulayarak "ödeme yaptı"; gerçek ETH hareket etmedi. İlk işlem, toplamBorçlanan yukarı manipüle edilmiş ve yalnızca küçük REV teminatı yakılmış halde sona erdi.
  • Adım 4: Saldırgan borrowFrom()'u bu kez kredi kaynağı olarak meşru ETH terminalini ve küçük miktarda REV teminatı geçirerek tekrar çağırdı. Nakit çıkış matematiği gerçek 18 ondalıklı ETH birimleriyle çalıştı.
  • Adım 5: toplamBorçlanan'ı hesaplarken REVLoans _loanSourcesOf üzerinde yineledi ve 3. adımdaki girişe çarptı. Bu girişin para birimi hâlâ ETH ile eşleştiğinden aynı para birimi kısayolu yeniden tetiklendi ve 36 ondalıklı saklanan bakiye 18 ondalıklı ETH toplamına 1e18 kat büyük olarak katlandı. toplamBorçlanan artık sahte borç tarafından domine ediliyor ve hisse fiyatı paydası muazzam biçimde şişirilmiş durumdaydı.
  • Adım 6: Nakit çıkış matematiği, saldırganın meşru terminalin gerçek fazlasının hemen altında kalacak şekilde önceden ayarladığı şişirilmiş paydaya boyutlandırılmış bir borçlanma tutarı döndürdü. Meşru terminal bunu ödedi ve neredeyse havuzun tamamı saldırgana boşaltıldı.

Sonuç

Temel neden, iki katmanlı boşluktur: (terminal, token) çiftleri revnet kaydı kontrol edilmeksizin kabul edilmekte ve aynı para birimi kısayolu, kaynak bakiyelerini ondalık farklılıkları için yeniden normalleştirmeksizin hedef toplamına katlamaktadır. Her bir boşluk tek başına daha az tehlikeli olurdu; birlikte bir arayanın keyfi bir totalBorrowedFrom girişi enjekte etmesine ve bunu nominal değerden kullanmasına olanak tanırlar. Hafifletme yöntemi: (terminal, token) çiftini revnet'in kayıtlı terminalleri karşısında doğrulayın ve katlamadan önce bakiyeleri kaynağın saklanan ondalık ölçeğiyle yeniden normalleştirin.


Volo Vault

22 Nisan 2026'da, kullanıcı mevduatlarını Navi borç verme protokolüne yönlendirerek borç verme getirisi elde eden Sui üzerindeki bir getiri vault'u olan Volo, operatörün özel anahtarının sızdırılmasının ardından yaklaşık 3,5 milyon dolar kaybetti. Vault sözleşmesinde kod düzeyinde herhangi bir hata yoktu; saldırgan yalnızca çalınan kimlik bilgileriyle meşru operatör yolunu çalıştırarak Volo'nun Navi mevduatlarını boşalttı.

Arka Plan

Volo, kullanıcıya yönelik vault'tur (0xcd86...27fefa); Navi ise temel borç verme protokolüdür. Vault, Volo'nun Navi hesabından para çekmeyi yetkilendiren bir Navi AccountCap (Sui yetenek nesnesi) tutar ve strateji hamlelerini bir operatör rolüne devreder. Navi'ye para yatırmak veya çekmek için operatör, AccountCap'i vault'tan geçici bir çantaya kaldırmak amacıyla start_op_with_bag_v2() fonksiyonunu çağırır; ardından deposit_with_account_cap() / withdraw_with_account_cap_v2() fonksiyonları fonları taşımak için bu yetkiyi kullanır.

Güvenlik Açığı Analizi

Temel neden, sözleşme düzeyinde bir güvenlik açığından ziyade operasyonel/anahtar saklama hatasıdır. Volo strateji yolu, para çekme yetkisini operatör özel anahtarını elinde bulunduran kişiye devreder: start_op_with_bag_v2() yalnızca iki kontrol gerçekleştirir (assert_operator_not_freezed(operation, cap) ve assert_single_vault_operator_paired(operation, vault.vault_id(), cap)); her ikisi de yalnızca sağlanan yetkinin kayıtlı operatör olup olmadığını doğrular. withdraw_with_account_cap_v2() ise kaldırılmış AccountCap'i sunabilen herhangi bir arayanı kabul eder. Bu nedenle operatör özel anahtarına sahip olan herkes, meşru operasyonların kullandığı yolun aynısını, ayırt edilemez biçimde çalıştırabilir.

Saldırı Analizi

Aşağıdaki analiz, AQw9wM...3RUS işlemine dayanmaktadır.

  • Adım 1: Saldırgan, sızdırılan operatör anahtarıyla @volosui/volo-vault::operation üzerinde start_op_with_bag_v2'yi çağırarak Navi AccountCap'ini geçici bir çantaya taşıdı.
  • Adım 2: Saldırgan, AccountCap'i geçici çantadan çıkarmak için bag::remove kullandı.

  • Adım 3: Saldırgan, çıkarılan AccountCap ile @navi-protocol/lending::incentive_v3 üzerinde withdraw_with_account_cap_v2 fonksiyonunu çağırarak Volo'nun mevduatlarını Navi'den çekti.

  • Adım 4: Saldırgan, AccountCap'i geri koymak için bag::add'i kullandı, işlemi kapattı ve fonları dışarı aktardı.

Sonuç

Kusur yapısaldır: tek operatör anahtarı, tam para çekme yetkisi, ikinci bir kontrol yok. Üç değişiklik, anahtar ele geçirilmesinden kaynaklanan hasarı azaltır. Operatör rolünü çok imzalı veya eşik tabanlı bir şemaya bölmek, sızdırılan bir anahtarın tek başına bir para çekme işlemini yetkilendirememesi anlamına gelir. Giden para çekilmelerine zaman kilidi eklemek, anormal çağrılara kapatmadan önce itiraz edilebilir bir pencere sunar. Operatörün yetkilerini yalnızca yatırma ve yeniden dengeleme işlemleriyle sınırlandırmak ve kullanıcıya yönelik para çekilmelerini ayrı bir yoldan yönlendirmek, operatör rolünün kullanıcı fonlarına ulaşmasını engeller.


Kipseli Router

22 Nisan 2026'da Base üzerindeki Kipseli Router, yaklaşık 72.350 dolar zarara uğratıldı. Router, harici bir USDC'ye özgü alıntılayıcı tarafından döndürülen bir teklifi, çıktı token'ının alıntı token'ına eşit olup olmadığını doğrulamaksızın ham çıktı token transferi tutarı olarak kullanır. Bir saldırgan, alıntılayıcının gerçekte desteklemediği bir yol üzerinden 0,04 WETH ile cbBTC takas etti ve alıntılayıcının USDC'ye ölçeklendirilmiş dönüş değerini (92.610.395) ham cbBTC birimi olarak aldı (≈0,926 cbBTC).

Arka Plan

Kipseli Router (0x579f...9a07), harici bir alıntılama sistemiyle desteklenen bir takas yürütme sözleşmesidir. Sözleşmenin açık kaynak kodu mevcut değildir; aşağıdaki analiz derlenmiş bayt koduna dayanmaktadır; bu nedenle fonksiyon adları 4 baytlık seçiciler olarak görünür (0xcce096f3(), 0x592(), 0xd88()). Fiyatları doğrudan zincir üzerindeki AMM havuzlarından hesaplamak yerine bir çıktı tutarı için alıntılayıcıyı sorgular (amountOut) ve ardından token transferini bu değere göre gerçekleştirir. Normal işleyişte kullanıcı tokenIn'i protokol cüzdanına gönderir; router ise aynı cüzdandan tokenOut'u çeker ve alıcıya iletir. Protokol tek bir QUOTE_TOKEN ile yapılandırılmıştır ve alıntılama mantığı 6 ondalıklı muhasebe kullanılarak USDC cinsinden belirlenir; sistem yalnızca USDC cinsinden alıntıları destekleyecek şekilde tasarlanmıştır.

Güvenlik Açığı Analizi

Kusur, alıntılayıcı çağrısının her iki tarafında da denetlenmeyen iki varsayımdan oluşur ve bu varsayımlar birbirini güçlendirir. Router tarafında, 0xcce096f3() fonksiyonu alıntılayıcı fonksiyonu 0x592() aracılığıyla v0 alıntısını alır ve bunu değiştirmeksizin 0xd88() fonksiyonuna tokenOut.transferFrom(_wallet, receiver, v0) olarak iletir. Router, tokenOut'un protokolün QUOTE_TOKEN'ına eşit olup olmadığını hiçbir zaman kontrol etmez; bu nedenle USDC'ye ölçeklendirilmiş bir değer (6 ondalıklı hassasiyet), cbBTC miktarıymış gibi (8 ondalıklı hassasiyet) transfer edilir. Alıntılayıcı tarafında ise temel PropAMM AMM yalnızca token-USDC çiftleri için tasarlanmıştır ancak desteklenmeyen yönlendirme yollarını (WETHcbBTC) geri döndürmeksizin kabul eder; tokenIn'i sessizce yok sayar ve takasın geçerli olduğunu varsayarak USDC'ye ölçeklendirilmiş bir değer döndürür.

Saldırı Analizi

Aşağıdaki analiz, 0x96edee...3db3bb işlemine dayanmaktadır.

  • Adım 1: Saldırgan, router'ı tokenIn=WETH ve tokenOut=cbBTC ile çağırdı. Temel AMM bu yolu desteklemiyordu ancak geri dönmedi ve alıntılayıcı 0x592(), USDC'ye ölçeklendirilmiş 92.610.395 değerini (≈92,61 USDC) döndürdü.
  • Adım 2: Router bu değeri doğrudan cbBTC transfer tutarı olarak kullandı. transferFrom aracılığıyla 0,04 WETH (≈$95) içeri aktı; protokol cüzdanından saldırgana 92.610.395 ham cbBTC birimi (≈0,926 cbBTC, ≈$72.350) dışarı aktı.

Sonuç

İstismar, alıntılayıcı çağrısının her iki tarafında da iki varsayımın denetlenmemesi nedeniyle gerçekleşir. Alıntılayıcı, çıktısının kendi USDC 6 ondalıklı çerçevesinde tüketildiğini varsayar; router ise alıntılayıcının döndürdüğü değerin talep edilen tokenOut cinsinden ifade edildiğini varsayar. Her iki düzeltme de hatayı giderir:

  • Router tarafında: tokenOut == QUOTE_TOKEN olduğunu doğrulayın ya da transferden önce USDC'ye ölçeklendirilmiş teklifi bir oracle aracılığıyla tokenOut birimine dönüştürün.

  • Alıntılayıcı tarafında: token'ları desteklenen çift kümesi için kayıtlı olmayan yönlendirme yollarında USDC'ye ölçeklendirilmiş bir geri dönüş değeri döndürmek yerine geri dönün.


Purrlend

25 Nisan 2026'da HyperLiquid ve MegaETH üzerinde faaliyet gösteren bir borç verme protokolü olan Purrlend, özel anahtar ele geçirilmesinin ardından yaklaşık 1,5 milyon dolar kaybetti. Saldırgan köprü rolünü devraldı ve desteksiz pToken'lar (Purrlend'in Aave benzeri makbuz token'ları) basti; ardından bu pToken'ları teminat olarak kullanarak havuzdan gerçek varlıklar borçlandı.

Arka Plan

Purrlend (0x81d5...a702), Aave benzeri bir muhasebe modeline sahip bir borç verme protokolüdür. Kullanıcılar protokole varlık yatırdıklarında, sağlanan pozisyonlarını temsil eden ve diğer varlıkları borçlanmak için teminat olarak kullanılabilen, Aave'nin aToken'larına benzer ilgili pToken'ları alırlar.

Protokol ayrıca pool admin, risk admin ve bridge dahil ayrıcalıklı roller içermektedir. Köprü rolü zincirler arası muhasebeye yöneliktir: karşı zincirde gerçekleşen mevduatları yansıtmak için pToken basabilir. Diğer yönetici rolleri ise risk parametrelerini değiştirir ve borçlanılabilir varlıkları yapılandırır.

Güvenlik Açığı Analizi

Doğrudan tetikleyici, ayrıcalıklı anahtar ele geçirilmesiydi: saldırgan, Purrlend'in yönetici ve köprü rollerini kontrol eden anahtarları elde etti. Sözleşme düzeyindeki bir tasarım hatası sızıntıyı büyüttü: bridge rolünün pToken basma yolu, doğrulanabilir herhangi bir zincirler arası emanet kanıtına bağlı değildir. Fonksiyon, köprü rolüne sahip bir arayanın herhangi bir adrese, herhangi bir miktarda pToken basmasına izin verir; kaynak zincirde karşılık gelen bir mevduatın gerçekleşip gerçekleşmediğini kontrol etmez. Protokolün başka her yerinde pToken'lar geçerli teminat olarak kabul edilir ve borçlanma yolu basım sırasında desteklenmeyi yeniden kontrol etmez. Dolayısıyla yetkisiz bir köprü rolü basımı, basım ile varlık çekimi arasında ikinci bir kapı olmaksızın doğrudan borçlanma gücüne dönüşür.

Saldırı Analizi

Aşağıdaki analiz, MegaETH üzerindeki 0xb96cff...dbbf24 işlemine dayanmaktadır.

  • Adım 1: Ele geçirilmiş ayrıcalıklı anahtarları elinde bulunduran saldırgan, GnosisSafeProxy aracılığıyla MultiSendCallOnly toplu işlemini kullanarak ACLManager üzerinden kendini pool admin, risk admin, bridge ve emergency admin olarak atadı; ardından WETH'i borçlanılabilir varlık olarak etkinleştirdi ve BorrowCap değerini 200 olarak ayarladı.
  • Adım 2: Köprü rolünü üstlenerek saldırgan, kendi adresine büyük miktarda pToken basti. Köprü basma yolu zincirler arası emanet doğrulaması gerçekleştirmediğinden yeni pToken'ların herhangi bir temel varlık desteği yoktu.

  • Adım 3: Saldırgan, desteksiz pToken'ları teminat olarak kullandı. Borçlanma yolu, desteklenmeyi yeniden kontrol etmeksizin her türlü pToken bakiyesini geçerli bir temin pozisyonu olarak kabul ettiğinden, teminat kontrolü geçti ve havuzdan WETH borçlandı.

Sonuç

Bu, sözleşme düzeyindeki bir tasarım hatasıyla büyütülmüş bir özel anahtar ele geçirme olayıydı. Sızdırılan anahtarlar saldırgana yalnızca köprü rolünün amaçlanan yetkisini verdi; ancak bu yetki kısıtsız pToken basımını içeriyordu ve bu da doğrudan borçlanılabilir teminata dönüşüyordu. Her katman bağımsız olarak sıkılaştırılabilir. Operasyonel katmanda köprü rolünü çok imzalı veya eşik tabanlı bir şemaya bölün; böylece tek bir anahtar sızıntısı onu kullanamaz. Sözleşme katmanında ise köprü basımının doğrulanabilir bir emanet kanıtı (örneğin güvenilir bir zincirler arası doğrulayıcıdan bir mesaj taahhüdü) taşımasını zorunlu kılın ve kanıt sağlanmadığında geri dönün. Kanıtı basım sırasında doğrulamak, anahtar saklamaya olan bağımlılığı tamamen ortadan kaldırdığı için daha kalıcı bir düzeltmedir.


SingularityFinance

26 Nisan 2026'da Base üzerinde SingularityFinance'ın dynBaseUSDCv3 vault'u yaklaşık 413.000 dolar kaybetti. Vault, geçersiz bir Uniswap V3 ücret kademesiyle (V3'te mevcut olmayan 42) yapılandırılmıştı; bu nedenle USDC dışındaki her varlığın fiyat oracle'ı var olmayan bir havuzu çözümlüyordu. Fiyatlandırma fonksiyonu geri dönmek yerine sessizce 0 döndürdü, vault USDC dışındaki rezervlerini sıfır olarak değerlendirdi ve bir saldırgan küçük miktarda USDC yatırarak neredeyse tüm hisse arzını basti; ardından gerçek temel varlıklar karşılığında kullandı.

Arka Plan

dynBaseUSDCv3 vault'u (0x67b9...4dcd), birden fazla getiri taşıyan token tutar ve USDC dışındaki rezervleri Uniswap V3 aracılığıyla fiyatlandırır. getPrice(baz, ücret, alıntı, miktar) yardımcısı, (baz, alıntı, ücret) demetini fabrika aracılığıyla bir Uniswap V3 havuzuna çözümler ve ardından bu havuzdan TWAP'ı okur. Vault'un totalAssets() fonksiyonu fiyatlandırılmış rezervleri toplar; hisse basımı ve kullanım oranları bu toplamdan türetilir.

Güvenlik Açığı Analizi

Kusur, getPrice() fonksiyonunun erken dönüş dalında yer almaktadır. IUniswapV3Factory.getPool(baz, alıntı, ücret) address(0) döndürdüğünde (sağlanan ücret kademesi için havuz mevcut değil), fonksiyon geri dönmek yerine sıfır ile başlatılmış price değişkenini döndürür. Vault, Uniswap V3'ün desteklenen kademelerinden (500/3000/10000) hiçbiri olmayan ücret=42 ile dağıtıldı; bu nedenle USDC dışındaki her token araması bu dala düşer. totalAssets() bu nedenle yalnızca vault'un USDC bakiyesine yakın bir değere ulaşırken gerçek getiri taşıyan token'lar sıfıra katkıda bulunur. totalAssets()'e bağlı basım ve kullanım oranları bu neredeyse sıfır olan payda üzerinden hesaplanır.

Saldırı Analizi

Aşağıdaki analiz, 0x00b949...8d3732 işlemine dayanmaktadır.

  • Adım 1: Saldırgan yaklaşık 100.000 USDC için flaş kredi kullandı.

  • Adım 2: Saldırgan USDC'yi vault'a yatırdı. totalAssets() yalnızca USDC bakiyesini saydığından vault kendini yaklaşık mevduat tutarı kadar değerlendirdi ve saldırgan hisse arzının neredeyse %100'ünü aldı.

  • Adım 3: Saldırgan hisseleri kullandı; bu işlem temel rezervleri hisse sahipliğiyle orantılı olarak dağıtır. Saldırgan, vault'un tuttuğu her getiri taşıyan token'ın büyük bir bölümünü aldı.

  • Adım 4: Saldırgan flaş krediyi geri ödedi ve boşaltılan getiri taşıyan token'ları kâr olarak sakladı.

Sonuç

İki kontrol eksikti. Dağıtım, ücret=42'yi Uniswap V3'ün desteklenen kademeleri (500/3000/10000) karşısında doğrulamadı; getPrice() ise eksik bir havuzda geri dönmek yerine 0 döndürdü. Her iki düzeltme de yeterlidir: yapılandırma sırasında oracle parametrelerini doğrulayın ya da getPool() == address(0) durumunda geri dönün. Derinlemesine savunma olarak hisse basımı mantığı, mevduatları kabul etmeden önce totalAssets()'i harici bir referans karşısında makul olup olmadığını kontrol etmelidir.


Scallop

26 Nisan 2026'da Sui üzerinde Scallop'ın staking-ödüller programı yaklaşık 142.700 dolar kaybetti. Kullanıcının tahakkuk eden ödüllerini güncelleyen fonksiyon, iletilen ödül takip nesnesinin kullanıcının hesabıyla eşleşip eşleşmediğini doğrulamıyordu; bu durum bir saldırganın terk edilmiş, uzun süredir hareketsiz bir ödül takip nesnesinden hayali bir puan bakiyesi çekmesine ve meşru ödül havuzuna karşı bakiye boşalana kadar kullanmasına olanak tanıdı.

Arka Plan

Scallop, Sui üzerinde bir borç verme protokolüdür. Borç verme ürününün üzerine Scallop bir spool programı yürütmektedir: kullanıcılar MarketCoin<T> almak için Scallop'ın piyasasına tek bir varlık yatırır (borç verme makbuzu; SUI mevduatları için bu MarketCoin<SUI>, yani "sSUI"nin zincir üstündeki temsilidir), ardından zaman içinde protokol puanı kazanmak için bu MarketCoin'i bir Spool'a stake ederler ve daha sonra bu puanları gerçek ödül token'ları karşılığında eşleştirilmiş bir RewardsPool'a karşı kullanırlar. Her Spool, hisse başına global bir index izleyen bir Sui paylaşımlı nesnesidir; her kullanıcı stake edilmiş bakiyesini ve tahakkuk eden points'ini kaydeden kişisel bir SpoolAccount tutar.

Güvenlik Açığı Analizi

Kusur spool::user::update_points içindedir: fonksiyon account.spool_id == object::id(spool) (ya da account.stake_type == spool.stake_type) doğrulamasını gerçekleştirmez. Kardeş girişler stake, unstake ve redeem_rewards girişte bu bağlama kontrolünü yapar; yalnızca update_points atlar. Kontrol olmaksızın spool_account::accrue_points, account.points += stake * (spool.index − account.index) / 1e9 hesaplamasını herhangi bir Spool iletilerek gerçekleştirir; bu Spool'un index'ini bu hesabın kendi ödül akışıymış gibi değerlendirir.

Yol istismar edilebilir hale gelir çünkü Sui paylaşımlı nesneleri asla çöp toplamaz: stakes değeri tozuna dönüşmüş terk edilmiş bir Scallop Spool'u ödül payı biriktirmeye devam eder (dönem başına 1e9 * ödül / stakes artışı), dolayısıyla index'i kümülatif olarak artar ve keyfi büyük değerlere ulaşabilir. Bağlama kontrolü eksik olduğunda update_points, bu şişirilmiş index'i kullanarak herhangi bir hesaba büyük bir puan deltası yazabilir. Kirletilmiş points daha sonra hedef spool'un RewardsPool'una karşı 1:1 oranında kullanılır; zira hesap meşru olarak o hedef spool'a bağlıdır ve redeem_rewards'ın kendi bağlama kontrolü geçer.

Saldırı Analizi

Aşağıdaki analiz, 6WNDjC...NfVL işlemine dayanmaktadır.

  • Adım 1: Yem olarak 0,2 SUI ile saldırgan MarketCoin<SUI> basti, ardından new_spool_account + stake'i hedef spool'a karşı çağırarak account.spool_id = hedef_spool ile meşru olarak bağlanmış bir SpoolAccount oluşturdu.

  • Adım 2: Saldırgan update_points<MarketCoin<SUI>>(donör_spool, account, clock) fonksiyonunu donor_spool terk edilmiş bir Spool'a ayarlanmış şekilde çağırdı. Donörün index'i (≈8.91e14) hesaba points olarak yazıldı: points = stake * (8.91e14 − 1.19e9) / 1e9 ≈ 1.62e14.

  • Adım 3: Saldırgan redeem_rewards<MarketCoin<SUI>, SUI>(hedef_spool, hedef_rp, account) fonksiyonunu çağırdı. Bağlama doğrulamaları hedef-bağlı hesabı kabul etti, iç yeniden tahakkuk erken döndü ve kirletilmiş points ödül havuzunun 1:1 oranında bakiyesine kadar dönüştürüldü: ödüller = 150.098.061.595.978 ham SUI.

  • Adım 4: Saldırgan 0,2 SUI yemini geri almak için unstake ve redeem'i çağırdı, ardından her şeyi dışarı taşımak için TransferObjects kullandı.

Sonuç

Düzeltme, stake, unstake ve redeem_rewards'ın zaten gerçekleştirdiği assert!(account.spool_id == object::id(spool)) kontrolünü update_points girişine eklemektir. Derinlemesine savunma olarak protokol, tek bir accrue_points çağrısı tarafından kabul edilen index deltasını da sınırlayabilir (yapılandırılmış bir tavanı aşan deltaları reddeder); böylece bağlama kontrolü gelecekte tekrar atlatılsa bile, hiçbir tek çağrı hesaba gerçek stake süresinin orantısız bir points miktarı yazar yapamaz.

Phalcon Security ile Başlayın

Her tehdidi tespit edin, önemli olanları uyarın ve saldırıları engelleyin.

Şimdi ücretsiz deneyin

BlockSec Hakkında

BlockSec, tam yığın blok zinciri güvenliği ve kripto uyumluluk sağlayıcısıdır. Müşterilerin protokollerin ve platformların tam yaşam döngüsü boyunca kod denetimi (akıllı sözleşmeler, blok zinciri ve cüzdanlar dahil), saldırıları gerçek zamanlı olarak engelleme, olayları analiz etme, yasa dışı fonları izleme ve AML/CFT yükümlülüklerini karşılamalarına yardımcı olan ürün ve hizmetler geliştiriyoruz.

BlockSec, prestijli konferanslarda birden fazla blok zinciri güvenliği makalesi yayımlamış, DeFi uygulamalarına yönelik çeşitli sıfır gün saldırıları bildirmiş, 20 milyonun üzerinde doları kurtarmak için birden fazla saldırıyı engellemiş ve milyarlarca dolar değerinde kripto para güvence altına almıştır.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit