Analise smart-contract
27.03.2024

Skryté pasti ve hře Honeypot

Chytré smlouvy v Honeypotu jsou jedním z typů podvodných chytrých smluv, které vypadají pro uživatele lákavě a slibují finanční výhody. Takové smlouvy však obsahují skryté pasti nebo škodlivý kód, kvůli kterým uživatelé naopak přicházejí o své finanční prostředky.

Chcete-li se před chytrými smlouvami typu Honeypot chránit, musíte před zapojením do smlouvy její kód důkladně prozkoumat a ověřit. Svět chytrých smluv a blockchainu se neustále vyvíjí a pravidelně se v něm objevují nové zranitelnosti a metody podvodu, proto je pro vývojáře, auditory a uživatele zásadní, aby sledovali nejnovější trendy a vývoj v této oblasti.

Příkladů chytrých smluv typu Honeypot je mnoho. V tomto článku se na některé z nich podíváme.

1. Honeypot s funkcí Fallback

V tomto typu honeypotu má kontrakt zranitelnou funkci "rollback", která zdánlivě umožňuje komukoli vlastnit kontrakt a odstranit jeho zůstatek. Taková funkce však obsahuje skrytou podmínku, která brání úspěšnému provedení.

Příklad kódu:


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

V tomto příkladu umožňuje funkce zpětného převodu, aby se vlastníkem stal libovolný uživatel. Operátor revert() však zajišťuje, že ke změně vlastníka nikdy nedojde, takže útočníkovy prostředky zůstanou uvězněny ve smlouvě.

2. Skrytá podmínka honeypot

Takové smlouvy obsahují skrytou podmínku nebo požadavek, který musí být splněn, aby zdánlivě zranitelná funkce uspěla. Uživatelé si mohou myslet, že smlouvu mohou použít, ale skrytá podmínka zajistí, že ji použít nemohou.

Příklad kódu:


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

V této smlouvě může uživatel vložit 1 ether a zdá se, že po 100 vkladech může vybrat celý zůstatek smlouvy. Podmínka counter > 100 však zajišťuje, že výběr je možný až po 101 vkladech, což zdržuje prostředky v kontraktu.

3. Reentrancy honeypot

V tomto typu honeypotu je kontrakt zranitelný reentrancy útokem, kdy útočník může opakovaně vyvolat funkci kontraktu během výběru. Kontrakt však obsahuje skrytý mechanismus, který úspěšnému zneužití brání.

Příklad kódu:


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

V tomto příkladu je smlouva zranitelná útokem reentrancy kvůli použití funkce msg.sender.call.value(_amount)(). Smlouva však neumožňuje uživatelům vložit více než počáteční zůstatek, čímž efektivně zdržuje jejich finanční prostředky.

reentrancy honeypot

4. Mezní hodnota plynu v medovém hrnci

V tomto typu honeypotu se zdá, že smlouva umožňuje uživateli vybrat finanční prostředky, ale množství plynu potřebné k provedení funkce výběru přesahuje limit plynového bloku, což transakci znemožňuje.

Příklad kódu:


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

V tomto příkladu funkce vklad inicializuje pole s 1000 prvky. Funkce pro výběr převede odesílateli zůstatek smlouvy, ale změní prvky pole. Plyn potřebný k provedení smyčky přesáhne limit bloku plynu, což zabrání výběru a zdrží ho ve smlouvě.

5. Manipulace s časovými razítky

V tomto typu honeypotu smlouva používá časové razítko blockchainu Ethereum jako podmínku pro provedení. Protože však těžaři mohou manipulovat s časovým razítkem bloku, smlouva se stává zranitelnou.

Příklad kódu:


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

V této smlouvě mohou uživatelé vkládat prostředky a vybírat celý zůstatek smlouvy po uplynutí 1 hodiny od poslední interakce. Protože však těžaři mohou manipulovat s časovým razítkem bloku, mohou spustit funkci výběru dříve, než se očekávalo, a tak ve skutečnosti ukrást finanční prostředky.

6. Honeypot se skrytým převodem

V tomto typu honeypotu obsahuje smlouva skrytý převod finančních prostředků na adresu útočníka, který budí zdání legitimní smlouvy, ale ve skutečnosti vede ke ztrátě finančních prostředků.

Příklad kódu:


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

V tomto příkladu se zdá, že smlouva umožňuje vklady a výběry. Funkce vkladu však obsahuje skrytý převod prostředků vlastníkovi (útočníkovi), pokud je částka vkladu rovna nebo větší než 1 ether. Nic netušící uživatelé, kteří vloží prostředky, o své ethery přijdou, protože budou převedeny na adresu útočníka.

7. Reentrancy honeypot

Reentrancy honeypot je založen na využití zranitelnosti reentrancy v kontraktu, kdy může být funkce kontraktu volána rekurzivně před aktualizací jeho stavu.

Příklad kódu:


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

V tomto příkladu smlouva povoluje vklady a výběry. Je však zranitelná vůči útokům na reentranci. Útočník by mohl vytvořit škodlivou smlouvu, která rekurzivně zavolá funkci pro výběr před nastavením zůstatku na nulu, čímž vyčerpá celý zůstatek smlouvy.

8. Typová konverze a přetečení

V tomto typu honeypotu útočník používá typovou konverzi a přetečení celých čísel, aby uživatele oklamal, že smlouva je bezpečná, ale ve skutečnosti obsahuje skryté pasti.

Příklad kódu:


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

V tomto příkladu smlouva odměňuje uživatele, kteří zvyšují proměnnou count, když odešlou částku rovnou nebo vyšší než částka odměny. Vzhledem k použití proměnné uint8 pro proměnnou count však dojde k přetečení celého čísla, když hodnota dosáhne hodnoty 255. Po přetečení se hodnota count vynuluje a útočník může získat celý zůstatek smlouvy.

overflow honeypot

9. Delegatecall honeypot

V tomto typu honeypotu útočník používá funkci delegatecall ke spuštění škodlivého kódu jménem zdánlivě bezpečné smlouvy.

Příklad kódu:


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

V tomto příkladu smlouva umožňuje vklady, výběry a provádění kódu prostřednictvím funkce execute. Útočník však může vytvořit škodlivý kód, který může manipulovat se stavem kontraktu, včetně proměnné vlastníka, provedením funkce delegatecall. Změnou vlastníka může útočník získat kontrolu nad kontraktem a vyprázdnit jeho zůstatek.

10. Skryté úložiště pro manipulaci s honeypotem

V tomto typu honeypotu útočník skrytě manipuluje s proměnnými úložiště, aby uživatele oklamal a převzal kontrolu nad kontraktem.

Příklad kódu:


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

V tomto příkladu vypadá smlouva jako jednoduchá smlouva s tokeny. Uživatelé si mohou navzájem předávat tokeny a vlastník je zpočátku nastaven jako tvůrce kontraktu. Útočník však může skrytě manipulovat s proměnnými totalSupply a balances a získat tak kontrolu nad kontraktem. Jakmile rozdíl mezi totalSupply a balances[owner] dosáhne hodnoty 1000, aktualizuje se proměnná owner, což útočníkovi umožní získat kontrolu nad kontraktem.

11. Název funkce collision honeypot

U tohoto typu honeypotu útočník využívá kolize názvů funkcí, aby uživatele přiměl k zavolání nesprávné funkce.

Příklad kódu:


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

V tomto příkladu má smlouva dvě funkce pro odstoupení od smlouvy. Jedna z nich umožňuje vlastníkovi vybrat celý zůstatek a druhá umožňuje vybrat určitou částku. Pokud se však uživatel pokusí vybrat určitou částku voláním funkce withdraw(uint256), dojde v důsledku kolize názvů funkcí k neúmyslnému volání funkce withdraw() bez argumentů, což povede k výběru celého zůstatku smlouvy.

12. Honeypot se zranitelností Delegatecall

V tomto typu honeypotu útočník použije delegatecall ke spuštění škodlivé funkce v jiné smlouvě, což může vést k odcizení finančních prostředků uživatelů.

Příklad kódu:


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

V tomto příkladu se smlouva zdá být neškodná, s jednoduchou funkcí withdraw(), která umožňuje vlastníkovi vybrat zůstatek smlouvy. Kontrakt však obsahuje také nouzovou funkci, která při přijetí více než 1 etheru provede delegované volání jiného kontraktu pomocí msg.data. Pokud útočník vytvoří škodlivou smlouvu s funkcí withdraw(), která ukradne zůstatek smlouvy a pošle více než 1 ether do smlouvy oběti, delegatecall provede škodlivou funkci withdraw() a ukradne zůstatek smlouvy.

vulnerability honeypot

13. Pseudoceločíselný honeypot přetečení

Tento typ honeypotu je založen na tom, že Solidity nemá vestavěnou podporu čísel s plovoucí desetinnou čárkou. Útočníci mohou vytvořit smlouvy, které navenek používají desetinná čísla, ale ve skutečnosti podvodně manipulují s celými čísly.

Příklad kódu:


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

Tento příklad vyvolává dojem, že smlouva spravuje zůstatek uživatele s desetinnou přesností tím, že hodnoty před uložením vynásobí 100. Tento přístup je však zavádějící, protože Solidity nepodporuje čísla s plovoucí desetinnou čárkou. Když se uživatel pokusí vybrat své prostředky, smlouva nesprávně vypočítá pseudosoučet kvůli zkracování celých čísel a uživatel nemusí být schopen vybrat celý svůj zůstatek.

Chcete-li se chránit před tímto typem honeypotu, musíte pochopit omezení jazyka Solidity a způsob, jakým zpracovává číselné operace. Buďte opatrní při práci se smlouvami, které tvrdí, že podporují desetinnou přesnost, a vždy zkontrolujte kód smlouvy, zda v něm nejsou potenciální problémy.

14. Skryté poplatky v chytrých smlouvách

Některé škodlivé chytré smlouvy mohou uživatelům ukládat skryté platby bez jejich vědomí. Takové platby mohou být v kódu smlouvy skryté nebo mohou být spuštěny pouze za určitých podmínek.

Příklad kódu:


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

V tomto příkladu nastaví tvůrce smlouvy při nasazení smlouvy poplatek (feePercentage). Při interakci se smlouvou nemusí být uživatelé o tomto poplatku informováni. Když uživatel vybere své prostředky, smlouva vypočítá poplatek, odečte jej od částky výběru a zbytek zašle uživateli. Provize je pak převedena vlastníkovi smlouvy.

Abyste se nedostali do této pasti, pečlivě si prostudujte kód smlouvy a ujistěte se, že rozumíte všem souvisejícím poplatkům. Vyhledejte skrytá nebo zastřená vyúčtování.

15. Skrytá manipulace se stavem

V některých případech mohou škodlivé smlouvy umožnit vlastníkovi smlouvy nebo útočníkovi manipulovat s vnitřním stavem smlouvy, což vede k neočekávaným důsledkům pro ostatní uživatele.

Příklad kódu:


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

V tomto příkladu smlouva umožňuje uživatelům vkládat a vybírat finanční prostředky. Funkce manipulateBalance však umožňuje vlastníkovi smlouvy libovolně měnit zůstatek libovolného uživatele. To může vést k neočekávaným ztrátám uživatelů nebo umožnit vlastníkovi smlouvy ukrást finanční prostředky.

Kód smlouvy by měl být pečlivě prozkoumán, zda neobsahuje funkce, které umožňují manipulaci se stavem, zejména pokud k nim má přístup pouze vlastník smlouvy nebo určité adresy.

hidden state manipulation

16. Skrytá krádež tokenů

V některých případech mohou škodlivé smlouvy obsahovat skryté funkce, které umožňují vlastníkovi smlouvy nebo útočníkovi krást tokeny nic netušícím uživatelům.

Příklad kódu:


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

V tomto příkladu smlouva umožňuje uživatelům vkládat a vybírat tokeny ERC20. Funkce stealTokens však umožňuje vlastníkovi smlouvy ukrást tokeny libovolnému uživateli.

Závěr:

Honeypot je jen jedním z mnoha podvodných schémat ve společnosti DeFi, které využívá nejen omezení výběru, ale také různé skryté pasti popsané výše.

Abyste nenaletěli, je třeba pochopit zvláštnosti podvodného schématu, záludnosti programovacího jazyka Solidity a před interakcí s ním důkladně prostudovat chytré smlouvy.

Pokud budete informováni a provedete důkladnou prověrku, můžete minimalizovat rizika spojená s používáním chytrých smluv.

 

Pokud máte pochybnosti o bezpečnosti nebo funkčnosti chytré smlouvy, použijte naši platformu Lotus Market.

 

 

Platforma Lotus Market byla vytvořena týmem zkušených vývojářů a profesionálních auditorů. Cílem projektu je minimalizovat rizika investování do podvodných tokenů a pomoci bezpečně a pohodlně obchodovat s kryptoměnami.

 

S pozdravem, tým Lotus Market.

All posts

Connect to a wallet

Metamask