Geçen hafta (2026/08/31 - 2026/09/06) boyunca, toplam tahmini kaybı yaklaşık 9,4 milyon dolar olan 4 güvenlik olayı gözlemledik.
| Tarih | Olay | Tür | Tahmini Kayıp |
|---|---|---|---|
| 2026/08/31 | Ankr FLOW | Hatalı Durum Doğrulaması | ~410 Bin $ |
| 2026/08/31 | Aquifer | Hatalı Girdi Doğrulaması | ~2,47 Milyon $ |
| 2026/08/31 | Injective | Eksik Değer Birimi Doğrulaması | ~4,8 Milyon $ |
| 2026/09/03 | Notional Finance | Güvensiz Tür Dönüşümü | ~1,73 Milyon $ |
Web3 için En İyi Güvenlik Denetçisi
Lansmandan önce tasarımı, kodu ve iş mantığını doğrulayın
Haftanın Öne Çıkanı: Injective
Bu olay, saldırı zincirinin karmaşıklığı ve kaybın büyüklüğü nedeniyle haftanın öne çıkanıdır: tek bir ödemenin gerçekleşmesi için iki ayrı kusurun bir araya gelmesi gerekiyordu. Her ikisi de bir uygulama sözleşmesinde değil, zincirin kendi borsa mantığında yer alıyordu. Bu durum, ayraç olmadan birleştirilmiş alanlardan oluşturulan bir tanımlayıcının, hiçbir zaman ilişkili olması amaçlanmamış iki nesneyi nasıl sessizce birleştirebildiğini ve ödeme yolunun, desteklediği piyasanın değer birimini bir fonun taşıyıp taşımadığını hiç kontrol etmemesi durumunda bu birleşmenin ne kadar maliyetli olabileceğini gösteriyor.
2026/08/31 tarihinde, Injective'in exchange modülü içindeki ikili opsiyon mantığı, yaklaşık 4,8 milyon dolarlık USDC için istismar edildi. Injective, bir sipariş defteri borsasını doğrudan zincirin kendisine yerleştiren bir katman-1 zinciridir, dolayısıyla etkilenen kod, birinin dağıttığı bir sözleşme değil, düğüm yazılımının bir parçasıdır. Injective'in yerel token'ı INJ'yi tutan bir sigorta fonu, iki tanımlayıcı çakıştığı için USDC cinsinden kotalanan bir ikili opsiyon piyasasına bağlı hale geldi ve bir sigorta fonundan yararlanan ödeme yolu, ikisini hiçbir zaman karşılaştırmadı. Saldırgan, böyle bir piyasada kendi alt hesapları arasında işlem yaparak bir açık oluşturdu; protokol de bunu, bir sentin küçük bir kesri değerindeki bir INJ bakiyesiyle karşıladı. Her pozisyon tam olarak iade edildi ve saldırgan yatırdığından çok daha fazlasını çekti.
Arka Plan
Injective'in exchange modülü, evet-hayır sonucu üzerine tam olarak teminatlandırılmış bahisler olan ikili opsiyon piyasalarını listeler. Herkes bir listeleme ücreti ödeyerek, sonucu belirleyecek oracle'ı ve piyasanın sona ereceği ve ödemenin yapılacağı zaman damgalarını seçerek bir piyasa listeleyebilir. Oracle, bir sağlayıcı artı bir sembol olarak adlandırılır: sağlayıcı olmak bir yönetişim oylaması gerektirirken, sembol listeleyenin sağladığı herhangi bir dizedir. Bir yatırımcı önce USDC gibi kota token'larını bir alt hesaba yatırır, ardından bir emir vererek taraf seçer. BUY emri olayın gerçekleşeceğine bahis oynar ve teminat olarak P * Q kilitler; SELL emri gerçekleşmeyeceğine bahis oynar ve (1 - P) * Q kilitler; burada Q, kontrat cinsinden miktardır ve P, [0, 1] aralığında giriş fiyatıdır. Bu nedenle daha olası sonuca bahis oynayan taraf daha büyük teminatı yatırır. Zincirin her bloğun sonunda çalıştırdığı kanca olan EndBlocker, aynı fiyattaki bir BUY ve bir SELL'i eşleştirerek bir LONG ve bir SHORT pozisyonu oluşturur. İki kilit her zaman tam olarak Q'ya toplandığından, yeni eşleşen bir defter yapı gereği tam olarak fonlanmıştır.
Vade sonunda oracle, [0, 1] aralığında bir ödeme fiyatı S yayınlar ve her pozisyona piyasa havuzundan margin ± (S - entry) * Q ödenir. Katılımcılar arasındaki ödemeler kesinlikle sıfır toplamlıdır ve her ödeme sıfırda taban değere sahiptir, dolayısıyla bir pozisyon asla negatife düşemez ve tasfiyeye ihtiyaç duymaz. Bir pozisyon ayrıca margin = 0 ile zıt yönde bir emir vererek erken kapatılabilir, bu da teminatını ve gerçekleşen karını, gelen açıcının kilitlediği teminattan ödenmiş olarak serbest bırakır. Her pozisyonun teminatı, girişte kilitlediği değerde kalır, dolayısıyla erken bir kapatmadan sonra defterlerdeki teminatların havuza eşit toplanması gerekmez.
Sembolünü hiçbir sağlayıcının yayınlamadığı bir piyasa, hiç fiyat olmadan ödeme aşamasına ulaşır. Modül bu durum için bir yedek mekanizmaya sahiptir: ödeme, getBinaryOptionsSocializedLossDataWithRefundFlag() tarafından uygulanan bir iade yoluna düşer, bu da piyasayı çözmek yerine tasfiye eder. Her pozisyon, defterin pozisyonların iddia ettiğini hâlâ tuttuğu sürece, kendi giriş fiyatından kapatıldığı için basitçe teminatı iade edilir, böylece hiçbir pozisyon kar veya zarar kaydetmez. İadeler piyasa bakiyesinden ve herhangi bir açık için o piyasayla ilişkili sigorta fonundan finanse edilir.
Bir piyasa ve bir sigorta fonu ayrı nesnelerdir, her biri kendi mesajıyla oluşturulur ve her biri bir değer birimi taşır: bir piyasa bir token cinsinden kotalanır ve bir fon, oluşturulduğu token'ı tutar. Kimlikle eşleştirilirler: bir fon, kimliği kendi kimliğine eşit olan piyasayı destekler. Her iki kimlik de kimlik alanlarının keccak256 özetleridir. exchange modülünün kendisi, piyasa başına ve fon başına bakiyeleri ham tam sayı muhasebesi olan tek bir toplu banka hesabıdır.
Zafiyet Analizi
Hatalı bileşen, injective-core'un exchange modülündeki ikili opsiyon işlemesidir; b994d6b6 [1] taahhüdünde giderilmiştir. Zincirlenmiş iki kusur, bir değer birimini tutan bir fonun başka bir değer birimi cinsinden kotalanan bir piyasayı desteklemesine izin verir.
Kusur 1: tanımlayıcılar, ayraçsız bir birleştirmeden türetilir. Bir piyasa başlatıldığında kimliğini hesaplayan NewBinaryOptionsMarketID() ile yeni bir fonun destekleyeceği piyasanın kimliğini hesaplayan CreateInsuranceFund() (expiry = BinaryOptionsExpiryFlag = -2 için), her ikisi de bu kimliği aynı ifadeden türetir:
return crypto.Keccak256Hash([]byte((BINARY_OPTIONS_MARKET_ID_PREFIX +
oracleType.String() + ticker + quoteDenom + oracleSymbol + oracleProvider)))
Alan ayraçları ve uzunluk önekleri bulunmadığından, alanlar arasındaki sınırlar özetlenen baytlarda hiçbir iz bırakmaz. CreateInsuranceFund(), bir sigorta fonunun kendi alanlarını bu yuvalara eşler; oracle_base, oracleSymbol yuvasını, oracle_quote ise oracleProvider yuvasını işgal eder. Bu nedenle bir fon demeti ile bir piyasa demeti, bu baytları farklı şekilde alanlara bölerken bayt-özdeş ön görüntüler üretebilir ve iki nesne daha sonra bir kimliği paylaşır. Kayıt işlemi, bu paylaşılan kimliği ikisi arasındaki bağlantı olarak kabul eder, dolayısıyla bir fon, tutmadığı quoteDenom'a sahip bir piyasanın sigorta fonu haline gelebilir.
Kusur 2: ödemeler asla piyasanın değer birimine karşı kontrol edilmez. PayDeficitFromInsuranceFund(), fonun tuttuğu değer biriminde fondan ham coin'leri çıkarır, ardından aynı ham tam sayıyı piyasa bakiyesine kredilendirir; bunu yaparken insuranceFund.DepositDenom'u piyasanın kota değer birimiyle hiç karşılaştırmaz. Modülün muhasebesi düz tam sayılar olduğundan, bir ham INJ birimi ile bir ham USDC birimi bu yolda ayırt edilemez; oysa aynı tam sayı, 10^11 mertebesinde bir faktörle farklılık gösteren değerleri temsil eder. Düzeltme taahhüdü, bu yolun eksik olduğu kontrolü hem giriş hem de çıkış tarafına ekler, böylece bir fon yalnızca tuttuğu değer biriminde kotalanan piyasaları destekleyebilir:

Aynı taahhüt ayrıca Injective ana ağında ikili opsiyon işlemlerini ve ödemelerini tamamen devre dışı bırakarak, iade yolunu bir saldırı yüzeyi olmaktan çıkarır.
Saldırı Analizi
Tüm adımlar, Q = 15.930 ile tek bir cüzdan tarafından kendi üç alt hesabı (...037c, ...037d ve ...037e) aracılığıyla gerçekleştirildi.
Çakışan çift, birleştirilmiş baytları aynı tutarken alan sınırlarının nereye düştüğünü kaydırarak oluşturuldu. Fonun ticker ve quoteDenom alanları (X ve inj), piyasanın Xinj ticker'ını yazar ve fonun oracle_base alanı (bir sözleşme adresi ile bir oracle sembolünün birbirine yapıştırılması), piyasanın quoteDenom'unu ve ardından oracleSymbol'unu yazar:
| Birleştirmedeki yuva | Fon (MsgCreateInsuranceFund) |
Piyasa (MsgInstantBinaryOptionsMarketLaunch) |
|---|---|---|
| önek | -BINARY-OPTIONS-MARKET- |
-BINARY-OPTIONS-MARKET- |
oracleType.String() |
Provider |
Provider |
ticker |
X |
Xinj |
quoteDenom |
inj |
erc20:0xa00C...235a |
oracleSymbol |
erc20:0xa00C...235aNO_PRICE_FOR_REFUND...297, her iki parça da tek bir alana paketlenmiş şekilde fonun oracle_base'inden doldurulmuş |
NO_PRICE_FOR_REFUND...297 |
oracleProvider |
Frontrunner, fonun oracle_quote'undan doldurulmuş |
Frontrunner |
Her ikisi de 0x9793a39f82993cdedb0c82c614102d5aba2451f29709dc9a6f7df191020b4efc'ye çözümlenir. NO_PRICE_FOR_REFUND adlı oracle, hiçbir zaman fiyat yayınlamayacak şekilde yapılandırılmıştı; bu da ödemeyi iade yoluna zorluyor.
Aşağıdaki analiz, 0x6ae9cb...51dcf8 işlemine dayanmaktadır.
- Adım 1:
181024772numaralı blokta tek bir atomik işlemde saldırgan, çakışanINJdeğer biriminde sigorta fonunu veUSDCdeğer biriminde ikili opsiyon piyasasını oluşturdu, fonu12.744.000.000hamINJ(yaklaşık0,000000063 $değerinde) ile doldurdu ve üç alt hesaba toplam30.267,02 USDCyatırdı. Toz yatırım, ham tam sayısının saldırganın oluşturmayı planladığı açığa uyacak şekilde boyutlandırıldı. Her alt hesap, daha sonra ihtiyaç duyacağı teminatı tam olarak aldı:
| Alt Hesap | Yatırım (USDC) |
Fonladığı Teminat |
|---|---|---|
037d |
1.593,001593 |
0,10'da 15.930'luk BUY, 1.593 kilitliyor (Adım 2) |
037c |
14.337,001593 |
0,10'da 15.930'luk SELL, 14.337 kilitliyor (Adım 2) |
037e |
14.337,014337 |
0,90'da 15.930'luk BUY, 14.337 kilitliyor (Adım 3) |
| Toplam | 30.267,017523 |
- |
-
Adım 2: Aynı işlemde,
037d,0,10'da15.930'luk bir BUY emri verdi ve037c,0,10'da15.930'luk bir SELL emri verdi. EndBlocker, bunları037diçin1.593teminat taşıyan bir LONG ve037ciçin14.337taşıyan bir SHORT olarak eşleştirdi. Defterlerdeki toplam teminat1,0Q = 15.930'dur, tam olarak piyasa havuzunun tuttuğu miktar, dolayısıyla defter herhangi normal, tam teminatlandırılmış bir piyasadan ayırt edilemez. -
Adım 3: İki blok ve 1,1 saniye sonra,
181024774numaralı bloktaki 0x012c17...2af694 işleminde,037d, uzun pozisyonunumargin = 0ile0,90'da kapattı ve1.593 + (0,90 - 0,10) * 15.930 = 14.337aldı. Bu ödeme, gelen açıcı037e'nin kilitlediği teminattan çıktı; bu hesap0,90'da15.930'luk bir BUY emri vererek14.337kilitlemişti.0,8Q = 12.744'lük gerçekleşen kar, artık037d'nin piyasa havuzu dışındaki kullanılabilir bakiyesinde yer alıyor, kalan iki pozisyon teminatı ise defterlerde kalıyor: yükümlülükler1,8Q = 28.674'ü gösterirken havuz hâlâ1,0Qtutuyor. -
Adım 4: Ödeme, bir işlem tarafından değil, piyasanın kendi saatinden tetiklenir. Her bloğun başında modül, ödeme zaman damgası geçmiş herhangi bir piyasayı alır ve pozisyon pozisyon değil bir bütün olarak öder. Bu piyasa, piyasa oluşturulmasından 18 saniye sonra tetiklendi; her iki pozisyon da hâlâ defterlerdeydi:
037c'nin SHORT'u ve037e'nin LONG'u, her biri14.337teminat. Oracle sessiz kaldı, bu yüzden iade yolu,1,0Q = 15.930'luk idealize edilmiş varlıklara karşı1,8Q = 28.674'lük yükümlülükleri hesapladı ve0,8Q = 12.744'lük bir açık bildirdi. Bu açık yalnızca iade yolunun muhasebesi içinde var. Tek bir fiyatSaltında, her tarafın ödemesi yalnızcaS'ye bağlıdır, nereden girildiğine değil: kısa pozisyon(1 - S) * Qalır, uzun pozisyonS * Qalır ve toplamları havuzdakiQ'ya tam olarak eşittir. İade yolu ise bunun yerine her tarafın kendi giriş fiyatı üzerinden öder, dolayısıyla girişler birbirini götürmez: kısa pozisyona(1 - 0,10) * Q, uzun pozisyona0,90 * Qödendi, her biri14.337. Kısa pozisyon, fiyat sanki hiç0,10'dan hareket etmemiş gibi tam teminatını tahsil etti; bu, saldırganın Adım 3'te zaten çektiği aynı0,8Q'dur. -
Adım 5:
PayDeficitFromInsuranceFund(), çakışan fondan12.744.000.000hamINJçıkararak ve piyasa havuzuna12.744 USDCkredilendirerek açığı kapattı. Açık karşılanmış olarak bildirildiğinden, kalan pozisyonlar için toplumsallaştırılmış zarar kesintisi atlandı ve her pozisyona tam teminatı iade edildi. Açığın kendisi kimseye bir şeye mal olmaz: piyasanın sigorta fonundan veya bu mümkün olmadığında kesintiden karşılanır. Bunu kârlı hale getiren şey, piyasaya bağlı fonunUSDCyerineINJtozu tutmasıydı. -
Adım 6:
181024803numaralı bloktaki 0xcb33ad...152eff işleminde saldırgan, yatırılan30.267,02 USDC'ye karşılık43.010.985.663hamUSDC, yani43.010,99 USDCçekti. Net kazanç12.743,97 USDC'dir; ilk işlemden sonuncuya kadar yaklaşık 21 saniye sürmüştür.
Yukarıdaki döngü, temsili bir turdur. Saldırgan bunu, 19 saatlik bir pencerede oluşturulan 299 kısa ömürlü ikili opsiyon piyasasında tekrarladı; her biri hiçbir zaman fiyat göndermeyecek şekilde yapılandırılmış bir oracle'a bağlıydı ve her birinin sona erme ve ödeme zaman damgaları birbirinden saniyeler uzaklıktaydı [2]. Bu turlardan elde edilen net kazançlar, olayda kaybedilen yaklaşık 4,8 milyon dolara ulaşıyor.
Sonuç
Bu olay, bir kimlik çakışmasını ödeme yolundaki eksik bir değer birimi kontrolüyle birleştiriyor. Bir fon, kimliğinin eşleştiği piyasayı desteklemek üzere tasarlanmıştır. Ancak kimlik alanlarını uçtan uca birleştirerek oluşturulan bir kimlik, bir alanın nerede bittiğini ve diğerinin nerede başladığını artık kaydetmez, dolayısıyla iki farklı alan seti aynı kimliği üretebilir ve eşleştirme, bir fonu aslında eşleşmediği bir piyasaya bağlar. Sonrasında hiçbir şey bu uyumsuzluğu yakalamaz, çünkü bir piyasa açığını kapatmak için bir sigorta fonundan yararlanan yol, miktarları karşılaştırır ama asla değer birimlerini karşılaştırmaz. Saldırgan, iki kusuru birlikte kullanarak kontrol ettiği bir piyasa içinde hayali bir açık oluşturdu, bunu bir sentin küçük bir kesri değerindeki bir fon bakiyesiyle kapattı ve havuzun asla tutmadığı teminatların tam bir iadesiyle çekip gitti.
Daha genel olarak, anlam taşıyan bir tanımlayıcı, her değişken uzunluklu alanda açık ayraçlar veya uzunluk önekleri bulunan, yapıyı koruyan bir kodlamadan türetilmelidir, böylece iki farklı alan demeti aynı özete eşlenemez.
Bu Haftaki Diğer Olaylar
Ankr FLOW
2026/08/31 tarihinde, Ankr'ın Flow EVM üzerindeki likit stake hizmeti istismar edildi. Ankr, ağın yerel token'ı olan stake edilmiş FLOW'a karşılık iki farklı token verir ve her biri kendi giriş noktası aracılığıyla basılır. Bu yollardan biri devre dışı bırakılmıştı, ancak buna giden ikinci bir yol, duraklatmayı zorunlu kılan kontrolü atladı ve bu yoldaki dönüşüm oranı bu süre zarfında bayatlamıştı. Saldırgan, aynı token'ların geri alınabileceği fiyattan çok daha ucuza bunun üzerinden basım yaptı, ardından bu açığı Ankr'ın geri alım tamponu, bir Uniswap V3 havuzu ve MORE Markets ödünç verme protokolü üzerinden döngüye soktu. O zamanki değeriyle yaklaşık 410 bin dolar değerinde yaklaşık 15,5M WFLOW (sarılmış FLOW), MORE Markets rezervinden çekildi ve saldırgan kayma (slippage) sonrası yaklaşık 246 bin dolar elde etti [3].
Arka Plan
Ankr FLOW, Flow EVM üzerinde bir likit stake hizmetidir. FlowStakingPool, validatör stake'i için FLOW'u Cadence'a iletir ve ortaya çıkan pozisyonları iki token aracılığıyla temsil eder: rebasing yapmayan sertifika token'ı ankrFLOW ve kendisi ankrFLOW tarafından desteklenen rebasing yapan taşıyıcı token aFLOWEVMb.
İki token'ın ayrı giriş noktaları vardır. Sertifika yolu, stakeCerts() ve unstakeCerts() üzerinden _stakeCerts() ve _unstakeCertsFor()'a gider; taşıyıcı yol ise stakeBonds() ve unstakeBonds() üzerinden _stakeBonds() ve _unstakeBondsFor()'a gider. Taşıyıcı yoldaki basım işleminin, Ankr'ın yönlendirme programı için bir ortak kodu alan ve ardından aynı dahili _stakeBonds()'u çağıran ikinci bir dış giriş noktası, stakeBondsWithCode(), vardır. Her yol, FLOW ile her bir token arasındaki dönüşüm oranını yayınlayan sözleşme olan InternetBondRatioFeed'den kendi girişini okur.
FlowStakingPool ayrıca acil geri alımlar için bir FLOW tamponu tutar. Havuzun ötesinde, ankrFLOW, bir Uniswap V3 ankrFLOW/WFLOW havuzunda işlem gördü ve bir Aave V3 tarzı ödünç verme protokolü olan MORE Markets'te teminat olarak kabul edildi. MORE Markets, borç vermeyi varlık başına ayarlanan bir kredi-değer oranı (LTV) ile sınırlar ve e-mode (verimlilik modu) kategorileri sunar: fiyatlarının birlikte hareket etmesi beklenen varlık grupları, bir borçlu kategoriyi etkinleştirdiğinde daha yüksek bir LTV uygulanır.
Zafiyet Analizi
Hatalı sözleşme, sertifika token'ı ankrFLOW'u (0x1b97...14bdb) ve taşıyıcı token aFLOWEVMb'yi (0xd6fd...f8d4a) InternetBondRatioFeed'den (0x3201...de38f) okunan oranlara karşı basan FlowStakingPool'dur (0xfe81...287a).
İki kusur çakışıyor. Birincisi, stakeBondsWithCode(), stakeBonds()'un zorunlu kıldığı bondStakingUnpaused değiştiricisi olmadan _stakeBonds()'a ulaşır, dolayısıyla taşıyıcı token yolu devre dışı bırakıldıktan sonra da erişilebilir kaldı. İkincisi, 29 Nisan 2025 ile 27 Ağustos 2026 arasında 71 haftalık oran güncelleme partisinde, yalnızca aktif ankrFLOW girişi yenilendi ve aFLOWEVMb girişi 1,0'da kaldı.
Bu nedenle iki oran, aynı temel stake'i farklı şekilde fiyatlandırdı:
| Yön | Fonksiyonlar | Oran | Dönüşüm |
|---|---|---|---|
| Basım | _stakeCerts() / stakeCerts() |
0.833437 | 1 FLOW = 0.833437 ankrFLOW |
| Basım | _stakeBonds() / stakeBondsWithCode() |
1.0 | 1 FLOW = 1 aFLOWEVMb |
| Geri Alım | _unstakeCertsFor() / unstakeCerts() |
0.833437 | 1 ankrFLOW = ~1.19985 FLOW |
| Geri Alım | _unstakeBondsFor() / unstakeBonds() |
1.0 | 1 aFLOWEVMb = 1 FLOW |
aFLOWEVMb, ankrFLOW tarafından desteklendiğinden, taşıyıcı yol üzerinden basım, yatırılan her FLOW başına bir desteklenmiş ankrFLOW üretirken, sertifika yolu 0,833437 üretti. Sertifika yolu üzerinden geri alım, ankrFLOW başına hâlâ ~1,19985 FLOW ödüyordu.
Saldırı Analizi
Aşağıdaki analiz, 0x2b2e6e...3f66c9 işlemine dayanmaktadır.
-
Adım 1: Saldırgan, iki yol arasında tek bir gidiş-dönüş ile sermaye ön yükledi. Uniswap V3
ankrFLOW/WFLOWhavuzundan5.000 ankrFLOWflash loan aldı, bunuunstakeCerts()üzerinden~5.999,25 FLOWkarşılığında geri aldı,stakeBondsWithCode()üzerinden5.000,50 FLOWyatırarak aynı miktardaankrFLOWile desteklenmiş5.000,50 aFLOWEVMbbastı ve krediyi ile0,50 ankrFLOWprimini geri ödemek için buankrFLOW'u serbest bırakmak üzereunlockShares()'i çağırdı. Yaklaşık998,75 FLOWişletme sermayesi olarak kaldı. -
Adım 2: Saldırgan bir Uniswap V3 swap işlemini 50 kez tekrarladı. Her döngüde, bir fiyat limiti ile
ankrFLOW'uWFLOWile takas etti, havuza borçlu olduğuankrFLOW'u swap geri çağrısı içindeFLOW'ustakeBondsWithCode()veunlockShares()üzerinden yönlendirerek bastı, ardından çıktıWFLOW'u bir sonraki döngü için sarımından çıkardı. 50 döngü boyunca havuz~38.634.755,38 ankrFLOWaldı ve~46.265.167,78 WFLOWödedi, bu da saldırganın bakiyesini~998,75 FLOW'dan~7.631.411,14 FLOW'a yükseltti. -
Adım 3: Saldırgan, taşıyıcı yol üzerinden
~38.601,95 FLOWdönüştürdü ve ortaya çıkanankrFLOW'uunstakeCerts()üzerinden geri aldı; bu daFlowStakingPool'un hâlâ tuttuğu~46.316,57 FLOW'luk geri alım tamponunu boşalttı ve~7.714,62 FLOWekledi. -
Adım 4: Saldırgan MORE Markets'e yöneldi.
ankrFLOWveWFLOW'u ilişkiliFLOWvarlıkları olarak ele alan e-mode kategori 1'i ("Sarılmış yerel token'lar") etkinleştirdi ve bu,ankrFLOWLTV'sini %78,5'ten %97'ye yükseltti.stakeBondsWithCode()üzerinden~7.639.125,76 FLOWyatırdı, eşleşenankrFLOW'un kilidini açtı, bunu teminat olarak sundu ve~5.668.483,10 WFLOWborç aldı. Bu borcun sarımını açıp aynı yol üzerinden geri göndermek (tek bir döngü borçlanma turu) eşit miktardaankrFLOWekledi, teminatı~13.307.608,86 ankrFLOW'a çıkardı ve~1,19985 WFLOWoracle oranındaki teminat değerinin yaklaşık %97'sini oluşturan, toplam~15.488.124,15 WFLOWborç için ikinci bir~9.819.641,05 WFLOWborçlanmasını destekledi.
Adım 4'teki teminat, basılma maliyetinden daha fazlasını geri döndürdü: taşıyıcı yolda bir FLOW, bir ankrFLOW bastı, oracle bu ankrFLOW'u ~1,19985 WFLOW olarak değerlendirdi ve e-mode buna karşı %97 borçlanmaya izin verdi, dolayısıyla sağlanan ~13,31M ankrFLOW, ~15,49M WFLOW'luk borcu destekledi; yatırılan her FLOW için ~1,16 WFLOW. Saldırgan, ikinci borçlanma WFLOW rezervini boşalttığı için yalnızca bir kez geri dönüştürdü.
Bu ~15,49M WFLOW, brüt borçlanmadır ve MORE Markets rezervinden çekildiği bildirilen 15,5M WFLOW'dur. Bunun yaklaşık 5,67M WFLOW'u, likit gelir olarak tutulmak yerine sarımından çıkarılıp ek teminat olarak geri dönüştürüldü; ikinci ~9.819.641,05 WFLOW'luk borçlanmanın sarımı açıldıktan sonra, saldırgan nakde çevirme olarak ~9.819.641,05 FLOW elinde tuttu. Değerin nereden geldiğine göre atfedildiğinde, bu nakde çevirmenin ~7.630.412,39 FLOW'u Uniswap V3 havuzundan, ~8.713,37 FLOW'u FlowStakingPool'dan ve ~2.180.515,29 FLOW'u MORE Markets'ten geldi.
Sonuç
Bir giriş noktasını koruyup kardeşini korumayan bir durum kontrolü, buradaki temel nedendir: devre dışı bırakılmış bir basım yolu çağrılabilir kaldı ve oranı on altı aylık haftalık güncellemeler boyunca yenilenmedi. Bu nedenle bu yoldan yönlendirilen her FLOW, sertifika yolunun asla teklif etmeyeceği bir fiyattan ankrFLOW üretti ve saldırgan bu uyumsuzluğu bir Uniswap V3 havuzu, stake havuzunun geri alım tamponu ve bir ödünç verme piyasası üzerinden döngüye sokarak her durakta ucuz ankrFLOW'u WFLOW likiditesine dönüştürdü.
Duraklatma korumaları, yalnızca çağrılması beklenen giriş noktasında değil, devre dışı bırakılan mantığa ulaşan her giriş noktasında zorunlu kılınmalıdır ve durgun bir yola hizmet eden bir oran beslemesi ya güncel tutulmalı ya da geri dönmeye zorlanmalıdır. Ödünç verme protokolleri ayrıca, basım fiyatı oracle fiyatından bağımsız olarak belirlenen bir likit stake token'ına yüksek bir e-mode LTV vermekten kaçınmalıdır; basım maliyetini, geri alım değerini ve oracle fiyatını birlikte izlemek bu ayrışmayı ortaya çıkarabilirdi.
Aquifer
2026/08/31 tarihinde, Solana üzerinde özel bir piyasa yapıcı AMM'i olan Aquifer, USDC, USDT, HYPE, cbBTC, CASH ve on üç diğer token'a yayılan 212 başarılı takas işleminde yaklaşık 2,47 milyon dolar için istismar edildi [4]. Her takas, her yönde bir tane olmak üzere iki token transferi olarak gerçekleşir ve Aquifer, çağırıcının bu transferlerin her birini hangi programın gerçekleştireceğini, bu seçimi hiç kontrol etmeden seçmesine izin verdi. Saldırgan, Aquifer'a ödeme yapması gereken transfer için kendi programını belirledi ve bu program hiçbir şeyi hareket ettirmeden başarı bildirdi; diğer yönde giden transfer ise gerçek Token Program üzerinden çalıştı ve Aquifer'ın kasalarından gerçek varlıklar teslim etti.
Arka Plan
Aquifer, bir Özel AMM'dir; yani Uniswap V2 tarzı havuzların yaptığı gibi takasları sabit çarpım eğrisi boyunca fiyatlandırmak yerine, profesyonel bir piyasa yapıcı kendi envanterini sağlar ve alış-satış kotasyonlarını sürdürür. Aquifer, kotasyon ve risk durumundan bir fiyat türetir, ardından ticareti kullanıcının Token Hesapları ile kendi kasaları arasında tamamlar.
Solana'da bir Token Programı, transfer, basım ve yakma gibi işlemleri uygulayan çalıştırılabilir bir programdır. Tokenkeg, orijinal SPL Token Programı'dır ve Token-2022 onun genişletilebilir halefidir; her biri tek bir varlık yerine birçok farklı token'ı yönetir. Bir Mint Hesabı, bir token türünü tanımlar ve arzını, ondalık basamaklarını ve yetkililerini saklar, bir Token Hesabı ise bir tutucunun bir Mint için bakiyesini saklar; her ikisi de kendilerini yöneten Token Programı tarafından sahiplenilir. Aquifer'ın kasaları, Aquifer PDA'ları tarafından kontrol edilen Token Hesaplarıdır.
Bir yatırımcı, Aquifer'ın swap talimatını çağırarak ve iki transferden her biri için, transferi çalıştırması gereken Token Programı da dahil olmak üzere, dokunacağı hesapları geçirerek işlem yapar. Aquifer, bu token'ları çapraz program çağrısı (CPI) aracılığıyla hareket ettirir: bir transfer talimatı oluşturur ve bunu Instruction.program_id tarafından belirtilen programa verir. Transfer şeklindeki talimat verisi, yalnızca doğru Token Programı bunu çalıştırdığında gerçek SPL token'larını hareket ettirir.
Zafiyet Analizi
Hatalı program Aquifer'dır (AQU1FR...Tz45). Dağıtılan bayt kodunu eşleştiren yayınlanmış kaynak bulunmadığından, aşağıdaki analiz programın ayrıştırmasından geri kazanılmıştır: fn_ isimleri, dahili işlevleri kod ofsetlerine göre etiketler ve swap dahil burada kullanılan hiçbir isim geliştiricilerden gelmez. Program, yalnızca Tokenkeg ve Token-2022'yi kabul eden bir izin listesi işlevi olan fn_49740()'ı içerir, ancak swap yolundaki hiçbir şey onu çağırmaz. Bir swap'ı işlerken, program her iki transfer talimatını da sabit kodlanmış bir Tokenkeg sabiti kullanarak fn_45f20() ve fn_46bf8() üzerinden oluşturur, bu da zorunlu olarak dahili kontrollerini karşılar, ardından çağırmadan önce her talimatın program_id'sini çağırıcı tarafından sağlanan Token Programı ile üzerine yazar:
// Programın bir swap için çalıştırdığı kodun anlamsal yeniden yapılandırması; construct_transfer()
// fn_45f20() ve fn_46bf8()'i temsil eder ve izin listesi fn_49740()'a hiç ulaşılmaz.
let checked_program = TOKENKEG_ID;
let mut output_instruction = construct_transfer(checked_program, output_accounts);
let mut input_instruction = construct_transfer(checked_program, input_accounts);
// Kontrol edilmeyen çağırıcı girdileri, kontrol edilen değerin yerini alır.
output_instruction.program_id = caller.token_program_b.key;
input_instruction.program_id = caller.token_program_a.key;
// Her invoke(), transfer talimatını çağırıcının adlandırdığı programa devreder.
invoke(output_instruction)?;
invoke(input_instruction)?;
Burada TOKENKEG_ID, TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'yı belirtir. Doğrulamadan geçen değer, dolayısıyla asla çalıştırılan değer değildir. Bunu daha da kötüleştiren şey, programın giriş CPI'sı döndükten sonra kasanın alınan miktarını doğrulamamasıdır, dolayısıyla hiçbir şey transfer etmeden başarılı olan bir CPI, ödeme olarak kabul edilir.
Saldırı Analizi
Olay, 212 başarılı saldırı işleminden oluşuyor. Aşağıdaki analiz, temsili bir örnek olan 4pBV1G...T3bf işlemine dayanmaktadır.
-
Adım 1: Saldırgan,
4.957,497101 USDC'lik nominal bir girdi ileswap'ı çağırdı; Aquifer bunun için195.849,433667 KMNO'luk bir çıktı hesapladı.KMNObacağı için Tokenkeg sağladılar;USDCbacağı için iseUSDCMint'ini, saldırganın yetkisini ve biru64::MAXbakiyesini taşıyan bir SPL Token Hesabını taklit eden 165 baytlık, bu program tarafından sahiplenilen sahte bir giriş hesabı9gsKJc...ile birlikte kendi programlarıDMBpPM...NRgb68'i sağladılar. -
Adım 2: Çıkış CPI'sı Tokenkeg'e ulaştı ve Aquifer
KMNOkasasından195.849,433667 KMNO'yu, imzalayıcı7fTe9p...4gRk7Jtarafından kontrol edilen saldırganın Token HesabıEUjkGc...'ye transfer etti.

- Adım 3: Giriş CPI'sı,
USDCtransfer şeklindeki veriyleDMBpPM...NRgb68'e ulaştı. Bu program, sahte kaynak9gsKJc...'den gerçek AquiferUSDCkasası7ULN1Y...'a herhangi birUSDChareket ettirmeden başarı döndürdü.

- Adım 4: Aquifer her iki CPI dönüşünü de kabul etti, dolayısıyla takas atomik olarak işlendi.
Bu işlem için ortaya çıkan bakiye değişiklikleri aşağıdaki gibidir.
| Hesap | Öncesi | Sonrası | Değişim |
|---|---|---|---|
Aquifer KMNO kasası 9BHsZp...FHSqG |
604.968,018277 KMNO |
409.118,584610 KMNO |
-195.849,433667 KMNO |
Saldırgan KMNO hesabı EUjkGc... |
0 KMNO |
195.849,433667 KMNO |
+195.849,433667 KMNO |
Aquifer USDC kasası 7ULN1Y... |
1.620.342,341679 USDC |
1.620.342,341679 USDC |
0 USDC |
Bu rakamlar yalnızca bu örnek işlemi tanımlar, olayın toplam kaybını değil.
Sonuç
Temel neden, fiyat veya oracle manipülasyonu değil, ödeme yolunda doğrulanmamış bir çağırıcı girdisidir: takas yolu, bir Token Programı sabitini doğruladı ve ardından talimatı çağırmadan önce bunu çağırıcı tarafından sağlanan biriyle değiştirdi, dolayısıyla Aquifer'a ödeme yapması gereken transferi hangi programın gerçekleştireceği çağırıcının seçimiydi. Kasada transfer sonrası bakiye kontrolü olmadığından, token hareket ettirmeden başarı döndüren bir program ödemeyi karşılarken, diğer yöndeki transfer gerçek varlıkları teslim eder.
Her CPI, çağırıcı tarafından sağlanan hesaplardan alınmak yerine Mint Hesabından çözümlenerek, taşınan Mint'e sahip olan Token Programına bağlanmalıdır ve kasanın bakiyesi, giriş transferinden önce ve sonra okunmalıdır, böylece kasa gerçekten teklif edilen miktarı almadıkça takas geri alınır.
Notional Finance
2026/09/03-09/04 (UTC) tarihinde, Ethereum üzerindeki Notional Finance V1, yaklaşık 1,73 milyon dolar için istismar edildi; 69.257,37 DAI ve 1.658.524,86 USDC olarak boşaltıldı. Bir hesabın borç almasına izin vermeden önce, protokol o hesabın tuttuğu ve borçlu olduğu her şeyi değerler ve bu yoldaki güvensiz bir sayısal dönüşüm, doğru büyüklükteki bir borcu sıfıra düşürdü. Bu nedenle kontrol, yükümlülüğü kaybolmuş bir hesabın geçmesine izin verdi; oluşturduğu büyük alacak ise saldırganın kontrol ettiği başka bir sözleşmede sağlam kaldı. Saldırgan, bu sahte alacağın protokolün bir sonraki vadesinde, gece yarısı UTC'de vadesi dolacak şekilde zamanlamıştı ve protokolün hâlâ tuttuğu DAI ve USDC'yi çekmek için birkaç dakika sonra ödedi.
Arka Plan
Notional Finance V1, Ethereum üzerinde sabit oranlı bir ödünç verme protokolüdür. Nakit akışlarını fCash ile önceden tanımlanmış vadelerde temsil eder: CASH_RECEIVER, vadede varlık almaya hak kazanan pozitif bir pozisyondur ve CASH_PAYER, bunları ödemekle yükümlü negatif bir pozisyondur. Her hesabın fCash ve diğer pozisyonları, bir varlığın nakit grubu ile vadesi birlikte tanımlandığı Portföyünde takip edilir; nakit grubu, hesaplandığı para birimini sabitler. Tek bir varlığın nominal değeri, vadede ödendiği tutar, bir uint128'dir.
ERC1155Trade.safeTransferFrom(), iki hesap arasında bir fCash çifti oluşturur. Çağrı bir ERC-1155 transferi şeklinde biçimlendirilmiştir, ancak hiçbir şey el değiştirmez: alıcı için pozitif bir pozisyon ve ödeyici için eşit bir negatif pozisyon olmak üzere iki karşılıklı pozisyon oluşturmak için Portfolios.mintfCashPair()'i çağırır. Her taraf, ilgili Portföye, nakit grubu ve vade her ikisi de eşleştiğinde yeni bir pozisyonu mevcut bir girişe katlayan ve iki nominal değeri SafeUInt128 kontrollü bir toplama ile ekleyen _upsertAsset() tarafından yazılır.
Ödeme gücü, hesabın Escrow nakit bakiyelerini Portföy değerlemesiyle birleştiren, para birimi başına girdileri işaretli bir int256'da toplayan freeCollateral() aracılığıyla ödeyici üzerinde kontrol edilir. Her para birimi bakiyesi, oran ve ondalık ölçeklendirme tam sayı bölmeleri olarak uygulanarak, o para biriminin döviz kuruna göre ETH'ye dönüştürülür ve nihai serbest teminat negatif olmamalıdır. Vadede bir fCash pozisyonu, Escrow.portfolioSettleCash() aracılığıyla hesabın nakit bakiyesine ödenir ve pozitif bir nakit bakiyesi, daha sonra Escrow'dan ilgili temel varlık olarak çekilebilir.
Zafiyet Analizi
Hatalı sözleşmeler, nominal bir sınır olmadan fCash çiftleri basan ERC1155Trade giriş noktası (0xbba8...ef08) ve _convertToETH() içinde bir borcu sıfır olarak değerleyebilen iki aritmetik kusur taşıyan teminat değerlemesine sahip Escrow'dur (0x9abd...f683).

Birincisi, kontrolsüz bir daraltma dönüşümü, büyük bir borcu sıfıra kesebilir. Fonksiyon, balance.abs()'i ham bir tür dönüşümüyle doğrudan uint128'e dönüştürür ve değerin hedef türe sığdığını hiç kontrol etmez. İki tür birbirine hiç yakın değildir: işaretli bir int256, 2^255 - 1'e kadar ulaşırken, uint128 2^128 - 1'de durur. Tam olarak -2^128'lik bir bakiye, bu nedenle int256'nın içine rahatça sığar ve balance.abs(), 2^128'i bozulmadan üretir. Başarısız olan tür dönüşümüdür: 2^128, uint128'in tutabileceğinden bir adım öteye düşer ve 0'a sarılır, dolayısıyla ardından gelen değerleme sıfır bir bakiye üzerinde çalışır ve tüm borç serbest teminat hesaplamasından düşer. Aynı fonksiyon, hesaplanan ETH değerinin sonraki dönüşümü için, aralık dışı bir sonuçta geri dönecek olan SafeCast.toUint128()'i kullanırken, önceki dönüşüm doğrudan bir tür dönüşümü olarak kalır ve sessizce keser.
İkincisi, tam sayı bölmesi küçük borçları sıfıra yuvarlayabilir. er.rateDecimals ve baseDecimals'a bölmeler kalanlarını keser, dolayısıyla yeterince küçük bir borç da 0 olarak değerlendirilir ve serbest teminattan çıkarılır.
Saldırı Analizi
Aşağıdaki analiz, 0xe1589a...25d60a işlemine dayanmaktadır.

-
Adım 1: 3 Eylül 2026, 23:58:47 UTC'de saldırgan,
cashGroupId = 2ve vade zaman damgası1788480000'i (4 Eylül 2026, 00:00 UTC), 73 saniye önde ve protokolün açık tuttuğu iki vadeden daha yakın olanı kullanarak,1miktarıyla bir fCash çifti basmak içinERC1155Trade'desafeTransferFrom()'u çağırdı. Basım sırasında,_upsertAsset(), saldırganın sözleşmesindeki negatif fCash yükümlülüğünü ve alıcı sözleşmesindeki pozitif alacağı kaydetti vePortfolios, saldırganın sözleşmesinin serbest teminatını hemen kontrol etti. Bu sözleşme hiçbir para biriminde bakiye tutmuyordu, dolayısıyla yeni yükümlülüğün herhangi bir pozitif değerlemesi kontrolü başarısız kılardı. İkinci kusur bunu kapattı: mevcut döviz kurunda,1'lik bir borç iki tam sayı bölmesinden hayatta kalmaz, dolayısıyla sıfır olarak değerlendirildi ve kontrol geçti. -
Adım 2: Saldırgan,
uint128.max(340.282.366.920.938.463.463.374.607.431.768.211.455) miktarıyla ikinci bir çift basmak için tekrarsafeTransferFrom()'u çağırdı. Bu çift,cashGroupId = 2'yi yeniden kullandı, ancak diğer açık vade olan1796256000(3 Aralık 2026, 00:00 UTC) vade zaman damgasını taşıdı ve pozitif tarafını farklı bir alıcı sözleşmesine gönderdi. Negatif taraf, Adım 1'dekinin yanına, tekrar saldırganın sözleşmesine indi. Tek bir basım hiçbir zaman2^128 - 1'i aşamaz; her zaman kesilecek değerin bir birim altında kalır, dolayısıyla buna ulaşmak iki pozisyon gerektirir. Farklı vade, ikisini ayrı girişlerde tuttu ve bir birleşmede geri dönecek olan kontrollü toplamanın erişiminden uzak tuttu; paylaşılan nakit grubu ise değerleme aşamasında yine de ikisini aynı para birimi yuvasına koydu; burada Adım 1'deki birim, toplamı tam olarak-2^128'e taşıdı. -
Adım 3: Teminat kontrolü sırasında, Escrow vekili
convertBalancesToETH()'i çağırdı. Toplanan negatif bakiye tam olarak-2^128'e ulaştı,_convertToETH()'e geçirildi ve sıfıra kesildi, dolayısıyla hesabın ETH cinsinden yükümlülüğü sıfır olarak bildirildi. -
Adım 4: Teminat muhasebesinden borç gittiğinden, ikinci alıcı sözleşmesi, protokolün kullanılabilir teminat olarak değerlendirdiği büyük bir pozitif fCash pozisyonu tutuyordu. Hâlâ aynı işlem içinde, bu teminata karşı iki fCash çifti daha bastı ve pozitif tarafı iki başka alıcı sözleşmesine verdi:
DAIcinsinden hesaplanancashGroupId = 2altında69.257,37veUSDCcinsinden hesaplanancashGroupId = 3altında1.658.524,86. Her iki rakam da saldırganın işlemin başında Escrow'da yaptığıbalanceOfçağrılarından geldi, dolayısıyla her alacak Escrow'un gerçekten tuttuğu bir bakiyeye göre boyutlandırıldı. Her biri,1788480000, yani 73 saniye ötede olan bir vade taşıyordu. -
Adım 5: 4 Eylül 2026, 00:01:35 UTC'de, bu vadeden 95 saniye sonra, saldırgan 0xc3f3e3...a24efa işleminde vadesi gelmiş iki alacağı ödedi ve Escrow'dan
~69.257,37 DAIve~1.658.524,86 USDCçekti. Fonlar daha sonra0x8aaf...3be6'ya iletildi.
Sonuç
Basım yolu hiçbir nominal sınır dayatmaz ve teminat değerlemesindeki iki aritmetik kusur, her biri bir ödeme gücü kontrolünün geçmesine izin verir: yuvarlama, hiçbir şey tutmayan bir hesabın ilk yükümlülüğünü üstlenmesine izin verdi ve ardından sessiz bir daraltma dönüşümü, tam olarak doğru büyüklükteki bir borcu sıfır olarak değerledi, dolayısıyla ikinci kontrol, derinden ödeme gücü olmayan bir hesapta geçti. Alıcı sözleşmesindeki sahte pozitif pozisyon daha sonra kullanılabilir teminat olarak ele alındı; bu da saldırganın onu Escrow'un bakiyelerine uyan alacaklara bölmesine ve hâlâ tuttuğu DAI ve USDC'yi çekmesine izin verdi.
İşaretli bakiyeler, herhangi bir daraltma dönüşümünden önce aralık kontrolünden geçirilmelidir ve girdisini temsil edemeyen bir dönüşüm, kesmek yerine geri dönmelidir. Daha genel olarak, bir ödeme gücü kontrolü, aritmetiğin bunu bir tür dönüşümüne mi yoksa bir yuvarlama adımına mı kaybettiğinden bağımsız olarak, sıfır olmayan bir borcu asla sıfır olarak bildirmemelidir. Aynı fonksiyonun daha aşağıda zaten kullandığı kontrollü tür dönüşümünü uygulamak veya tek bir çift basımının oluşturabileceği nominal değeri sınırlamak, bu istismarı tek başına durdurabilirdi.
Referanslar
[3] https://x.com/flow_blockchain/status/2094506622429307061
[4] https://bitquery.io/investigations/aquifer-solana-hack-2-5-million
BlockSec Hakkında
BlockSec, uçtan uca bir blockchain güvenliği ve kripto uyumluluk sağlayıcısıdır. Protokollerin ve platformların tüm yaşam döngüsü boyunca müşterilerin kod denetimi (akıllı sözleşmeler, blockchain ve cüzdanlar dahil) gerçekleştirmesine, saldırıları gerçek zamanlı olarak engellemesine, olayları analiz etmesine, yasa dışı fonları izlemesine ve AML/CFT yükümlülüklerini yerine getirmesine yardımcı olan ürün ve hizmetler geliştiriyoruz.
BlockSec, saygın konferanslarda birden fazla blockchain güvenliği makalesi yayınladı, DeFi uygulamalarının çeşitli sıfır gün saldırılarını bildirdi, 20 milyon dolardan fazlasını kurtarmak için birden fazla saldırıyı engelledi ve milyarlarca dolarlık kripto para birimini güvence altına aldı.
-
Resmi web sitesi: https://blocksec.com/
-
Resmi Twitter hesabı: https://twitter.com/BlockSecTeam



