Analise smart-contract
27.03.2024

Honeypot'i varjatud lõksud

Honeypoti nutilepingud on üks petturlike nutilepingute tüüp, mis näevad kasutajate jaoks atraktiivsed välja ja lubavad rahalist kasu. Sellised lepingud sisaldavad aga varjatud lõkse või pahatahtlikku koodi, mille tõttu kasutajad kaotavad oma raha vastupidi.

Selle eest Honeypot-intellektiga lepingute eest kaitsmiseks tuleb enne lepingukoodi põhjalikult uurida ja kontrollida, enne kui sellega tegelema hakkate. Arukate lepingute ja plokiahela maailm areneb pidevalt, regulaarselt ilmuvad uued haavatavused ja pettusmeetodid, mistõttu on äärmiselt oluline, et arendajad, audiitorid ja kasutajad oleksid kursis viimaste suundumuste ja arengutega selles valdkonnas.

Honeypot-arukate lepingute kohta on palju näiteid. Selles artiklis vaatleme mõningaid näiteid.

1. Fallback-funktsiooniga honeypot

Sellise honeypoti puhul on lepingul haavatav "rollback" funktsioon, mis näiliselt võimaldab kellelgi lepingut omada ja selle saldo eemaldada. Selline funktsioon sisaldab aga varjatud tingimust, mis takistab edukat täitmist.

Näidiskood:


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);
  }
}

Selles näites võimaldab taganemisfunktsioon igal kasutajal omanikuks saada. Operaator revert() tagab aga, et omanikuvahetust ei toimu kunagi, jättes ründaja raha lepingusse lõksu.

2. Varjatud tingimus honeypot

Sellised lepingud sisaldavad varjatud tingimust või nõuet, mis peab olema täidetud, et näiliselt haavatav funktsioon õnnestuks. Kasutajad võivad arvata, et nad saavad lepingut kasutada, kuid varjatud tingimus tagab, et nad ei saa seda teha.

Näidiskood:


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);
  }
}

Selles lepingus saab kasutaja hoiustada 1 eeter ja tundub, et pärast 100 hoiustamist saab kasutaja kogu lepingu saldo välja võtta. Kuid tingimus counter > 100 tagab, et väljavõtmine on võimalik alles pärast 101 sissemakset, mis viivitab lepingus olevaid vahendeid.

3. Reentrancy honeypot

Sellises honeypotis on leping haavatav reentrancy rünnakule, kus ründaja saab korduvalt kutsuda lepingu funktsiooni väljavõtmise ajal. Leping sisaldab aga varjatud mehhanismi, mis takistab edukat ärakasutamist.

Näidiskood:


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;
      }
  }
}

Ses näites on leping haavatav reentrancy rünnaku suhtes, kuna kasutatakse msg.sender.call.value(_amount)(). Leping ei luba kasutajatel siiski hoiustada rohkem kui algsaldot, mistõttu nende rahalised vahendid on tõhusalt edasi lükatud.

reentrancy honeypot

4. Gas limit honeypot

Sellise honeypoti puhul tundub, et leping lubab kasutajal raha välja võtta, kuid väljavõtmisfunktsiooni täitmiseks vajalik gaasikogus ületab gaasibloki limiiti, mistõttu tehing on võimatu.

Näidiskood:


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);
  }
}

Ses näites initsialiseerib hoiuse funktsioon 1000 elemendiga massiivi. Välja võtmise funktsioon kannab lepingu saldo saatjale üle, kuid muudab massiivi elemente. Ringi täitmiseks vajalik gaas ületab gaasibloki piiri, mis takistab väljavõtmist ja lükkab selle lepingus edasi.

5. Ajatempliga manipuleerimise mesilane

Sellises mesilases kasutab leping Ethereumi plokiahela ajatemplit täitmise tingimusena. Kuna kaevandajad saavad aga manipuleerida ploki ajatemplit, muutub leping haavatavaks.

Näidiskood:


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;
  }
}

Selle lepingu puhul saavad kasutajad raha sisse maksta ja kogu lepingu saldo välja võtta 1 tunni möödumisel viimasest interaktsioonist. Kuna kaevurid saavad aga manipuleerida blokeerimise ajatemplit, võivad nad väljavõtmisfunktsiooni käivitada oodatust varem ja seega tegelikult raha varastada.

6. Hidden transfer honeypot

Sellises honeypot'is sisaldab leping varjatud rahaülekannet ründaja aadressile, mis annab seadusliku lepingu mulje, kuid tegelikult toob kaasa raha kaotuse.

Näidiskood:


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);
  }
}

Selles näites tundub, et leping lubab sissemakseid ja väljavõtteid. Kuid deposiitfunktsioon sisaldab varjatud raha ülekandmist omanikule (ründaja), kui deposiidisumma on võrdne või suurem kui 1 eeter. Aimamatu kasutaja, kes hoiustab raha, kaotab oma eetrid, sest need kantakse üle ründaja aadressile.

7. Reentrancy honeypot

Reentrancy honeypot põhineb lepingu reentrancy haavatavuse ärakasutamisel, kus lepingu funktsiooni saab rekursiivselt kutsuda enne selle oleku uuendamist.

Näidiskood:


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;
  }
}

Ses näites lubab leping sissemakseid ja väljavõtteid. See on aga haavatav reentrancy-rünnakute suhtes. Ründaja võib luua pahatahtliku lepingu, mis kutsub rekursiivselt funktsiooni tagasivõtmine, enne kui saldo on nulliks seatud, tühjendades kogu lepingu saldo.

8. Tüübivahetuse ja ülevoolu honeypot

Sellises honeypotis kasutab ründaja tüübikonversiooni ja täisarvu ülevoolu, et petta kasutajaid uskuma, et leping on ohutu, kuid tegelikult sisaldab see varjatud lõkse.

Näidiskood:


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);
      }
  }
}

Ses näites premeerib leping kasutajaid, kes suurendavad muutujat count, kui nad saadavad summa, mis on võrdne või suurem kui preemiasumma. Kuna aga count-muutuja jaoks kasutatakse uint8, tekib täisarvu ülevool, kui väärtus jõuab 255-ni. Pärast ülevoolu nullib loenduri väärtus 0 ja ründaja saab kogu lepingu saldo kätte.

overflow honeypot

9. Delegatecall honeypot

Sellise honeypoti puhul kasutab ründaja delegatecall-funktsiooni, et täita näiliselt turvalise lepingu nimel pahatahtlikku koodi.

Näidiskood:


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.");
  }
}

Ses näites võimaldab leping sissemakseid, väljavõtteid ja koodi täitmist funktsiooni execute kaudu. Ründaja saab aga luua pahatahtliku koodi, mis suudab delegecall-funktsiooni täitmisega manipuleerida lepingu olekut, sealhulgas omaniku muutujat. Omaniku muutmisega saab ründaja saada kontrolli lepingu üle ja tühjendada selle saldo.

10. Varjatud salvestusruumiga manipuleerimise honeypot

Sellise honeypoti puhul manipuleerib ründaja varjatud viisil salvestusruumi muutujaid, et petta kasutajaid lepingu üle kontrolli alla võtta.

Näidiskood:


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;
      }
  }
}

Selles näites näeb leping välja nagu lihtne sümboolne leping. Kasutajad saavad üksteisele tokenid üle anda ja omanik on algselt määratud lepingu loojaks. Ründaja saab aga salaja manipuleerida muutujaid totalSupply ja balansi, et saada tagasi kontroll lepingu üle. Kui totalSupply ja balances[owner] vahe jõuab 1000ni, uuendatakse muutujat owner, mis võimaldab ründajal lepingu üle kontrolli tagasi saada.

11. Funktsioonide nimede kokkupõrge honeypot

Sellises honeypotis kasutab ründaja funktsioonide nimede kokkupõrkeid, et petta kasutajaid valet funktsiooni kutsuma.

Näidiskood:


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 {}
}

Ses näites on lepingul kaks tagasivõtmisfunktsiooni. Üks neist võimaldab omanikul välja võtta kogu saldo ja teine võimaldab omanikul välja võtta teatud summa. Kui kasutaja üritab aga teatud summa välja võtta, kutsudes funktsiooni withdraw(uint256), siis funktsiooni nimede kokkupõrke tõttu kutsub kasutaja kogemata funktsiooni withdraw() ilma argumentideta, mille tulemusel võetakse välja kogu lepingu saldo.

12. Delegatecall haavatavus honeypot

Sellises honeypotis kasutab ründaja delegatecall'i, et käivitada pahatahtlik funktsioon teises lepingus, mis võib viia kasutajate raha vargusele.

Näidiskood:


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);
  }
}

Se näites tundub leping süütu, millel on lihtne funktsioon withdraw(), mis võimaldab omanikul lepingu saldot välja võtta. Kuid leping sisaldab ka tagavarafunktsiooni, mis rohkem kui 1 eetri saamisel täidab teise lepingu delegeerimiskutse, kasutades msg.data. Kui ründaja loob pahatahtliku lepingu funktsiooniga withdraw(), mis varastab lepingu saldo ja saadab ohvrile lepingule rohkem kui 1 eeter, täidab delegatecall pahatahtliku funktsiooni withdraw(), varastades lepingu saldo.

vulnerability honeypot

13. Pseudo-integreerivate arvude ülevoolu honeypot

Sellise honeypoti aluseks on asjaolu, et Solidityl ei ole sisseehitatud toetust ujukomaarvudele. Ründajad saavad luua lepinguid, mis väliselt kasutavad kümnendarvu, kuid tegelikult manipuleerivad pettuse teel täisarvudega.

Näidiskood:


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);
  }
}

See näide jätab mulje, et leping haldab kasutaja saldot kümnendiku täpsusega, korrutades väärtused enne salvestamist 100-ga. Selline lähenemine on aga eksitav, sest Solidity ei toeta ujukomaarvusid. Kui kasutaja üritab oma raha välja võtta, arvutab leping täisarvude kärpimise tõttu pseudosummat valesti ja kasutaja ei pruugi kogu oma saldot välja võtta.

Selle tüübi mesilaste vastu kaitsmiseks tuleb mõista Solidity keele piiranguid ja seda, kuidas see numbrilisi operatsioone käitleb. Olge ettevaatlik, kui töötate lepingutega, mis väidavad, et toetavad kümnendtäpsust, ja kontrollige alati lepingu koodi võimalike probleemide suhtes.

14. Varjatud tasud nutilepingutes

Mõni pahatahtlik nutileping võib kasutajatele nende teadmata varjatud makseid kehtestada. Sellised maksed võivad olla lepingukoodis varjatud või käivituvad ainult teatud tingimustel.

Näidiskood:


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);
  }
}

Ses näites määrab lepingu looja lepingu kasutuselevõtmisel tasu (feePercentage). Lepinguga suheldes ei pruugi kasutajad sellest tasust teadlikud olla. Kui kasutaja võtab oma raha välja, arvutab leping tasu, lahutab selle väljavõtte summast ja saadab ülejäänud summa kasutajale. Seejärel kantakse komisjonitasu üle lepingu omanikule.

Vältimaks sellesse lõksu sattumist, vaadake hoolikalt läbi lepingu kood ja veenduge, et te mõistate kõiki sellega seotud tasusid. Otsige varjatud või varjatud arveldusi.

15. Varjatud olekuga manipuleerimine

Mõnel juhul võivad pahatahtlikud lepingud võimaldada lepingu omanikul või ründajal manipuleerida lepingu sisemist olekut, mis toob kaasa ootamatuid tagajärgi teistele kasutajatele.

Näidiskood:


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;
  }
}

Ses näites võimaldab leping kasutajatel raha sisse maksta ja välja võtta. Funktsioon manipulateBalance võimaldab aga lepingu omanikul suvaliselt muuta suvalise kasutaja saldot. See võib põhjustada kasutajatele ootamatuid kahjusid või võimaldada lepingu omanikul raha varastada.

Lepingu koodi tuleks kontrollida, et leida funktsioone, mis võimaldavad olekuga manipuleerimist, eriti kui ainult lepingu omanikul või teatud aadressidel on neile juurdepääs.

hidden state manipulation

16. Varjatud tokenite varastamine

Mõnel juhul võivad pahatahtlikud lepingud sisaldada varjatud funktsioone, mis võimaldavad lepingu omanikul või ründajal varastada tokenid pahaaimamatutelt kasutajatelt.

Näidiskood:


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;
  }
}

Ses näites võimaldab leping kasutajatel ERC20-märkide hoiustamist ja väljavõtmist. Funktsioon stealTokens võimaldab aga lepingu omanikul varastada tokenid ükskõik milliselt kasutajalt.

Kokkuvõte:

Honeypot on vaid üks paljudest DeFi pettuse skeemidest, mis kasutab lisaks väljavõtmispiirangule ka erinevaid eespool kirjeldatud varjatud lõkse.

Kui ei langeks sööta, tuleb mõista pettuse skeemi eripärasid, Solidity programmeerimiskeele peensusi ja uurida põhjalikult nutilepinguid enne nendega suhtlemist.

Informeerituse ja põhjaliku hoolsuskohustuse täitmise abil saate minimeerida nutilepingute kasutamisega seotud riske.

 

Kui teil on kahtlusi nutilepingu turvalisuse või funktsionaalsuse osas, kasutage meie Lotus Market platvormi.

 

 

Lotus Market platvormi on loonud kogenud arendajate ja professionaalsete audiitorite meeskond. Projekti eesmärk on minimeerida petturlike tokenitesse investeerimise riske ning aidata krüptovaluutadega turvaliselt ja mugavalt kaubelda.

 

Regards, Lotus Market team.

All posts

Connect to a wallet

Metamask