Önceki makalemizde vurgulandığı üzere, Awesome Uniswap v4 Hooks deposundaki[1] projelerin %30'undan fazlası güvenlik açıkları barındırmaktadır. Burada bahsettiğimiz güvenlik açıklarının Uniswap v4 etkileşimlerine özgü olduğunu belirtmek gerekir. Bu doğrultuda, bu makalede güvenli hook etkileşim mantığını aşağıdaki iki perspektiften inceleyeceğiz:
- Hatalı Erişim Kontrolü
- Uygunsuz Girdi Doğrulaması
Her kategori için önce güvenlik açığının analiziyle başlayacak ve ilgili Kavram Kanıtı'nı (PoC) sunarak potansiyel istismarı göstereceğiz. Ardından olası azaltma stratejileri üzerine bir tartışma yapılacaktır.
Hatalı Erişim Kontrolü
Genel olarak, Uniswap v4'ün hook'larıyla ilgili etkileşimler; hook'un bir kilit alarak PoolManager'da işlem gerçekleştirmek üzere kilit sahibi (locker) rolünü üstlenip üstlenmediğine göre sınıflandırılabilir. Uygun erişim kontrolü gerektiren iki temel etkileşim senaryosu şunlardır:
- Hook-PoolManager Etkileşimi: Bu, resmi geri çağırma (callback) fonksiyonları ile
PoolManagerarasındaki etkileşimleri kapsar. Geri çağırma fonksiyonları arasında sekiz havuz işlemi geri çağırması (yaniinitialize,modifyPosition,swapvedonate) ve kilit geri çağırması (yanilockAcquired) yer alır.
- Hook-Dahili Etkileşimi: Bu, hook sözleşmesi içinde gerçekleşen etkileşimleri kapsar (kilit sahibi olarak hareket ederken).
Hook-PoolManager etkileşimleri nispeten daha basittir. Burada hook, tamamen bir hook olarak hareket ederek sekiz havuz işlemi geri çağırmasını kabul eder. Hook içindeki mantık ilgili havuzları etkilemez; yani hook ile havuzlar arasında fon akışı yoktur. Geri çağırma fonksiyonları tarafından sağlanan parametreler, gerekli depolamaları değiştirmek veya önemli fonksiyon parametreleri olarak kullanılır. Temel husus, geri çağırma parametrelerinin manipüle edilip edilemeyeceğidir.
Hook-Dahili etkileşimler biraz daha karmaşıktır. Pratikte, pek çok hook prototipi yalnızca saf bir hook olarak hareket etmekten fazlasını yapar. Bazı geliştiriciler, hook'ların kullanıcılarına fon yönetimi işlevleri sunmasına izin verir. Bu işlevler hook sözleşmelerinde uygulanmamış olabilir, ancak yine de bu bağlamda hepsini hook olarak değerlendirebiliriz. Bu durumlarda bir hook, kullanıcı fonlarını kabul eder ve likidite yönetimi veya takas gibi havuz işlemleri gerçekleştirir. Bu, sözleşmenin PoolManager'dan bir kilit alması gerektiği anlamına gelir ve hook'u bir kilit sahibine dönüştürür.
Uniswap Vakfı bu durumu göz önünde bulundurmuş ve hook şablonuna bir fonksiyon entegre etmiştir. Özellikle, BaseHook şablonu aşağıdaki gibi kilit geri çağırması olarak lockAcquired fonksiyonunu sağlar:
function lockAcquired(bytes calldata data) external virtual poolManagerOnly
returns (bytes memory) {
(bool success, bytes memory returnData) = address(this).call(data);
if (success) return returnData;
if (returnData.length == 0) revert LockFailure();
// if the call failed, bubble up the reason
/// @solidity memory-safe-assembly
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
Özel mantığı yürütmek için lockAcquired, data baytlarını kabul eder ve bu data'yı kullanarak kendisine düşük seviyeli bir çağrı gerçekleştirir. data, hook'un iş mantığına bağlıdır ve kullanıcılar tarafından manipüle edilebilir; bu durum lockAcquired tarafından tetiklenen Hook-Dahili etkileşimler nedeniyle güvenlik sorunlarına yol açabilir. Hook tasarımının o kadar esnek olduğunu ve bu durumda tüm olası senaryoları kapsamanın mümkün olmadığını belirtmek gerekir. Buradaki birincil odak noktamız hook'un bir kilit alması ve ardından gerçekleştirdiği dahili etkileşimlerdir. Diğer olası iş mantıklarına dalmak, bu tartışma için konuyu aşırı karmaşık hale getirecektir.
Her iki senaryoda da, bu fonksiyonların açık etkileşim varlıklarına sahip olduğu göz önüne alındığında, öncelik potansiyel istismara yol açabilecek hatalı erişim kontrollerini ele almaktır. Sonraki alt bölümlerde her senaryoyu sırayla inceleyecek ve daha güvenli bir etkileşim mantığı sağlamak için gerekli erişim kontrollerini tartışacağız.
Güvenlik Açığı Analizi
Erişim kontrolleri, pek çok proje için son derece verimli ve basit güvenlik çözümleri olarak hizmet eder. Bir fonksiyon belirli varlıklar tarafından çağrılmak üzere tasarlanmışsa, erişim kontrolü içermesi gerekir. Erişim kontrolünün en bilinen örneği, OpenZeppelin kütüphanesinin Ownable sözleşmesidir; bu sözleşme, ayrıcalıklı fonksiyonların yalnızca sözleşme sahibi tarafından çağrılmasını zorunlu kılar. Yukarıda tartıştığımız iki senaryonun bu tür bir kontrol için uygun durumlar olduğu açıktır.
Hook-PoolManager Etkileşimi: PoolManager ile güvenli etkileşimler için hook'lar, bu geri çağırma fonksiyonlarına gerekli erişim kontrolünü uygulamalıdır. Özellikle, bu geri çağırmalar yalnızca PoolManager tarafından çağrılabilmeli, başka hesaplar tarafından çağrılamamalıdır. Bu tür kontrollerin oluşturulmaması, söz konusu hassas arayüzlerin kötü niyetli aktörlerin potansiyel istismarına açık kalmasına neden olabilir.
Sekiz havuz işlemi geri çağırmasının ötesinde, PoolManager'dan kilit aldıktan sonra özel mantığı yürüten kilit (yani lockAcquired) geri çağırması da bu sorunu ele alması gerekmektedir.
Hook-Dahili Etkileşimi: Hook-dahili etkileşimlerde yer alan fonksiyonlar da belirli çağırıcılar tarafından çağrılmak üzere tasarlanmıştır. Daha önce belirttiğimiz gibi, bu senaryo iki aşama içerir. İlk olarak, kilit sahibinin lockAcquired fonksiyonu PoolManager tarafından çağrılır; bu, fonksiyonun msg.sender'ın PoolManager olmasını şart koşması gerektiğini gösterir. İkinci olarak, hook fonksiyon çağrısını buna göre dağıtır. BaseHook'un tasarımına göre bu, hook'un kendisine yapılan düşük seviyeli çağrılarla gerçekleştirilir. Bu, söz konusu fonksiyonların external olarak tanımlanması ve çağırıcının hook'un adresi olmasının zorunlu kılınması gerektiğini gösterir.
Awesome Uniswap v4 Hooks deposunda listelenen örneklerden biri olan Stop Loss Order'ı[2] örnek olarak ele alalım:
Uniswap V4 havuzlarına doğrudan entegre edilen stop loss emirleri zincir üzerinde yayınlanır ve afterSwap() hook'u aracılığıyla gerçekleştirilir. Yürütmeyi garanti etmek için harici bot veya aktörlere gerek yoktur.
afterSwap geri çağırma fonksiyonunu inceleyelim:

Açıkça görüldüğü üzere, yukarıdaki fonksiyon hassas işlemler gerçekleştirmek üzere tasarlanmıştır. Ancak hatalı erişim kontrolü nedeniyle, kötü niyetli aktörler argümanları (örneğin key ve params) manipüle ederek bu fonksiyonu istismar edebilir ve beklenmedik davranışlara yol açabilir. Örneğin, afterSwap geri çağırması, takasın PoolManager'da zaten gerçekleştiği varsayımı altında çalışabilir. Bunu takiben, mevcut fiyat veya toplanan takas ücretleri gibi temel durum bilgilerini kaydetmek için eylemler başlatabilir. Ancak afterSwap, çağrılarını yalnızca PoolManager'dan gelecek şekilde sınırlandırmazsa, kötü niyetli aktörler params parametresini sahte değerlerle doldurarak kaydedilen durumların çarpıtılmasına neden olabilir.
İstismar ve PoC
Basitlik adına, bu erişim kontrolü sorununu temel bir PoC ile açıklayacağız. Genel olarak, hook'un beforeInitialize fonksiyonu, hooks alanında bu hook adresini içermesi gereken PoolKey türünde bir parametre kabul eder (çünkü PoolManager bu alanı çağrılacak hook adresini belirlemek için kullanır).
Ekran görüntüsü, DiamondHookPoC[3]'te görüldüğü gibi hatalı erişim kontrolüne sahip bir hook'un istismarını gösteren bir PoC sunmaktadır.
beforeInitialize geri çağırma fonksiyonunda erişim kısıtlaması bulunmadığından, kötü niyetli aktörler bu fonksiyona istedikleri bir poolKey besleyebilir. Hook, bu poolKey'in hook'unun mevcut hook adresiyle eşleşip eşleşmediğini doğrulamaz.

Bu senaryodaki istismarın hook'a finansal kayıplara yol açmayabileceğini belirtmek önemlidir; bununla birlikte, korumasız geri çağırma fonksiyonları aracılığıyla hook'un durumunun nasıl manipüle edilebildiğini çarpıcı biçimde ortaya koymaktadır.
Nasıl Azaltılır
Hook-PoolManager etkileşimlerinin güvenliğini sağlamak için hem hook geri çağırmaları hem de kilit geri çağırması, erişimlerini yalnızca PoolManager ile sınırlandırmalıdır.
Neyse ki Uniswap v4, v4-periphery deposundaki[4] BaseHook aracılığıyla en iyi uygulamaları sunmaktadır.
BaseHook, çağrıları yalnızca PoolManager'dan gelecek şekilde kısıtlamak için poolManagerOnly değiştiricisini sağlar:
/// @dev Only the pool manager may call this function
modifier poolManagerOnly() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
Bu değiştirici, hassas hook ve kilit geri çağırmalarında uygun erişim kontrolünü uygulamak için etkin biçimde kullanılabilir.
Öte yandan, Hook-Dahili etkileşimlerin varlığı, BaseHook tarafından belirtildiği üzere lockAcquired geri çağırması aracılığıyla çağrılan önemli durum değiştiren fonksiyonların keyfi biçimde çağrılabilir olmamasını zorunlu kılar.
Bu gereksinimi karşılamak için BaseHook, bir selfOnly değiştirici sunar. Bu değiştirici, bildirilen fonksiyonun erişilebilirliğini yalnızca hook'un kendisiyle sınırlandırır; harici sözleşmelerin bu hassas fonksiyonları kötü niyetli amaçlarla doğrudan çağırmasını engeller.
/// @dev Only this address may call this function
modifier selfOnly() {
if (msg.sender != address(this)) revert NotSelf();
_;
}
Özetle, BaseHook'tan miras alarak özel hook'lar, uygun erişim kontrolünü uygulamak için bu yerleşik erişim kontrolü değiştiricilerinden ve geri çağırmalarından yararlanabilir.
Uygunsuz Girdi Doğrulaması
v4-periphery[4]'deki BaseHook, hook geliştiricilerinin yararlanabileceği daha güvenli bir etkileşim mantığı için bir çözüm sunar. Ancak, mevcut hook'larda yeni saldırı vektörlerine kapı açan uygunsuz kullanım örnekleri görmeye devam ediyoruz.
Varsayılan olarak, hook'lar herhangi bir havuzun PoolManager'daki initialize fonksiyonu aracılığıyla kaydolmasına izin verir. Ancak bir hook, kaydolan havuzdaki temel varlıkları doğrulamazsa, kötü niyetli kullanıcılar sahte token içeren bir havuz kaydederek token'ların transfer fonksiyonu aracılığıyla hook'a yeniden girebilir.
Bu güvenlik açığı, hook'un kendisinin kötü niyetli mantık yürütmemesi nedeniyle ince bir yapıya sahiptir. Ancak hook, PoolManager'ı çağırdığında, PoolManager ile kötü niyetli bir havuzun temel varlıkları arasındaki etkileşimler, PoolManager'daki take fonksiyonu aracılığıyla kontrolün bir saldırgana devredilmesine potansiyel olarak yol açabilir.
/// @inheritdoc IPoolManager
function take(Currency currency, address to, uint256 amount) external override
noDelegateCall onlyByLocker {
_accountDelta(currency, amount.toInt128());
reservesOf[currency] -= amount;
currency.transfer(to, amount);
}
Özünde, güvenlik açığı hook kullanıcılarının etkileşim kurmayı planladığı kayıtlı havuzda uygunsuz doğrulamalardan kaynaklanmaktadır. Bu güvenlik açığını somut bir örnekle ele alacak ve olası azaltma stratejilerini tartışacağız.
Güvenlik Açığı Analizi
Take Profits Hook[5], Awesome Uniswap v4 Hooks tarafından listelenen bir hook'tur:
Bu örnekte, kullanıcıların 'kâr al' pozisyonları açmasına olanak tanıyan bir hook oluşturuyoruz. Örneğin, bir ETH/DAI havuzunda 1 ETH = 1500 DAI ise, "1 ETH = 2000 DAI olduğunda tüm ETH'imi sat" şeklinde otomatik olarak gerçekleştirilecek bir kâr alma emri verebilirsiniz.
Bu hook'taki _handleSwap fonksiyonuna bir göz atalım. Bu fonksiyon, bir kilit aldıktan sonra kâr alma emirlerini doldurmak için bir takas gerçekleştirir.
![Şekil 3: Take Profits Hook'un _handleSwap fonksiyonu[5]](https://assets.blocksec.com/frontend/blocksec-strapi-online/take_profit_handle_Swap_36133dc1fe.jpg)
Bu fonksiyonun herhangi bir erişim kontrolü değiştiricisiyle korunmadığını fark etmiş olabilirsiniz. Ancak, 250. satır erişimi etkin biçimde kısıtlar; bu sayede fonksiyon yalnızca PoolManager'dan bir kilit alındıktan sonra çağrılabilir. Aksi takdirde, operatör en son kilit sahibi olmayacağından poolManager.swap başarısız olurdu. Başka bir deyişle, kayıtlı havuzlar doğrulanmış olsa, _handleSwap belirli bir sırayla çağrılmak zorundadır. Ne yazık ki hook böyle bir doğrulama uygulamamaktadır.
Bu hatalı uygulama nedeniyle hook, yeniden giriş saldırısına karşı savunmasızdır. Bu güvenlik açığı, saldırganların kullanıcılar tarafından yatırılan fonları kullanarak keyfi takasları zorla gerçekleştirmesine olanak tanıyabilir.
İstismar ve PoC
Özellikle saldırı aşağıdaki adımlarla gerçekleştirilebilir:
- Saldırgan, havuzun hook'u olarak Take Profits Hook'u belirterek sahte token içeren kötü niyetli bir havuz kaydeder.
- Saldırgan, hook aracılığıyla kötü niyetli havuzda bir kâr al emri verir.
- Saldırgan, kötü niyetli havuzda bir takas gerçekleştirerek
afterSwapgeri çağırmasındakifillOrder'ı tetikler ve saldırganın kâr alma emrini doldurur. - Hook, bir kilit talep etmek için
PoolManager'ınlockfonksiyonunu çağırır velockAcquiredgeri çağırmasında_handleSwapfonksiyonunu çağırır. _handleSwapfonksiyonunda, token transferleri sahte token sözleşmesindeki kötü niyetli mantığı tetikler ve bu mantık_handleSwapfonksiyonuna yeniden girer. Bu mümkündür çünkü_handleSwap, herhangi bir erişilebilirlik kısıtlaması olmayan harici bir fonksiyondur. Kilit zaten alınmış olduğundan, saldırgan hook'un yeterli temel varlığa sahip olması koşuluyla herhangi bir havuzda keyfi takaslar yaptırmaya zorlayabilir. Saldırgan, diğer kullanıcıların aleyhine kâr elde etmek için takasları sandviçleyebilir.
Aşağıdaki ayrıntılı diyagram, saldırının akışını göstermektedir.

Daha önce belirtildiği gibi, hook'un kendisi kötü niyetli mantık yürütmez. Tek hata hook'un güvenilmeyen token havuzlarının PoolManager sözleşmesine kaydolmasını engellememesidir. Dolaylı olarak, sahte token sözleşmesindeki kötü niyetli mantık, aynı zamanda güvenilmeyen harici çağrı türü olan token transfer işlemleri aracılığıyla çağrılmaktadır.
Nasıl Azaltılır
Uygunsuz girdi doğrulamasından kaynaklanan potansiyel saldırıları azaltmak için üç uygulanabilir yaklaşım mevcuttur:
-
Uygun Erişim Kontrolü.
BaseHook'tan yapı taşlarını kullanarak bir hook, fonksiyon erişilebilirliğini sıkı biçimde yönetebilir. Bu, keyfi hesapların hassas fonksiyonları çağırmasını engeller. -
Yeniden Giriş Kilidi. Yukarıdaki saldırı senaryosunda, bu yaklaşım şüphesiz kötü niyetli token mantığının hassas fonksiyonlara yeniden girmesini önleyebilir. Ancak bazı durumlarda hook tasarımı, hook'un kendisinin yeniden girilebilir olmasını gerektirir. Özellikle, bir hook'un bazı havuz işlemlerini yürütmesi gerektiğinde,
PoolManager'ın bu işlemleri tamamlamak için geri çağırmalarına yeniden girmesine izin vermesi gerekir. Yeniden giriş kilidi, bu amaçlanan işlevselliği bozabilir. -
Beyaz Liste Yaklaşımı. Bu yaklaşım, hook'larda onaylı havuzları beyaz listeye almak için ayrıcalıklı bir yönetici gerektirir. Yönetici, beyaz listedeki havuzların potansiyel riskler oluşturmadığını güvence altına alır. Ancak sınırlama şudur: hook kullanıcıları, yalnızca yönetici tarafından onaylanmış sınırlı sayıda havuzda hook aracılığıyla işlem gerçekleştirebilir. Beyaz liste yaklaşımı güvenliği artırırken hook'un işlevselliğini ciddi ölçüde kısıtlar.
Hook'lar için güvenlik ve kullanılabilirliği dengeleyen mükemmel bir çözüm bulmak zorludur. Birçok azaltma yaklaşımını tartışırken, geliştiricilerin hook tasarımlarındaki dengeleri dikkatli biçimde değerlendirmeleri gerekecektir. Hedef, amaçlanan işlevselliği korurken potansiyel riskleri mümkün olduğunca azaltmak olmalıdır. Ayrıca tartışmamız yalnızca Uniswap v4'e özgü etkileşimlerde ortaya çıkabilecek güvenlik açıklarını kapsamaktadır. Pratik uygulamalar şüphesiz çok daha kapsamlı olacaktır. Sözleşmelerinizin her satırını anladığınızdan emin olun ve GÜVENDE kalın!
Sonuç
Bu makalede, hook etkileşim mantığı sırasında ortaya çıkan güvenlik açıklarını inceledik; özellikle iki senaryoya odaklandık: hatalı erişim kontrolü ve uygunsuz girdi doğrulaması. Ayrıntılı bir güvenlik açığı analizi sunduk, PoC'leriyle birlikte potansiyel istismarları gösterdik ve olası azaltma stratejilerini tartıştık. Bu içgörülerin hook'ların güvenli geliştirilmesine ve kullanımına katkıda bulunabileceğine ve güvenlik açığı tespitindeki gelecekteki çabalara rehberlik edebileceğine inanıyoruz.
Referans
[2] Stop Loss Order
[3] DiamondHookPoC
[4] v4-periphery
[5] Take Profits



