Analise smart-contract
27.03.2024

Ukryte pułapki w Honeypot

Smart kontrakty Honeypot to jeden z rodzajów oszukańczych inteligentnych kontraktów, które wyglądają atrakcyjnie dla użytkowników i obiecują korzyści finansowe. Jednak takie kontrakty zawierają ukryte pułapki lub złośliwy kod, powodując, że użytkownicy tracą swoje fundusze.

Aby uchronić się przed inteligentnymi kontraktami Honeypot, należy dokładnie zbadać i zweryfikować kod kontraktu przed zaangażowaniem się w niego. Świat inteligentnych kontraktów i blockchain stale ewoluuje, a nowe podatności i metody oszustw pojawiają się regularnie, dlatego tak ważne jest, aby programiści, audytorzy i użytkownicy byli na bieżąco z najnowszymi trendami i osiągnięciami w tej dziedzinie.

Istnieje wiele przykładów inteligentnych kontraktów Honeypot. W tym artykule przyjrzymy się niektórym z nich.

1. Honeypot z funkcją awaryjną

W tego typu honeypotach kontrakt posiada podatną na ataki funkcję "rollback", która pozornie pozwala każdemu na posiadanie kontraktu i usunięcie jego salda. Jednak taka funkcja zawiera ukryty warunek, który uniemożliwia pomyślne wykonanie.

Przykładowy kod:


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

W tym przykładzie funkcja awaryjna pozwala każdemu użytkownikowi stać się właścicielem. Jednak operator revert() zapewnia, że zmiana właściciela nigdy nie nastąpi, pozostawiając środki atakującego uwięzione w kontrakcie.

2. Ukryty warunek honeypot

Takie kontrakty zawierają ukryty warunek lub wymóg, który musi zostać spełniony, aby pozornie podatna funkcja zakończyła się sukcesem. Użytkownicy mogą myśleć, że mogą skorzystać z kontraktu, ale ukryty warunek gwarantuje, że nie mogą.

Przykładowy kod:


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

W tym kontrakcie użytkownik może zdeponować 1 ether i wydawałoby się, że po 100 depozytach użytkownik może wypłacić całe saldo kontraktu. Jednak warunek counter > 100 zapewnia, że wypłata jest możliwa dopiero po 101 wpłatach, co opóźnia środki w kontrakcie.

3. Reentrancy honeypot

W tym typie honeypota kontrakt jest podatny na atak reentrancy, w którym atakujący może wielokrotnie wywoływać funkcję kontraktu podczas wypłaty. Kontrakt zawiera jednak ukryty mechanizm, który zapobiega skutecznemu wykorzystaniu.

Przykładowy kod:


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

W tym przykładzie umowa jest podatna na atak reentrancy z powodu użycia msg.sender.call.value(_amount)(). Jednakże, umowa nie pozwala użytkownikom na wpłatę więcej niż początkowe saldo, skutecznie opóźniając ich fundusze.

reentrancy honeypot

4. Honeypot z limitem gazu

W tym typie honeypota, kontrakt wydaje się zezwalać użytkownikowi na wypłatę środków, ale ilość gazu wymagana do wykonania funkcji wypłaty przekracza limit bloku gazu, uniemożliwiając transakcję.

Przykładowy kod:


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

W tym przykładzie funkcja wpłaty inicjalizuje tablicę zawierającą 1000 elementów. Funkcja withdraw przekazuje saldo kontraktu do nadawcy, ale modyfikuje elementy tablicy. Gaz wymagany do wykonania pętli przekracza limit blokady gazu, co uniemożliwia wypłatę i opóźnia ją w kontrakcie.

5. Honeypot manipulujący znacznikiem czasu

W tym typie honeypota kontrakt wykorzystuje znacznik czasu blockchaina Ethereum jako warunek wykonania. Ponieważ jednak górnicy mogą manipulować znacznikiem czasu bloku, umowa staje się podatna na ataki.

Przykładowy kod:


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

W tym kontrakcie użytkownicy mogą wpłacać środki i wypłacać całe saldo kontraktu po 1 godzinie od ostatniej interakcji. Ponieważ jednak górnicy mogą manipulować znacznikiem czasu bloku, mogą uruchomić funkcję wypłaty wcześniej niż oczekiwano, a tym samym faktycznie ukraść środki.

6. honeypot z ukrytym przelewem

W tym typie honeypota kontrakt zawiera ukryty przelew środków na adres atakującego, co sprawia wrażenie legalnego kontraktu, ale w rzeczywistości skutkuje utratą środków.

Przykładowy kod:


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

W tym przykładzie umowa wydaje się zezwalać na wpłaty i wypłaty. Jednak funkcja depozytu zawiera ukryty transfer środków do właściciela (atakującego), jeśli kwota depozytu jest równa lub większa niż 1 ether. Nieświadomi użytkownicy, którzy zdeponują środki, stracą swoje etery, ponieważ zostaną one przesłane na adres atakującego.

7. Reentrancy honeypot

Reentrancy honeypot opiera się na wykorzystaniu luki reentrancy w kontrakcie, gdzie funkcja kontraktu może być wywoływana rekurencyjnie, zanim jej stan zostanie zaktualizowany.

Przykładowy kod:


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

W tym przykładzie umowa autoryzuje wpłaty i wypłaty. Jest ona jednak podatna na ataki typu reentrancy. Atakujący może stworzyć złośliwy kontrakt, który rekurencyjnie wywołuje funkcję wypłaty, zanim saldo zostanie ustawione na zero, wyczerpując całe saldo kontraktu.

8. Type casting and overflow honeypot

W tym typie honeypota atakujący wykorzystuje konwersję typów i przepełnienia liczb całkowitych, aby oszukać użytkowników, że kontrakt jest bezpieczny, ale w rzeczywistości zawiera ukryte pułapki.

Przykładowy kod:


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

W tym przykładzie umowa nagradza użytkowników, którzy zwiększają zmienną count, gdy wysyłają kwotę równą lub większą niż kwota nagrody. Jednak ze względu na użycie uint8 dla zmiennej count, przepełnienie liczby całkowitej występuje, gdy wartość osiągnie 255. Po przepełnieniu licznik jest resetowany do 0, a atakujący może uzyskać całe saldo kontraktu.

overflow honeypot

9. Delegatecall honeypot

W tym typie honeypota atakujący używa funkcji delegatecall do wykonania złośliwego kodu w imieniu pozornie bezpiecznego kontraktu.

Przykładowy kod:


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

W tym przykładzie kontrakt pozwala na wpłaty, wypłaty i wykonywanie kodu poprzez funkcję execute. Atakujący może jednak utworzyć złośliwy kod, który może manipulować stanem kontraktu, w tym zmienną właściciela, poprzez wykonanie funkcji delegatecall. Zmieniając właściciela, atakujący może przejąć kontrolę nad kontraktem i opróżnić jego saldo.

10. Ukryta manipulacja pamięcią masową - honeypot

W tym typie honeypota atakujący potajemnie manipuluje zmiennymi pamięci masowej, aby nakłonić użytkowników do przejęcia kontroli nad kontraktem.

Przykładowy kod:


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

W tym przykładzie kontrakt wygląda jak prosty kontrakt tokenowy. Użytkownicy mogą przekazywać sobie tokeny, a właściciel jest początkowo ustawiony jako twórca kontraktu. Atakujący może jednak potajemnie manipulować zmiennymi totalSupply i saldami, aby odzyskać kontrolę nad kontraktem. Gdy różnica między zmiennymi totalSupply i balances[owner] osiągnie 1000, zmienna owner jest aktualizowana, umożliwiając atakującemu odzyskanie kontroli nad kontraktem.

11. Honeypot wykorzystujący kolizję nazw funkcji

W tym typie honeypota atakujący wykorzystuje kolizję nazw funkcji, aby nakłonić użytkowników do wywołania niewłaściwej funkcji.

Przykładowy kod:


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

W tym przykładzie umowa ma dwie funkcje wypłaty. Jedna z nich pozwala właścicielowi wypłacić całe saldo, a druga pozwala właścicielowi wypłacić określoną kwotę. Jeśli jednak użytkownik spróbuje wypłacić określoną kwotę, wywołując funkcję withdraw(uint256), z powodu kolizji nazw funkcji użytkownik nieumyślnie wywoła funkcję withdraw() bez argumentów, co spowoduje wypłatę całego salda kontraktu.

12. Honeypot podatności na delegatecall

W tym typie honeypota atakujący używa delegatecall do wykonania złośliwej funkcji w innym kontrakcie, co może prowadzić do kradzieży środków od użytkowników.

Przykładowy kod:


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

W tym przykładzie kontrakt wydaje się niewinny, z prostą funkcją withdraw(), która pozwala właścicielowi wycofać saldo kontraktu. Kontrakt zawiera jednak również funkcję awaryjną, która po otrzymaniu więcej niż 1 eteru wykonuje wywołanie delegata innego kontraktu przy użyciu msg.data. Jeśli atakujący utworzy złośliwy kontrakt z funkcją withdraw(), która kradnie saldo kontraktu i wysyła więcej niż 1 ether do kontraktu ofiary, delegatecall wykona złośliwą funkcję withdraw(), kradnąc saldo kontraktu.

vulnerability honeypot

13. Pseudo-integers overflow honeypot

Ten typ honeypota bazuje na fakcie, że Solidity nie posiada wbudowanej obsługi liczb zmiennoprzecinkowych. Atakujący mogą tworzyć kontrakty, które pozornie używają liczb dziesiętnych, ale w rzeczywistości manipulują liczbami całkowitymi w nieuczciwy sposób.

Przykładowy kod:


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

Przykład ten sprawia wrażenie, że kontrakt zarządza saldem użytkownika z dziesiętną precyzją, mnożąc wartości przez 100 przed ich zapisaniem. Takie podejście jest jednak mylące, ponieważ Solidity nie obsługuje liczb zmiennoprzecinkowych. Gdy użytkownik próbuje wypłacić swoje środki, umowa nieprawidłowo oblicza pseudo-sumę z powodu obcinania liczb całkowitych, a użytkownik może nie być w stanie wypłacić całego salda.

Aby chronić się przed tego typu honeypotami, należy zrozumieć ograniczenia języka Solidity i sposób, w jaki obsługuje on operacje numeryczne. Zachowaj ostrożność podczas pracy z kontraktami, które twierdzą, że obsługują precyzję dziesiętną i zawsze sprawdzaj kod kontraktu pod kątem potencjalnych problemów.

14. Ukryte opłaty w inteligentnych kontraktach

Niektóre złośliwe inteligentne kontrakty mogą nakładać ukryte płatności na użytkowników bez ich wiedzy. Takie płatności mogą być ukryte w kodzie kontraktu lub uruchamiane tylko w określonych warunkach.

Przykładowy kod:


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

W tym przykładzie twórca kontraktu ustawia opłatę (feePercentage) podczas wdrażania kontraktu. Podczas interakcji z umową użytkownicy mogą nie być świadomi tej opłaty. Gdy użytkownik wypłaca swoje środki, umowa oblicza opłatę, odejmuje ją od kwoty wypłaty i wysyła pozostałą część do użytkownika. Prowizja jest następnie przekazywana właścicielowi umowy.

Aby uniknąć wpadnięcia w tę pułapkę, należy dokładnie zapoznać się z kodem umowy i upewnić się, że rozumie się wszystkie powiązane opłaty. Szukaj ukrytych lub zamaskowanych rozliczeń.

15. Ukryta manipulacja stanem

W niektórych przypadkach złośliwe kontrakty mogą pozwolić właścicielowi kontraktu lub atakującemu na manipulowanie wewnętrznym stanem kontraktu, prowadząc do nieoczekiwanych konsekwencji dla innych użytkowników.

Przykładowy kod:


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

W tym przykładzie kontrakt pozwala użytkownikom na wpłacanie i wypłacanie środków. Jednak funkcja manipulateBalance pozwala właścicielowi kontraktu na arbitralną zmianę salda dowolnego użytkownika. Może to spowodować nieoczekiwane straty dla użytkowników lub pozwolić właścicielowi kontraktu na kradzież środków.

Kod kontraktu powinien być przeanalizowany pod kątem funkcji umożliwiających manipulację stanem, zwłaszcza jeśli tylko właściciel kontraktu lub określone adresy mają do nich dostęp.

hidden state manipulation

16. Ukryta kradzież tokenów

W niektórych przypadkach złośliwe kontrakty mogą zawierać ukryte funkcje, które pozwalają właścicielowi kontraktu lub atakującemu na kradzież tokenów od niczego niepodejrzewających użytkowników.

Przykładowy kod:


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

W tym przykładzie kontrakt pozwala użytkownikom wpłacać i wypłacać tokeny ERC20. Jednak funkcja stealTokens pozwala właścicielowi kontraktu na kradzież tokenów od dowolnego użytkownika.

Wnioski:

Honeypot to tylko jeden z wielu oszukańczych schematów w DeFi, który wykorzystuje nie tylko ograniczenie wypłat, ale także różne ukryte pułapki opisane powyżej.

Aby nie dać się nabrać na przynętę, musisz zrozumieć specyfikę oszukańczego schematu, subtelności języka programowania Solidity i dokładnie przestudiować inteligentne kontrakty przed interakcją z nimi.

Pozostając poinformowanym i przeprowadzając dokładną analizę due diligence, możesz zminimalizować ryzyko związane z korzystaniem z inteligentnych kontraktów.

 

Jeśli masz jakiekolwiek wątpliwości co do bezpieczeństwa lub funkcjonalności inteligentnego kontraktu, skorzystaj z naszej platformy Lotus Market.

 

 

Platforma Lotus Market została stworzona przez zespół doświadczonych programistów i profesjonalnych audytorów. Celem projektu jest zminimalizowanie ryzyka związanego z inwestowaniem w oszukańcze tokeny i pomoc w bezpiecznym i wygodnym handlu kryptowalutami.

 

Regards, Lotus Market team.

All posts

Connect to a wallet

Metamask