Повторное вхождение и кража владельцев

Предварительные требования: базовое понимание блокчейна Ethereum и смарт-контрактов.

Введение

Туннельное видение - ловушка, с которой сталкивался каждый разработчик. При разработке нового кода для конкретной цели легко настолько сосредоточиться на решении конкретной проблемы, что мы упускаем что-то важное.

Это особенно актуально при переходе от одной технологии к другой. Например: если вы пришли из фона в javascript, маловероятно, что вы сильно беспокоитесь о эксплуатации переполнения, но в Solidity это необходимо решить.

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

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

Что

Смарт-контракты часто нуждаются в вызове или отправке эфира на внешний адрес. Этот тип операций по своей природе уязвим для повторного входа.

Чтобы выполнить атаку повторного входа, злоумышленник развертывает в сети вредоносный контракт. Этот контракт предназначен для манипулирования логикой целевого контракта для отправки ему Эфира, вызывая тем самым его резервную функцию. Затем функция отката вызывает контракт target во время выполнения, чтобы истощить target контракт эфира.

Как

Возьмите следующую функцию внутри уязвимого контракта target:

function withdraw() external {
uint amount = balances[msg.sender];
require(msg.sender.call.value(amount)());
balances[msg.sender] = 0;
}

Предполагая, что balances является отображением адресов в целочисленные значения, функция выполняет следующие действия:

  • Получите amount эфира для отправки вызывающему.
  • Отправьте эту сумму звонящему
  • Установите баланс звонящего на ноль

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

Вот простая функция отката в нашем вредоносном контракте:

function() external payable {
while(calls < 10){
calls++;
reentrancyContract.withdraw();
}
}

Предположим, что calls - это переменная состояния, определенная как 0 в конструкторе. Когда целевой контракт пытается отправить эфир на вредоносный адрес, где находится этот контракт, вызывается эта резервная функция. Если calls меньше 10, функция withdraw() в целевом контракте будет вызвана снова. Это повторяется рекурсивно, пока не будет вызвано 10 раз, истощая контракт target в 10 раз больше, чем предполагалось.

Это произошло в реальной жизни. В 2016 году взлом DAO вызвал огромный шок в Ethereum и индустрии блокчейнов. Его последствия ощущаются до сих пор.

Предотвращение повторных атак

Есть несколько способов защитить ваши контракты от атак повторного входа.

Первый - использовать transfer() для отправки эфира. В предыдущих версиях Solidity контракты должны были использовать метод call(), в котором по умолчанию не было установлено ограничение по газу. transfer(), с другой стороны, в настоящее время имеет лимит в 2300 единиц газа, что примерно достаточно, чтобы вызвать Событие. С этим пределом любое сложное рекурсивное выполнение практически сразу закончится.

Используйте шаблон Проверки → Эффекты → Взаимодействие. При написании кода для конфиденциальных операций убедитесь, что вы изначально:

  1. Выполнить проверки: require() операторов.
  2. Затем внесите изменения, которые влияют на переменные состояния: balance -= withdrawAmount;.
  3. Затем, наконец, выполните взаимодействие: address.transfer(withdrawAmount).

Другой способ - заблокировать контракт, также известный как мьютекс. Используйте переменную состояния, чтобы определить, выполняется ли в настоящее время функция контракта. Если код заблокирован, никакая функция не может быть вызвана, пока блокировка не будет снята.

Кража логики владельца

Что

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

Общий шаблон модификатора - onlyOwner. Здесь переменная состояния owner записывает адрес, по которому был развернут контракт. Любые функции с этим модификатором требуют, чтобы адрес вызывающего абонента был равен owner.

Пользовательский модификатор выглядит так:

modifier onlyOwner {
    require(msg.sender == owner);
    _;
}

Функции, использующие этот модификатор, можно объявить следующим образом:

function somethingImportant() public onlyOwner { … }

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

Как

OnlyOwner контракты часто имеют функцию смены владельца:

function setOwner(address _newOwner) public {
    owner = _newOwner;
}

Проблема здесь в том, что это функция public, что означает, что любой может вызвать эту функцию и, следовательно, стать владельцем контракта. К объявлению функции следует применить дополнительный модификатор onlyOwner.

Предотвращение кражи собственника

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

Получите второе мнение. Существует множество сообществ и компаний, которые проводят аудит смарт-контрактов. Если вы развертываете конфиденциальные контракты, я настоятельно рекомендую вам воспользоваться этими услугами.

Используйте стандартные отраслевые библиотеки. OpenZeppellin имеет набор библиотек и контрактов, которые перед выпуском тщательно проверяются сообществом.

Вывод

Повторная входимость - самый популярный эксплойт из-за взлома DAO. Были предприняты шаги по снижению риска, как и введение transfer(), но вам по-прежнему нужно относиться к нему с осторожностью и кодировать соответственно.

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

Используйте статические инструменты анализа и аудит своего кода.

Прочтите часть 2, в которой обсуждаются уязвимости арифметического недостаточного и переполнения и способы их устранения.

Выучить больше

Если вам понравился этот пост и вы хотите узнать больше о безопасности смарт-контрактов, разработке блокчейнов или пространстве блокчейнов в целом, я настоятельно рекомендую зарегистрироваться на платформе Blockgeeks. У них есть курсы по широкому кругу тем в отрасли, от программирования до маркетинга и торговли. Это оказалось бесценным инструментом для моего развития в области блокчейн.



Следуйте за мной, чтобы увидеть больше статей о разработке блокчейнов, подобных этой.

Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик