• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Введение в аудит безопасности смарт-контрактов | Переполнение

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
Существует два типа потока, переполнение и недополнение. Так называемое переполнение относится к тому факту, что при выполнении одного числового вычисления результат вычисления превышает предел емкости, который может хранить или представлять регистр или память. Например, в Solidity диапазон, который может представлять uint8, составляет 256 чисел от 0 до 255. Когда тип uint8 используется для вычисления 255 + 1 в фактической операции, произойдет переполнение, поэтому расчетный результат равен 0, минимальное значение, которое может представлять тип uint8. Точно так же потеря значимости возникает, когда результат вычисления минимален, меньше предела емкости, которую регистр или память может хранить или представлять. Например, в Solidity, когда тип uint8 используется для вычисления 0–1, это приведет к потере значимости, поэтому вычисленное значение равно 255, что является максимальным значением, которое может представлять тип uint8.
Если в контракте есть лазейка для переполнения, фактический результат расчета может существенно отличаться от ожидаемого результата. Это повлияет на нормальную логику контракта и приведет к потере средств в контракте. Однако существуют ограничения версии для уязвимостей переполнения. В версиях Solidity < 0.8 переполнение не сообщит об ошибке, но в версиях >= 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 действует как хранилище времени. Пользователи могут вносить и блокировать средства в контракте с помощью функции депозита, которая будет заблокирована как минимум на одну неделю. Конечно, пользователь все еще может увеличить время хранения с помощью функции increaseLockTime. Пользователь не может вывести токены, заблокированные в контракте TimeLock, до истечения установленного срока хранения. Глядя на функцию increaseLockTime и функцию депозита в этом контракте, мы видим, что он содержит арифметические функции. Версия, поддерживаемая контрактом, является 0.7.6 повышающей совместимость, поэтому этот контракт не будет сообщать об ошибке при арифметическом переполнении. Давайте проанализируем две функции: increaseLockTime и функцию депозита. Мы можем начать с изучения диапазона влияния параметров в этих двух функциях, а затем решить, как начать атаку.

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

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

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

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

Давайте посмотрим на contract Attack:

Код:
    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();
    }
    }

Далее мы развернем контракт эксплойта, чтобы сначала внести Eth, а затем воспользуемся уязвимостью переполнения контракта, чтобы извлечь средства, которые мы депонировали.

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

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

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

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

Ниже приведена блок-схема вызова функции злоумышленника:

1659083161315.png


" Ичто же, Шура, теперь делать..."

Теперь, когда мы лучше понимаем уязвимости переполнения, мы можем научиться защищаться от них. Мы узнаем, как предотвратить уязвимости переполнения и быстро обнаружить их с точки зрения разработчиков и аудиторов:

Как разработчик

1. Используйте SafeMath для предотвращения переполнения;
2. Используйте Solidity 0.8 и выше для разработки контрактов и используйте unchecked с осторожностью, потому что нет проверки переполнения для параметров в непроверенных измененных блоках кода;
3. Необходимо с осторожностью использовать приведение типов переменных. Например, приведение параметра типа uint256 к типу uint8 может вызвать переполнение из-за разных диапазонов значений двух типов.

Как аудитор

1. Сначала проверьте, не ниже ли версия контракта Solidity 0.8 или непроверенный модифицированный блок кода. Если есть, сначала проверьте переполнение параметров и определите сферу влияния.
2. Если версия контракта ниже Solidity 0.8, вам необходимо проверить, ссылается ли контракт на SafeMath.
3. Если используется SafeMath, нужно обратить внимание на то, есть ли в контракте обязательное преобразование типов. Если есть, может возникнуть риск переполнения.
4. Если SafeMath не используется и в контракте есть арифметические операции, мы можем сделать вывод, что этот контракт может иметь риск переполнения. При фактическом аудите мы также должны учитывать существующий код.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх