Başarısızlığa sadece 111 bayt kala: HOT Omni Bridge güvenlik açığı nasıl $3.86M kayba yol açtı

Başarısızlığa sadece 111 bayt kala: HOT Omni Bridge güvenlik açığı nasıl $3.86M kayba yol açtı

Veriler, düzeltilen herkese açık kaynak kodu ile ağda fiilen çalışan sözleşme sürümü arasında olası bir tutarsızlığa işaret ediyor.

12 Eki 2026

Saldırgan, yeni bir yatırma tutarını çok aşan iadeler talep etmek için özel bir yardımcı token'ın önceden biriktirilmiş büyük bir bakiyesini kullandı. Ortaya çıkan devasa sayısal değerlerin yazılması, iade olayının NEAR'ın 16,384 baytlık günlük limitini aşmasına neden oldu. Bu nedenle Intents yatırma işleyicisi işlemi tamamlayamadı. Başarısızlığın ardından HOT, USDT dahil olmak üzere yatırmayı destekleyen orijinal varlıkları iade etti ancak daha önceki başarılı bir işlemle alacaklandırılan dahili Intents bakiyesi yerinde kaldı.

Adresler ve rolleri

AğAdres veya hesapRol
BSC0x09fd1f5d9f185067a92493e43aa259ea4ab3ad37Yaklaşık $3.865M alan saldırgan
BSC0x233c5370CCfb3cD7409d9A3fb98ab94dE94Cb4CdHOT Bridge hazinesi
NEAR20e6bfe4685d99004fc2095d3a0fc6158cd810ce8f801196141e183ed6e19d65Toplu yatırmaların ve çekim taleplerinin başlatıldığı saldırgan hesabı; aynı zamanda yatırmayı destekleyen iade edilen varlıkların alıcısı
NEARdd1fe58ec316ada5b673b88c14fe2fa2b7ede82080440901dfbd64b994241757Saldırganın sözleşmesi, dahili bakiyenin alıcısı ve bildirimler için geri çağırma sözleşmesi
NEAR873518f0f5cf120d7a8e2a416337bba16d7ae622e91f7da3862f07e89dc2ba9eDoğrulanabilen sonraki 1 Ekim işlem dizilerinde dahili bakiyenin alıcısı haline gelen saldırgan sözleşmesi. Kısa biçim: 8735…
NEARv2_1.omni.hot.tgHOT köprü sözleşmesi ve NEP-245 çoklu token temsillerinin kaydı
NEARintents.nearIntents doğrulayıcı sözleşmesi ve kullanıcı bakiyelerinin dahili kaydı

Arka plan

HOT Omni Bridge olarak da adlandırılan HOT Bridge, harici blokzincirlerden gelen varlıkları NEAR ağına bağlar ve token takasları için NEAR Intents ile entegre edilmiştir. Projenin herkese açık SDK'sı @hot-labs/omni-sdk'dir.

Sistem üç muhasebe düzeyini içerir: harici blokzincirde HOT hazinesinde tutulan gerçek varlık → bu varlığın NEAR üzerinde HOT tarafından ihraç edilen temsili → intents.near'daki kullanıcının dahili bakiyesi.

Normal bir gelen yatırma işlemi şu aşamalardan geçer:

  • Kullanıcı, kaynak blokzincirinde gerçek bir varlık yatırır.
  • HOT, yatırmayı doğrular ve NEAR üzerindeki karşılık gelen varlık temsiline izin verir.
  • HOT, NEAR üzerinde karşılık gelen token'ları basar. Bu token'lar Intents'e transfer edilir ve burada karşılık gelen tutar kullanıcının dahili bakiyesine yansıtılır.

Intents yatırma işleme kodu, isteğe bağlı bir alıcı bildirimini de destekler. Sistem önce fonları hesaba alacaklandırır, ardından alıcı uygulamayı çağırır. Bu uygulama, yatırılan tutarın bir kısmının iadesini talep edebilir.

Normal bir iadede, Intents önce dahili bakiyeden karşılık gelen tutarı çıkarmalı, ardından bu bakiyeyi destekleyen varlıkları iade etmelidir. İşleyici, kullanılmayan krediyi yakar, yani dahili bakiyenin karşılık gelen kısmını iptal eder ve HOT'a hangi tutarların kullanılmadan kaldığını bildirir. HOT daha sonra karşılık gelen token'ları orijinal gönderene iade eder.

Olay sırasında, ikinci ve üçüncü muhasebe düzeyleri arasındaki bağlantı koptu: HOT, yatırma desteğini NEAR üzerindeki temsil token'ları olarak iade etti ancak Intents, kullanıcının dahili bakiyesini korudu. Bu durum daha sonra köprünün normal çekim mekanizması aracılığıyla hazineden çekim talep edilmesini mümkün kıldı.

1. Hazırlık

29 Eylül'de saldırgan, 9SiSBv… işleminde NEAR üzerinde 10 BSC USDT aldı.

Saldırgan ayrıca Gonka ağından, ctfhote6ae2a90d1836ef1 özel yardımcı varlığının 97 × 10³⁶ + 97 minimal birimini transfer etti. Orijinal yatırma, Fmwao… işleminde varlığın NEAR üzerinde daha sonra alınmasıyla eşleşiyor. Sonuç olarak, HOT temsil token'ları 20e6… adresine basıldı.

Ardından, 4wf2… işleminde saldırgan, saldırının alıcı sözleşmesi dd1… için yardımcı varlığın 97 × 10³⁶ birimini Intents'e yatırdı. 97 minimal birim Intents dışında kaldı. Bu, daha sonraki aşırı büyük iade taleplerinin gerektirdiği büyük dahili bakiyeyi oluşturdu.

2. Güvenlik açığından yararlanma

Doğrulanan ilk istismar işlemi 3k91…, 29 Eylül'de saat 16:14:19.280 UTC'de başladı.

Saldırgan, HOT'un mt_batch_transfer_call fonksiyonunu çağırarak Intents'e, her biri bir birimlik yardımcı token'dan 97 giriş ile birlikte 10 USDT yatırdı. Intents, fonları dd1…'in iç bakiyesine başarıyla yatırdıktan sonra saldırganın kontrol ettiği alıcı sözleşmeyi bilgilendirdi. Alıcı sözleşme, her biri 10³⁶ birim olmak üzere yardımcı token'dan 97 adet miktarın iadesini talep etti ve ardından USDT iadesine gerek olmadığını belirtti.

Güvenlik açığı bulunan iade işleyicisi, iade miktarını mevcut bakiye ile sınırlandırıyordu ancak mevcut yatırmanın büyüklüğünü hesaba katmıyordu. Sonuç olarak, önceden hazırlanmış bakiye, token yakma olayına devasa iade miktarlarının dahil edilmesine olanak tanıdı.

Burada balance_left değişkeni, alıcının önceki yatırmalar dahil olmak üzere belirli bir varlıktaki tüm iç bakiyesini ifade eder. Kontrol, alıcının bakiyesinde yeterli fon olup olmadığını belirliyordu ancak iadeyi mevcut işlemde yatırılan miktarla sınırlamıyordu.

Sonuç olarak, alıcının önceden birikmiş bakiyesi bu miktarı karşılayabiliyorsa, yalnızca bir birimlik bir yatırma girişi 10³⁶ birimlik bir iade talebi oluşturabiliyordu. Hesaplanan sıfırdan farklı her iade miktarı, yukarıdaki kodun 198-199 satırlarında token yakma olayına eklendi.

Ortaya çıkan olayın boyutu 16,495 bayt idi:

BileşenHesaplamaBayt
97 tam token kimliği, her biri 125 karakter97 × (125 karakter + 2 tırnak işareti) + 96 virgül12,415
97 iade miktarı, her biri tırnak içinde 37 basamaklı bir dize97 × (37 basamak + 2 tırnak işareti) + 96 virgül3,879
EVENT_JSON öneki, meta veriler, sahip kimliği, not ve diğer JSON karakterleriSabit boyut201
Toplam12,415 + 3,879 + 20116,495

Olayı oluştururken ortaya çıkan hata, iade işleyicisi tarafından yapılan önceki bakiye değişikliklerini geri aldı. Ardından HOT, 10 USDT dahil olmak üzere orijinal yatırma grubunun tamamını iade etti. Yatırma işleyicisi, alıcı bildirimini ve sonraki mt_resolve_deposit çağrısını planlamadan önce self.deposit(...) fonksiyonunu çağırmıştı.

Dolayısıyla, sonraki bir çağrının başarısız olması, önceki bir çağrı içinde başarıyla yapılan değişiklikleri otomatik olarak geri almaz. Bu davranış NEAR belgelerinde açıklanmaktadır. Önceki başarılı aşamada yatırılan iç Intents bakiyesi yerinde kaldı.

3. Ölçeği büyütme

Daha sonraki işlemlerden birinde, 3mst2uZ32KPuCs7wc8rJqEtCrZqugCy4yDSZ3df8KPm3, saldırgan 649,984 USDT yatırdı ve gönderene tam olarak aynı miktarın iadesini talep etti.

Yeniden oluşturulan işlem geçmişi, iade işleyicisi hatalarıyla bağlantılı 23 toplu çağrı içermektedir. İç bakiyenin korunurken yatırma desteğinin iade edildiği tam model, altı vaka için güvenilir bir şekilde doğrulanmıştır.

Senkronizasyon sorunu: Verus protokolü blokzincirler arasındaki farklar nedeniyle $7.5M nasıl kaybetti3 Ağu 2026Devamını oku

4. Fonların çekilmesi

Ardından saldırgan, alıcı olarak BSC adresi 0x09fd…ad37'yi belirterek HOT üzerinden NEAR'dan para çekme talepleri gönderdi. Beş talep, BSC'deki 0x233c…4Cd adresindeki HOT hazinesinden yapılan başarılı USDT ödemelerine karşılık gelmektedir.

Güvenlik açığı

1. İç bakiye, alıcı iade talebine yanıt vermeden önce alacaklandırılır

Gelen yatırmaları işlemek için Intents uygulaması önce şunu çağırır:

self.deposit( receiver_id.clone(), core_token_ids .clone() .zip(amounts.iter().map(|amount| amount.0)), Some(«deposit»), ) .unwrap_or_else(|err| err.panic());

Bildirimli bir yatırma için sistem daha sonra alıcı sözleşmesine bir çağrı ve ardından mt_resolve_deposit'e bir çağrı planlar. İç bakiyeyi oluşturan aşama, sonraki iade işleyicisi bir hata atmadan önce başarıyla tamamlanabilir. Nihai yürütme sonucu HOT'a, daha önce oluşturulan iç bakiye zaten mevcut olmasına rağmen sonraki işlemin başarısız olduğunu söyler.

2. Güvenlik açığı bulunan hesaplama, mevcut yatırmadan daha büyük bir iadeye izin veriyor

NEAR'ın «chore: TEMP change that reflects deployed state» başlıklı 1 Ekim tarihli 35044fd… commit'i, iade mantığını şu şekilde yeniden üretir:

let requested_refund = requested_refund.unwrap_or(deposited); let balance_left = receiver.token_balances.amount_for(&token_id); let refund_amount = balance_left.min(requested_refund);*

Kaynak: resolve_deposit_internal.

Bu hesaplama mevcut bakiyeyi dikkate alır ancak iadeyi mevcut yatırma tutarıyla sınırlamaz. Döngü, sıfır olmayan her iade tutarını yakma (burn) olayına ekler, bunu yaparken alıcının bakiyesinden ve toplam iç token arzından düşer. Bundan sonra şu çağrı yapılır:

MtEvent::MtBurn([burn_event].as_slice().into()).emit();

Bu muhasebe değişiklikleri, iade işleyicisinin makbuzu (receipt) içinde yapılır. Olayın yazılması başarısız olursa, o makbuzda yapılan tüm değişiklikler de geri alınır.

3. Yayınlanan kod ile dağıtılan sürüm arasında olası bir tutarsızlık

İade tutarının mevcut yatırma büyüklüğüyle sınırlandırılmaması eksikliği, 2026 yılının 23 Mart tarihinde, 12:47:07 UTC'de herkese açık depoda düzeltildi. Düzeltme, «fix: cap notify refunds by the original amounts» başlıklı PR #240 / commit 015fa811…'de yayınlandı. Düzeltilmiş sürümde hesaplama şu şekildedir:

let refund_amount = requested_refund.min(deposited).min(balance_left);*

Saldırıda kullanılan bir birimlik yardımcı girişler için bu, önceden biriken bakiyeden bağımsız olarak her iadeyi en fazla bir birimle sınırlar. Bu mekanizma, iade tutarlarının aşırı büyük sayılarla çarpılmasını önler. Ayrıca yatırma muhasebesinin temel kuralını geri getirir: mevcut işlem için bir iade, önceki yatırmalarda yatırılan fonları alamaz.

Geçici 1 Ekim commit'i, dağıtılan sürümün davranışını yeniden üretmek için bu sınırı kaldırır. NEAR'ın Ekim ayı regresyon testine ilişkin not, aşırı büyük iade olaylarının «dağıtılan sürümde olduğu gibi» geri çağırmanın başarısız olmasına neden olduğunu açıkça belirtir.

Müdahale ve geri kazanım

Olaydan sonra, FPLaCtd… işleminde yansıtılan bir yükseltme gerçekleştirildi. 2 Ekim'de 20:31:41.711 UTC'de yürütülen ana işlem, intents.near sözleşmesi için ctl-intents.near.upgrade'i çağırır. Kontrolör olayı 0.4.4 sürüm numarasını listeler.

20:31:45.442 UTC'de kaydedilen başarılı bir iç içe dağıtım makbuzu (receipt), kodu dağıttı ve state_migrate fonksiyonunu çalıştırdı. Alex Shevchenko, $3.86M'ün tamamının iade edildiğini kamuoyuna açıkladı.

#Tether#Hackleme#DeFi
USDT iade edildi ama bakiyeden düşülmedi: HOT Omni Bridge'e nasıl saldırıldı