paint-brush
Akıllı Sözleşmelerde Tam Sayı Taşması/Eksik Akış Güvenlik Açığı Çözümüile@dansierrasam79
2,203 okumalar
2,203 okumalar

Akıllı Sözleşmelerde Tam Sayı Taşması/Eksik Akış Güvenlik Açığı Çözümü

ile Daniel Chakraborty9m2023/02/11
Read on Terminal Reader
Read this story w/o Javascript

Çok uzun; Okumak

Veri türleri, kodladıkları programlama diline bağlı olarak programcıların belirlemek için zaman ayırdığı veya belirtmediği bir şeydir. Veri türleri, temel aritmetik işlemler için önemlidir, ancak herhangi bir hesaplama için sınırlı bir aralığa sahiptir. Gerçek dünyada tamsayı taşmasının en popüler örneği, kat edilen mil değerinin 000000 olarak sıfırlandığı araçlarda görülür.
featured image - Akıllı Sözleşmelerde Tam Sayı Taşması/Eksik Akış Güvenlik Açığı Çözümü
Daniel Chakraborty HackerNoon profile picture


Hemen hemen hepimiz bazı hesaplamalar için veri girmek amacıyla Google E-Tablolar veya Microsoft Excel kullanmışızdır. Diyelim ki çalışanlarınızın isimlerini, telefon numaralarını, unvanlarını ve aldıkları maaşları girmek istiyorsunuz.


En basit haliyle, E-Tablolar'da veya Excel'de bir kayıt veya vaka şu şekilde görünür:

Excel veya Google E-Tablolar'daki bir kayıt veya vaka

Gördüğünüz gibi hem çalışan adı hem de unvan metinden, Telefon Numarası ve Maaş ise bir dizi rakamdan oluşuyor.


Yani anlamsal açıdan bakıldığında biz insanlar bu alanların gerçek dünyada ne anlama geldiğini anlıyoruz ve aralarında ayrım yapabiliyoruz.


Açıkçası, farkı anlamak için Bilgisayar Bilimleri diplomasına ihtiyacınız olmasa da, bir derleyici veya yorumlayıcı bu verileri nasıl işler?

Veri tipleri

Veri türlerinin devreye girdiği yer burasıdır ve bu, kodladıkları programlama diline bağlı olarak programcıların belirtmeye zaman ayırıp ayırmadıkları bir şeydir.


Yani çalışan adı ve unvanı altındaki veri noktalarına string adı verilmektedir. Elbette maaş, ondalık noktasının olmaması nedeniyle açıkça bir tam sayıdır. Basitçe söylemek gerekirse bunlar, kod yazarken bu şekilde bildirilmesi gereken veri türleridir, böylece yalnızca o veri türüyle ilişkili doğru işlemler gerçekleştirilebilir.


Solidity'de bir tamsayı veri türünü şu şekilde bildiririz:

Bununla birlikte, yukarıdaki e-tablodaki Telefon Numarası alanı benzersiz bir dize olarak kullanılacak bir veri noktası içermektedir, ancak bu tartışma başka bir güne kaldı. Şimdilik odak noktamız hepimizin temel aritmetik işlemlerini gerçekleştirdiğimiz ilkel veri türü üzerinde olacak.


Evet, temel aritmetik işlemler için önemli olmasına rağmen her türlü hesaplama için sınırlı bir aralığa sahip olan tamsayı veri türünden bahsediyoruz.

Tamsayı Taşması/Eksiği Neden Oluşur?

Tamsayı taşmasının gerçek dünyadaki en popüler örneği muhtemelen araçlarda yaşanıyor. Kilometre sayacı olarak da bilinen bu cihazlar genellikle bir aracın kaç kilometre kat ettiğini takip eder.


Peki, kat edilen mil değeri altı basamaklı bir kilometre sayacında işaretsiz 999999 tamsayı değerine ulaştığında ne olur?


İdeal olarak, bir mil daha eklendiğinde bu değerin 1000000'e ulaşması gerekir, değil mi? Ancak yedinci rakam için hüküm olduğu için bu gerçekleşmiyor.


Bunun yerine katedilen mil değeri aşağıda gösterildiği gibi 000000 olarak sıfırlanır:

Bir kilometre sayacında tamsayı taşması


Tanım gereği, yedinci basamak mevcut olmadığından, doğru değer temsil edilmediğinden bu durum 'taşma' ile sonuçlanır.


Resmi anladınız, değil mi?


Tam tersi çok sık olmasa da tam tersi durumlar da yaşanabilmektedir. Başka bir deyişle, kaydedilen değer aralıkta mevcut olan en düşük değerden daha düşük olduğunda ve buna "düşük akış" adı verilir.


Hepimizin bildiği gibi bilgisayarlar tamsayıları ikili eşdeğerleri olarak bellekte saklarlar. Şimdi basitlik adına 8 bitlik bir yazmaç kullandığınızı varsayalım.


Dolayısıyla, işaretsiz 511 tamsayısını depolamak istiyorsanız, bu şu şekilde bölünür:


= 2⁸*1 + 2⁷*1 + 2⁶*1 + 2⁵*1 + 2⁴*1 + 2³*1 + 2²*1 + 2¹*1 + 2⁰*1

= 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1

= 111111111


Her bitin 1 olduğu ve sizin de görebileceğiniz gibi daha yüksek bir değeri saklayamazsınız.


Öte yandan, 0 sayısını 8 bitlik kayıtta saklamak istiyorsanız şu şekilde görünecektir:


= 2⁸*0 + 2⁷*0 + 2⁶*0 + 2⁵*0 + 2⁴*0 + 2³*0 + 2²*0 + 2¹*0 + 2⁰*0

= 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0

= 000000000


Her bitin 0 olduğu yer, bu size daha düşük bir değeri saklayamayacağınızı söylemelidir.


Başka bir deyişle, böyle bir 8 bitlik kayıt için izin verilen tamsayı aralığı 0-511'dir. Peki 512 veya -1 tamsayısını böyle bir kayıt defterinde saklamak mümkün müdür?


Tabii ki değil. Sonuç olarak, kilometre sayacı örneğinde katedilen kilometrenin sıfırlama değerine benzeyen bir değeri ikili değerler olarak depolayacaksınız.


Açıkçası, böyle bir sayıyı rahatça barındırabilmek için birkaç bit daha içeren kayıtlara ihtiyacınız olacaktır. Aksi takdirde taşma durumunu bir kez daha riske atabilirsiniz.


İmzalı tamsayılar durumunda, negatif tamsayıları da saklarız. Dolayısıyla, yukarıda gösterildiği gibi kabul edilen aralıktan daha küçük veya sıfırdan daha küçük bir sayıyı depolamaya çalıştığımızda, yetersiz akış meydana gelir.


Yine, herhangi bir hesaplama yapmanın amacı deterministik sonuçlar elde etmek olduğundan, bu en iyi ihtimalle can sıkıcı olabilir, en kötü ihtimalle ise milyonların kaybına neden olabilir. Özellikle akıllı sözleşmelerde bu tam sayı taşması veya yetersiz akış hataları meydana geldiğinde.

Tamsayı Taşması/Düşük Akış Güvenlik Açığı neden bu kadar zarar verici olabilir?

Tamsayı taşması ve az akışı onlarca yıldır ortalıkta olsa da, akıllı sözleşmelerdeki bir hata olarak bunların varlığı riskleri artırdı. Saldırganlar bu tür hatalardan yararlandıklarında akıllı sözleşmeyi büyük miktarlarda token tüketebilirler.


Muhtemelen bu tür bir hata ilk kez üç adres için milyarlarca Bitcoin oluşturan 74638 numaralı blokta meydana geldi. Bu hatayı soft fork ile çözmek saatler alacak ve blok atılacak ve işlem geçersiz hale gelecektir.


Birincisi, değeri 21 milyon Bitcoin'den büyük olan işlemler reddedildi. Yukarıda adı geçen üç hesaba çok fazla para gönderilen işlemlerde olduğu gibi, taşma işlemlerinde de durum farklı değildi.


Bununla birlikte, Ethereum akıllı sözleşmeleri de tamsayı taşması ve az akışı yaşadı; BeautyChain de buna önemli bir örnek.


Bu durumda akıllı sözleşme hatalı bir kod satırı içeriyordu:


Tamsayı Taşmasına neden olan tek kod


Sonuç olarak, saldırganlar teorik olarak sınırsız miktarda BEC tokeni alabildiler ve bu da teorik olarak (2²⁵⁶)-1 değerine karşılık gelebilir.


Güvenlik açığı bulunan BatTransfer işlevi


Şimdi tam sayı taşması/eksikliğinin meydana geldiği başka bir akıllı sözleşme örneğine bakalım.

Akıllı Sözleşmede Tamsayı Taşması/Düşük Akış Güvenlik Açığının Giderilmesi

İlk bakışta, bu örnekte etkileşim halinde olan ve tamsayı taşması durumunda ne olacağını gösteren iki sözleşme vardır.


Aşağıda görebileceğiniz gibi, TimeLock sözleşmesi para yatırmanıza ve çekmenize olanak tanır, ancak bir farkla: ikincisini ancak belirli bir süre sonra gerçekleştirebilirsiniz. Bu durumda paranızı ancak bir hafta içinde çekebilirsiniz.


TimeLock sözleşmesi


Ancak, Saldırı sözleşmesinde saldırı fonksiyonunu çağırdığınızda, uygulanan zaman kilidi artık etkili değildir ve bu nedenle saldırgan, bakiye tutarını anında geri çekebilir.

Yani type(uint).max+1-timeLock.locktime(address(this)) deyimiyle tamsayı taşmasına neden olacağından zaman kilidi ortadan kalkar.


Tamsayı Taşması kullanıldığında para yatırma zaman kilidi anında ortadan kaldırılır


Örneğin, yukarıdaki kodu kullanarak her iki akıllı sözleşmeyi de dağıttığınızda, aşağıda gösterildiği gibi TimeLock sözleşmesindeki para yatırma ve çekme işlevlerini çağırarak zaman kilidinin geçerli olup olmadığını test edebilirsiniz:


2 ETH yatırdıktan sonraki bakiye


Gördüğünüz gibi 2 Ether miktarını seçerek yukarıda gösterilen 2 Ether akıllı sözleşme bakiyesini elde ediyoruz:


2 ETH yatırma


Spesifik olarak, 2 Ether bakiyesini tutan belirli adres, adresi bakiye fonksiyonu alanına ekleyerek ve bakiyeler düğmesine tıklayarak kontrol edilebilir:


Hangi adreste 2 ETH bulunuyor?


Ancak yukarıda da belirtildiği gibi, zaman kilidi nedeniyle bu fonları henüz çekemezsiniz. Para çekme fonksiyonuna bastıktan sonra konsola baktığınızda kırmızı 'x' simgesiyle gösterilen bir hata göreceksiniz. Aşağıda görebileceğiniz gibi bu hatanın nedeni sözleşmede “Kilitleme süresi dolmadı” olarak belirtiliyor:


Kilitleme süresi dolmadı hatası


Şimdi aşağıda gösterildiği gibi konuşlandırılan Saldırı sözleşmesine bakalım:



Şimdi saldırı fonksiyonunu başlatmak için 1 Ether veya daha fazla değer yatırmanız gerekiyor. Bu örnekte aşağıda gösterildiği gibi 2 Ether seçtik:


Önce 2 ETH yatırın!


Bundan sonra 'saldırı'ya basın. Yatırdığınız 2 Ether'in hemen geri çekileceğini ve aşağıdaki 2 Ether bakiyesinin de gösterdiği gibi Saldırı sözleşmesine ekleneceğini göreceksiniz:


Attack akıllı sözleşmesine 2 ETH aktarıldı


Açıkçası, uzun süre kilidinin para yatırdığınız anda devreye girmesi gerektiği için bunun gerçekleşmesi beklenmiyor. Elbette bildiğimiz gibi type(uint).max+1-timeLock.locktime(address(this)) ifadesi, boostLockTime fonksiyonunu kullanarak kilit süresini azaltır. İşte tam da bu yüzden Ether bakiyesini anında çekebiliyoruz.


Bu da bizi şu soruya getiriyor: Tamsayı taşması ve yetersiz akış güvenlik açığını düzeltmenin yolları var mı?

Tamsayı Taşması/Yetersiz Akış Güvenlik Açığı'nı Önlemenin 2 Yolu

Tamsayı taşması/eksikliği güvenlik açığının yıkıcı olabileceği kabul edilerek, bu hataya yönelik birkaç düzeltme kullanıma sunuldu. Hem bu düzeltmelere hem de bu tür bir hatayı nasıl çözdüklerine bakalım:


Yöntem 1: OpenZeppelin'in SafeMath kitaplığını kullanma

Open Zeppelin bir kuruluş olarak siber güvenlik teknolojisi ve hizmetleri söz konusu olduğunda çok şey sunuyor; SafeMath kütüphanesi akıllı sözleşme geliştirme deposunun bir parçası. Bu depo, akıllı sözleşme kodunuza aktarılabilecek sözleşmeleri içerir; SafeMath kütüphanesi de bunlardan biridir.


SafeMath.sol içindeki işlevlerden birinin tam sayı taşmasını nasıl kontrol ettiğini görelim:


tryAdd SafeMath işlevi


Şimdi, a+b hesaplaması yapıldıktan sonra c<a'nın gerçekleşip gerçekleşmediğini kontrol etmek gerekir. Elbette bu yalnızca tamsayı taşması durumunda geçerli olacaktır.


Solidity'nin derleyici sürümünün 0.8.0 ve üstüne ulaşmasıyla, tamsayı taşması ve yetersiz akış kontrolleri artık yerleşiktir. Dolayısıyla, hem dili hem de bu kitaplığı kullanırken bu güvenlik açığını kontrol etmek için bu kitaplığı kullanmaya devam edebilirsiniz. Elbette akıllı sözleşmeniz 0.8.+'dan daha düşük bir derleyici sürümü gerektiriyorsa taşma veya yetersiz akışı önlemek için bu kütüphaneyi kullanmanız gerekir.


Yöntem 2: Derleyicinin 0.8.0 sürümünü kullanın

Şimdi, daha önce de belirttiğimiz gibi, akıllı sözleşmeniz için 0.8.0 ve üzeri bir derleyici sürümü kullanıyorsanız, bu sürümde bu tür bir güvenlik açığına karşı yerleşik bir denetleyici bulunmaktadır.


Hatta yukarıdaki akıllı sözleşmeyle çalışıp çalışmadığını doğrulamak için derleyici sürümünü “^0.8.0” olarak değiştirip yeniden dağıtırken aşağıdaki 'geri alma' hatası alınıyor:


Tamsayı Taşmasını engelleyen geri dönüş hatası


Tabii ki, zaman kilidi değerinin taşmasının kontrolü nedeniyle 2 Ether yatırma işlemi gerçekleştirilmiyor. Sonuç olarak, ilk etapta para yatırılmadığından para çekme işlemi mümkün değildir.


2 ETH'nin Saldırı sözleşmesine aktarılması engellendi


Hiç şüphe yok ki, Attack.attack() işlev çağrısı burada işe yaramadı, yani her şey yolunda!

Taşma/Yetersiz Akış Güvenlik Açığının Özetlenmesi

Bu uzun blog yazısından anlamanız gereken bir şey varsa o da BEC saldırısında olduğu gibi bu güvenlik açığını göz ardı etmenin maliyetli olabileceğidir. Sizin de görebileceğiniz gibi, eğer işaretlenmezse, kötü amaçlı olmayan hataların meydana gelmesi kolaydır. Veya bilgisayar korsanlarının bu güvenlik açığından yararlanması da aynı derecede basittir.


Bundan bahsetmişken ve BEC saldırısının nasıl gerçekleştiğine dair anlayışımızı kullanarak, bu güvenlik açığını tanımak, sunulan düzeltmeler sayesinde akıllı sözleşmelerinizi yazarken herhangi bir saldırıyı önlemede uzun bir yol kat edebilir. Sizi tuzağa düşürmek için bekleyen başka akıllı sözleşme güvenlik açıkları olsa bile.