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

Статья Взлом $olidity: повторный вход

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
1653451562570.png



Уязвимость повторного входа использует функцию, которая несколько раз вызывает untrusted contract.​

Давайте рассмотрим упрощенный пример:​

Код:
    mapping (address=>uint) balances;

    function deposit() public payable { balances[msg.sender] += msg.value; }

В этом коде злоумышленник может неоднократно вызывать функцию депозита, чтобы снять больше эфира, чем было первоначально депонировано. Причина этого в том, что Solidity объединяет все изменения состояния и запускает их сразу после возврата из функции. В этом случае balances[msg.sender] считывается до того, как ему будет присвоено новое значение, потому что он еще не был обновлен предыдущим deposit() , который все еще выполняется, когда начинается следующий вызов. Если другой контракт Solidity вызывает deposit(), он также будет подвержен повторным атакам. Такие языки, как C++ и Python, не подвержены повторному входу, потому что в них нет пакетной обработки изменений состояния в отдельных функциях!

В Solidity это возможно, потому что все изменения состояния объединяются и применяются сразу после возврата всех функций.

Возможность вызывать функции из других функций — мощный инструмент, но он также может быть опасным. В Solidity это возможно, потому что все изменения состояния объединяются и применяются сразу после возврата всех функций. Причина этого в том, что пользователи могут быть уверены, что состояние не изменится между вызовами функций. Это компромисс, который Ethereum делает для повышения безопасности, но это означает, что мы должны быть осторожны с тем, как мы вызываем функции.

Пользователь может использовать функцию transfer() для многократного вызова withdraw().

Вы можете видеть, что мы уменьшили баланс на 100 эфиров после однократного вызова withdraw(), оставив в контракте баланс в 99 эфиров. Однако злоумышленник может вызвать функцию withdraw() еще раз, вызвав transfer(). Помните, что функция transfer() вызывает withdraw() перед переводом средств на адрес назначения. Поэтому если мы отправим еще 1 эфир на этот контракт, это вызовет еще один вызов функции withdraw() и еще больше уменьшит наш баланс на 2 эфира.

Вот код, как выглядит уязвимый контракт, с двумя общими функциями (конструктор и функция withdraw())

Код:
    contract VulnerableContract {

    uint public balance = 100 ether;

    constructor () public payable {}

    function withdraw() external {

    address(this).transfer(balance); // отправляем все оставшиеся средства владельцу контракта

    balance = 0;

    }

    }

Конструктор устанавливает начальный баланс в 100 эфиров, а функция withdraw() позволяет владельцу вывести все оставшиеся в контракте средства.

Код:
    pragma solidity ^0.4.0;

    contract Reentrancy {

    mapping (address => uint) public balances;

    address owner;

    function Reentrancy() public {

    owner = msg.sender;

    balances[msg.sender] = 100 ether; // the initial balance is set at 100 ethers

    }

function withdraw() public { -->> владельцу снять любой оставшийся баланс в контракте (обратите внимание, что это не оплачиваемая функция, что означает, что вы не можете отправить Эфир непосредственно в нее).

if (!msg.sender.send(balances[owner])) revert(); -->> Если отправка средств не удалась, отмените все изменения состояния, сделанные в транзакции, и восстановите балансы в первозданное состояние, чтобы никакие переводы не были зарегистрированы ни на одном счете (фактически отменяя все, что мы сделали). Без этого злоумышленник смог бы продолжать вызывать `withdraw` до успеха и украсть все Эфиры из контракта. '!' указывает на "не", поэтому утверждение становится "Если отправка средств не провалилась...", что означает, что если она прошла успешно, то выполнить соответствующее действие, следующее за следующим ("revert" в данном случае).

delete balances[owner]; -->> данные, установив их значение равным нулю или false или null, в зависимости от типа переменной. В данном случае "delete" обнулит значение переменной "balances", сохранив при этом выделенное ей место в памяти блокчейна и предотвратив запись в нее других данных в дальнейшем.

Теперь давайте посмотрим, как злоумышленник может взломать этот контракт, вызывая transfer(...) несколько раз, изменяя баланс и снимая больше, чем было первоначально внесено.

Пользователь вносит депозит в размере 10 эфиров. Затем вызывается функция withdraw(). Эта функция вызывает функцию transfer(), чтобы отправить баланс на счет абонента. Но прежде чем эта функция будет выполнена, абонент снова вызывает функцию withdraw(), фактически удваивая снятую сумму!

Будьте осторожны при вызове других контрактов внутри своего контракта!

Очень легко случайно вызвать вредоносный контракт, который выполняет операции, которые вы не планировали. Например, злоумышленник может послать вашему контракту эфир, а затем заставить код вернуться в ваш контракт с дополнительными переводами баланса, которые многократно опустошают ваш счет.

Чтобы избежать этой проблемы, вам следует помнить о нескольких вещах:

Убедитесь, что вы понимаете, что делают другие контракты, прежде чем вызывать их. Вы можете прочитать их исходный код и/или вручную дизассемблировать байткод, чтобы проверить это. Если вы не можете этого сделать, то, возможно, лучше не полагаться на эти контракты.
Убедитесь, что вы знаете, сколько эфира будет переведено, чтобы перевод прошел успешно (и чтобы не было переведено больше, чем предполагалось). Это включает в себя учет Эфира, отправленного вызывающей функцией (msg.value), а также любые вызовы методов forward или подобных, которые могут привести к пересылке большего количества Эфира, чем было первоначально заложено в msg.value. (Если злоумышленники вызовут вашу функцию с достаточно большим значением msg.value, они могут нанести определенный ущерб). Убедитесь, что вызываемый вами контракт не обращается обратно к вашему контракту со значением extraBalance выше 0, если он полагается на send или подобные функции для перевода средств, поскольку, опять же, это может позволить злоумышленнику с достаточным количеством Эфира создать проблемы. В частности, обратите внимание, что если не указано иное, счета, принадлежащие внешним владельцам, могут свободно игнорировать все остальные соображения и выполнять произвольные действия, когда им вздумается, поэтому постарайтесь не стать жертвой, если это произойдет! Убедитесь, что вы знаете, кто является владельцем контракта и существуют ли какие-либо проблемы безопасности, связанные с правом собственности, такие как возможность переназначения и т.д..

Перевод вот ЭТОЙ статьи.
 


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