Analise smart-contract
27.03.2024

Rejtett csapdák Honeypot-ban

A Honeypot okosszerződések a csalárd okosszerződések egyik típusa, amelyek vonzónak tűnnek a felhasználók számára és pénzügyi előnyöket ígérnek. Az ilyen szerződések azonban rejtett csapdákat vagy rosszindulatú kódot tartalmaznak, ami miatt a felhasználók éppen ellenkezőleg, elveszítik a pénzüket.

A Honeypot okosszerződésektől való védekezéshez alaposan fel kell kutatni és ellenőrizni kell a szerződés kódját, mielőtt belemerülne. Az okos szerződések és a blokklánc világa folyamatosan fejlődik, rendszeresen jelennek meg új sebezhetőségek és megtévesztési módszerek, ezért a fejlesztők, ellenőrök és felhasználók számára létfontosságú, hogy naprakészek maradjanak a terület legújabb trendjeivel és fejlesztéseivel kapcsolatban.

A Honeypot okosszerződésekre számos példa van. Ebben a cikkben néhány példát tekintünk át.

1. Fallback funkció honeypot

Egy ilyen típusú honeypotban a szerződés egy sebezhető "rollback" funkcióval rendelkezik, amely látszólag lehetővé teszi, hogy bárki birtokba vehesse a szerződést és eltávolítsa az egyenlegét. Egy ilyen funkció azonban tartalmaz egy rejtett feltételt, amely megakadályozza a sikeres végrehajtást.

Példakód:


pragma solidity 0.4.18

contract FallbackHoneypot {
  address public owner;

  function() public payable {
      if (owner == 0) {
          owner = msg.sender;
      } else {
          revert();
      }
  }

  function withdraw() public {
      require(msg.sender == owner);
      msg.sender.transfer(this.balance);
  }
}

Ebben a példában a fallback funkció lehetővé teszi, hogy bármelyik felhasználó tulajdonos legyen. A revert() operátor azonban biztosítja, hogy a tulajdonosváltás soha nem történik meg, így a támadó pénze csapdában marad a szerződésben.

2. Rejtett feltételes mézesmadzag

Az ilyen szerződések tartalmaznak egy rejtett feltételt vagy követelményt, amelynek teljesülnie kell ahhoz, hogy a látszólag sebezhető funkció sikeres legyen. A felhasználók azt hihetik, hogy használhatják a szerződést, de a rejtett feltétel biztosítja, hogy nem tudják használni.

Példakód:


pragma solidity 0.4.18

contract HiddenConditionHoneypot {
  uint256 public counter = 0;

  function deposit() public payable {
      require(msg.value == 1 ether);
      counter++;
  }

  function withdraw() public {
      require(counter > 100);
      msg.sender.transfer(this.balance);
  }
}

Ebben a szerződésben a felhasználó 1 étert fizethet be, és úgy tűnik, hogy 100 befizetés után a felhasználó kiveheti a teljes szerződés egyenlegét. A feltétel counter > 100 azonban biztosítja, hogy a kifizetés csak 101 befizetés után lehetséges, ami késlelteti a szerződésben lévő pénzeszközöket.

3. Reentrancy honeypot

Egy ilyen típusú honeypotban a szerződés sebezhető a reentrancy támadással szemben, ahol a támadó a kifizetés során ismételten meghívhat egy szerződésfüggvényt. A szerződés azonban tartalmaz egy rejtett mechanizmust, amely megakadályozza a sikeres kihasználást.

Példakód:


pragma solidity 0.4.18

contract ReentrancyHoneypot {
  mapping(address => uint256) public balances;

  function deposit() public payable {
      balances[msg.sender] += msg.value;
  }

  function withdraw(uint256 _amount) public {
      require(balances[msg.sender] >= _amount);
      if (msg.sender.call.value(_amount)()) {
          balances[msg.sender] -= _amount;
      }
  }
}

Ebben a példában a szerződés az msg.sender.call.value(_amount)() használata miatt sebezhető a reentrancia támadással szemben. A szerződés azonban nem engedi meg a felhasználóknak, hogy a kezdeti egyenlegnél többet fizessenek be, így gyakorlatilag késleltetve a pénzüket.

reentrancy honeypot

4. Gázlimites honeypot

Egy ilyen típusú honeypotban a szerződés látszólag lehetővé teszi a felhasználó számára a pénzfelvételt, de a pénzfelvételi funkció végrehajtásához szükséges gázmennyiség meghaladja a gázblokk limitet, így a tranzakció lehetetlenné válik.

Példakód:


pragma solidity 0.4.18

contract GasLimitHoneypot {
  uint256 constant public numArrayElements = 1000;
  uint256[numArrayElements] public someArray;

  function deposit() public payable {
      for (uint256 i = 0; i < numArrayElements; i++) {
          someArray[i] = 0;
      }
  }

  function withdraw() public {
      uint256 balance = address(this).balance;
      for (uint256 i = 0; i < numArrayElements; i++) {
          someArray[i] = 1;
      }
      msg.sender.transfer(balance);
  }
}

Ebben a példában a befizetés függvény egy 1000 elemű tömböt inicializál. A visszavonás függvény átadja a szerződés egyenlegét a feladónak, de módosítja a tömb elemeit. A ciklus végrehajtásához szükséges gáz meghaladja a gázblokk limitet, ami megakadályozza a visszavonást, és késlelteti azt a szerződésben.

5. Időbélyeg manipulációs honeypot

Egy ilyen típusú honeypotban a szerződés az Ethereum blokklánc időbélyegét használja a végrehajtás feltételeként. Mivel azonban a bányászok manipulálhatják a blokk időbélyegét, a szerződés sebezhetővé válik.

Példakód:


pragma solidity 0.4.18

contract TimestampHoneypot {
  uint256 public lastInteraction;
  uint256 public prize;

  function deposit() public payable {
      lastInteraction = now;
      prize += msg.value;
  }

  function withdraw() public {
      require(now >= lastInteraction + 1 hours);
      msg.sender.transfer(prize);
      prize = 0;
  }
}

Ebben a szerződésben a felhasználók az utolsó interakciótól számított 1 óra elteltével befizethetnek pénzt és kivehetik a teljes szerződéses egyenleget. Mivel azonban a bányászok manipulálni tudják a blokk időbélyegét, a vártnál korábban is aktiválhatják a kivonási funkciót, és így ténylegesen ellophatják a pénzeszközöket.

6. Rejtett átutalásos honeypot

Egy ilyen típusú honeypotban a szerződés egy rejtett pénzátutalást tartalmaz a támadó címére, ami egy legitim szerződés látszatát kelti, de a valóságban pénzvesztést eredményez.

Példakód:


pragma solidity 0.4.18

contract HiddenTransferHoneypot {
  address public owner;

  constructor() public {
      owner = msg.sender;
  }

  function deposit() public payable {
      if (msg.value >= 1 ether) {
          owner.transfer(msg.value);
      }
  }

  function withdraw() public {
      require(msg.sender == owner);
      owner.transfer(address(this).balance);
  }
}

Ebben a példában úgy tűnik, hogy a szerződés lehetővé teszi a befizetéseket és a kifizetéseket. A befizetési funkció azonban tartalmaz egy rejtett pénzátutalást a tulajdonos (támadó) számára, ha a befizetés összege egyenlő vagy nagyobb, mint 1 éter. A gyanútlan felhasználók, akik pénzt helyeznek el, elveszítik az étereiket, mivel azok a támadó címére kerülnek át.

7. Reentrancia honeypot

A reentrancia honeypot a szerződés reentrancia sebezhetőségének kihasználásán alapul, ahol egy szerződésfüggvényt rekurzívan lehet hívni, mielőtt az állapota frissülne.

Példakód:


pragma solidity 0.4.18

contract ReentrancyHoneypot {
  mapping(address => uint256) public balances;

  function deposit() public payable {
      balances[msg.sender] += msg.value;
  }

  function withdraw() public {
      uint256 amount = balances[msg.sender];
      (bool success,) = msg.sender.call.value(amount)("");
      require(success, "Withdrawal failed.");
      balances[msg.sender] = 0;
  }
}

Ebben a példában a szerződés engedélyezi a befizetéseket és a kifizetéseket. Azonban sebezhető a reentrancia támadásokkal szemben. Egy támadó létrehozhat egy rosszindulatú szerződést, amely rekurzívan hívja a visszavonás függvényt, mielőtt az egyenleg nullára állna, és ezzel kimeríti a teljes szerződés egyenlegét.

8. Típus-átalakítás és túlcsordulás mézesmadzag

Egy ilyen típusú mézesmadzagban a támadó típusátalakítással és egész számok túlcsordulásával elhiteti a felhasználókkal, hogy a szerződés biztonságos, de valójában rejtett csapdákat tartalmaz.

Példakód:


pragma solidity 0.4.18

contract TypeCastingHoneypot {
  uint8 public count = 0;
  uint256 public reward = 1 ether;

  function increment() public payable {
      require(msg.value >= reward);

      uint8 prevCount = count;
      count++;

      if (count < prevCount) {
          msg.sender.transfer(address(this).balance);
      }
  }
}

Ebben a példában a szerződés azokat a felhasználókat jutalmazza, akik növelik a count változót, amikor a jutalom összegével megegyező vagy annál nagyobb összeget küldenek. Mivel azonban a count változóban uint8 értéket használunk, egész szám túlcsordulás következik be, amikor az érték eléri a 255-öt. A túlcsordulás után a számláló 0-ra áll vissza, és a támadó megszerezheti a szerződés teljes egyenlegét.

overflow honeypot

9. Delegatecall honeypot

Egy ilyen típusú honeypotban a támadó a delegatecall funkciót használja arra, hogy rosszindulatú kódot hajtson végre egy látszólag biztonságos szerződés nevében.

Példakód:


pragma solidity 0.4.18

contract DelegateCallHoneypot {
  address public owner;

  constructor() public {
      owner = msg.sender;
  }

  function() external payable {}

  function withdraw() public {
      require(msg.sender == owner);
      owner.transfer(address(this).balance);
  }

  function execute(address _target, bytes memory _data) public {
      require(msg.sender == owner);
      (bool success,) = _target.delegatecall(_data);
      require(success, "Execution failed.");
  }
}

Ebben a példában a szerződés lehetővé teszi a befizetéseket, a kifizetéseket és a kód végrehajtását az execute függvényen keresztül. Egy támadó azonban a delegatecall függvény végrehajtásával olyan rosszindulatú kódot hozhat létre, amely képes manipulálni a szerződés állapotát, beleértve a tulajdonos változót is. A tulajdonos megváltoztatásával a támadó átveheti az irányítást a szerződés felett, és kiürítheti annak egyenlegét.

10. Rejtett tárolómanipulációs honeypot

A honeypot ezen típusában a támadó burkoltan manipulálja a tárolóváltozókat, hogy becsapja a felhasználókat és átvegye az irányítást a szerződés felett.

Példakód:


pragma solidity 0.4.18

contract HiddenStorageManipulation {
  mapping(address => uint256) public balances;
  uint256 public totalSupply;
  address public owner;

  constructor() public {
      owner = msg.sender;
      balances[owner] = 1000;
      totalSupply = 1000;
  }

  function transfer(address _to, uint256 _value) public {
      require(balances[msg.sender] >= _value);
      balances[msg.sender] -= _value;
      balances[_to] += _value;

      if (totalSupply - balances[owner] >= 1000) {
          owner = _to;
      }
  }
}

Ebben a példában a szerződés úgy néz ki, mint egy egyszerű token szerződés. A felhasználók tokeneket adhatnak át egymásnak, a tulajdonos pedig kezdetben a szerződés létrehozója. Egy támadó azonban titokban manipulálhatja a totalSupply és az egyensúly változókat, hogy visszaszerezze az irányítást a szerződés felett. Amikor a totalSupply és a balances[owner] közötti különbség eléri az 1000-et, a owner változó frissül, és a támadó visszaszerezheti a szerződés feletti ellenőrzést.

11. Funkciónév ütközéses honeypot

A honeypot ezen típusában a támadó a függvénynevek ütközését használja, hogy a felhasználókat rossz függvény meghívására csábítsa.

Példakód:


pragma solidity 0.4.18

contract FunctionNameCollision {
  address public owner;

  constructor() public {
      owner = msg.sender;
  }

  function withdraw() external {
      require(msg.sender == owner);
      msg.sender.transfer(address(this).balance);
  }

  function withdraw(uint256 amount) external {
      require(msg.sender == owner);
      owner.transfer(amount);
  }

  function() external payable {}
}

Ebben a példában a szerződésnek két visszavonási funkciója van. Az egyik lehetővé teszi, hogy a tulajdonos felvegye a teljes egyenleget, a másik pedig, hogy a tulajdonos felvegyen egy bizonyos összeget. Ha azonban a felhasználó a withdraw(uint256) hívásával megpróbál egy bizonyos összeget felvenni, a függvénynév ütközése miatt a felhasználó véletlenül argumentumok nélkül hívja meg a withdraw() függvényt, ami a teljes szerződéses egyenleg felvételét eredményezi.

12. Delegatecall sebezhetőség honeypot

Egy ilyen típusú honeypotban a támadó a delegatecallt használja egy másik szerződésben egy rosszindulatú függvény végrehajtásához, ami a felhasználók pénzének ellopásához vezethet.

Példakód:


pragma solidity 0.4.18

contract DelegatecallVulnerability {
  address public owner;

  constructor() public {
      owner = msg.sender;
  }

  function withdraw() external {
      require(msg.sender == owner);
      msg.sender.transfer(address(this).balance);
  }

  function () external payable {
      if (msg.value > 1 ether) {
          this.delegatecall(msg.data);
      }
  }
}

contract Malicious {
  function withdraw() external {
      msg.sender.transfer(address(this).balance);
  }
}  

Ebben a példában a szerződés ártalmatlannak tűnik, egy egyszerű withdraw() függvénnyel, amely lehetővé teszi a tulajdonos számára, hogy kivegye a szerződés egyenlegét. A szerződés azonban tartalmaz egy fallback függvényt is, amely 1-nél több éter átvételekor egy másik szerződés delegatecallját hajtja végre msg.data használatával. Ha egy támadó olyan rosszindulatú szerződést hoz létre a withdraw() funkcióval, amely ellopja a szerződés egyenlegét, és 1-nél több étert küld az áldozat szerződésének, a delegatecall végrehajtja a rosszindulatú withdraw() funkciót, ellopva a szerződés egyenlegét.

vulnerability honeypot

13. Pszeudo-integrálszámok túlcsordulása honeypot

A honeypot ezen típusa azon alapul, hogy a Solidity nem rendelkezik beépített támogatással a lebegőpontos számokhoz. A támadók olyan szerződéseket hozhatnak létre, amelyek külsőleg tizedes számokat használnak, de valójában egész számokat manipulálnak csalárd módon.

Példakód:


pragma solidity 0.4.18

contract PseudoIntegersOverflow {
  mapping(address => uint256) public balances;

  function deposit() external payable {
      require(msg.value > 0);
      uint256 pseudoValue = msg.value * 100;
      balances[msg.sender] += pseudoValue;
  }

  function withdraw(uint256 amount) external {
      uint256 pseudoAmount = amount * 100;
      require(balances[msg.sender] >= pseudoAmount);
      balances[msg.sender] -= pseudoAmount;
      msg.sender.transfer(amount);
  }
}

Ez a példa azt a benyomást kelti, hogy a szerződés tizedes pontossággal kezeli a felhasználó egyenlegét azáltal, hogy az értékeket 100-zal szorozza meg, mielőtt elmenti azokat. Ez a megközelítés azonban félrevezető, mivel a Solidity nem támogatja a lebegőpontos számokat. Amikor a felhasználó megpróbálja felvenni a pénzét, a szerződés az egész számok csonkítása miatt helytelenül számítja ki az álösszeget, és előfordulhat, hogy a felhasználó nem tudja felvenni a teljes egyenlegét.

Az ilyen típusú mézesmadzagok elleni védelemhez meg kell értenie a Solidity nyelv korlátait és azt, hogy hogyan kezeli a numerikus műveleteket. Legyen óvatos, amikor olyan szerződésekkel dolgozik, amelyek azt állítják, hogy támogatják a tizedes pontosságot, és mindig ellenőrizze a szerződés kódját a lehetséges problémák szempontjából.

14. Rejtett díjak az okosszerződésekben

Egyes rosszindulatú okosszerződések tudtuk nélkül rejtett fizetéseket róhatnak ki a felhasználókra. Az ilyen kifizetéseket a szerződés kódjában elrejthetik, vagy csak bizonyos feltételek mellett válthatják ki.

Példakód:


pragma solidity 0.4.18

contract HiddenFees {
  address public owner;
  uint256 public feePercentage;

  constructor(uint256 _feePercentage) public {
      owner = msg.sender;
      feePercentage = _feePercentage;
  }

  function withdraw(uint256 amount) external {
      uint256 fee = amount * feePercentage / 100;
      uint256 netAmount = amount - fee;
      require(address(this).balance >= netAmount);
      msg.sender.transfer(netAmount);
      owner.transfer(fee);
  }
}

Ebben a példában a szerződés létrehozója a szerződés telepítésekor beállít egy díjat (feePercentage). A szerződéssel való interakció során a felhasználók nem biztos, hogy tudnak erről a díjról. Amikor a felhasználó felveszi a pénzét, a szerződés kiszámítja a díjat, levonja a felvett összegből, és a fennmaradó összeget elküldi a felhasználónak. A jutalékot ezután a szerződés tulajdonosa kapja meg.

Azért, hogy ne essen ebbe a csapdába, alaposan nézze át a szerződés kódját, és győződjön meg róla, hogy megértette az összes kapcsolódó díjat. Keresse a rejtett vagy álcázott elszámolásokat.

15. Rejtett állapotmanipuláció

A rosszindulatú szerződések bizonyos esetekben lehetővé tehetik a szerződés tulajdonosának vagy egy támadónak, hogy manipulálja a szerződés belső állapotát, ami váratlan következményekkel jár a többi felhasználó számára.

Példakód:


pragma solidity 0.4.18

contract HiddenStateManipulation {
  address public owner;
  mapping(address => uint256) public balances;

  constructor() public {
      owner = msg.sender;
  }

  function deposit() external payable {
      balances[msg.sender] += msg.value;
  }

  function withdraw(uint256 amount) external {
      require(balances[msg.sender] >= amount);
      msg.sender.transfer(amount);
      balances[msg.sender] -= amount;
  }

  function manipulateBalance(address user, uint256 newBalance) external {
      require(msg.sender == owner);
      balances[user] = newBalance;
  }
}

Ebben a példában a szerződés lehetővé teszi a felhasználók számára, hogy pénzt fizessenek be és vegyenek ki. A manipulateBalance függvény azonban lehetővé teszi a szerződés tulajdonosának, hogy tetszőlegesen megváltoztassa bármelyik felhasználó egyenlegét. Ez váratlan veszteségeket okozhat a felhasználóknak, vagy lehetővé teheti a szerződés tulajdonosának, hogy pénzt lopjon.

A szerződés kódját alaposan meg kell vizsgálni az állapotmanipulációt lehetővé tevő funkciók szempontjából, különösen, ha csak a szerződés tulajdonosa vagy bizonyos címek férnek hozzá.

hidden state manipulation

16. Rejtett tokenlopás

A rosszindulatú szerződések bizonyos esetekben olyan rejtett funkciókat tartalmazhatnak, amelyek lehetővé teszik a szerződés tulajdonosának vagy a támadónak, hogy tokeneket lopjon a gyanútlan felhasználóktól.

Példakód:


pragma solidity 0.4.18

contract HiddenTokenStealing {
  address public owner;
  mapping(address => uint256) public balances;

  constructor() public {
      owner = msg.sender;
  }

  function deposit(IERC20 token, uint256 amount) external {
      require(token.transferFrom(msg.sender, address(this), amount));
      balances[msg.sender] += amount;
  }

  function withdraw(IERC20 token, uint256 amount) external {
      require(balances[msg.sender] >= amount);
      require(token.transfer(msg.sender, amount));
      balances[msg.sender] -= amount;
  }

  function stealTokens(IERC20 token, address user, uint256 amount) external {
      require(msg.sender == owner);
      require(token.transfer(owner, amount));
      balances[user] -= amount;
  }
}

Ebben a példában a szerződés lehetővé teszi a felhasználók számára az ERC20 tokenek befizetését és kivonását. A stealTokens függvény azonban lehetővé teszi a szerződés tulajdonosának, hogy bármelayik felhasználótól ellopjon tokeneket.

Következtetés:

A Honeypot csak egy a DeFi számos csalárd sémája közül, amely nemcsak a visszavonási korlátozást, hanem a fent leírt különböző rejtett csapdákat is kihasználja.

Hogy ne dőljön be a csalinak, meg kell értenie a csalárd séma sajátosságait, a Solidity programozási nyelv finomságait és alaposan tanulmányoznia kell az okosszerződéseket, mielőtt kapcsolatba lépne velük.

Azzal, hogy tájékozott marad és alapos átvilágítást végez, minimálisra csökkentheti az okosszerződések használatával járó kockázatokat.

 

Ha kétségei vannak az okosszerződések biztonságával vagy funkcionalitásával kapcsolatban, használja a Lotus Market platformot.

 

 

A Lotus Market platformot tapasztalt fejlesztőkből és professzionális auditorokból álló csapat hozta létre. A projekt célja, hogy minimalizálja a csalárd tokenekbe való befektetés kockázatát, és segítsen a kriptovalutákkal való biztonságos és kényelmes kereskedésben.

 

Regards, Lotus Market team.

All posts

Connect to a wallet

Metamask