Глубокое погружение в безопасность смарт-контрактов

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

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

Этот список также можно найти на GitHub.

Атаки

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

Зависимость от упорядочивания транзакций

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

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

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

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

DoS с блокировкой лимита газа

В блокчейне Ethereum все блоки имеют лимит газа. Одним из преимуществ ограничения газа на блок является то, что он не позволяет злоумышленникам создавать бесконечный цикл транзакции, но если использование газа транзакцией превышает этот лимит, транзакция завершится ошибкой. Это может привести к DoS-атаке несколькими способами.

Неограниченные операции

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

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

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

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

Заблокировать загрузку

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

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

Транзакции Ethereum требуют, чтобы отправитель заплатил за газ, чтобы не стимулировать спам-атаки, но в некоторых ситуациях может быть достаточно стимулов для проведения такой атаки. Например, атака с заполнением блоков была использована в игровом Dapp Fomo3D. В приложении был таймер обратного отсчета, и пользователи могли выиграть джекпот, купив ключ последними, за исключением того, что каждый раз, когда пользователь покупал ключ, таймер продлевался. Злоумышленник купил ключ, а затем набил следующие 13 блоков подряд, чтобы выиграть джекпот.

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

DoS с (неожиданным) откатом

DoS-атаки (отказ в обслуживании) могут происходить в функциях, когда вы пытаетесь отправить средства пользователю, и функциональность зависит от успешного перевода средств.

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

Например:

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

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

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

Принудительная отправка эфира в контракт

Иногда пользователям нежелательно иметь возможность отправлять эфир в смарт-контракт. К сожалению, для этих обстоятельств можно обойти функцию отката контракта и принудительно отправить эфир.

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

Первый метод - вызвать метод selfdestruct для контракта с адресом контракта Vulnerable, установленным в качестве получателя. Это работает, потому что selfdestruct не вызовет функцию отката.

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

Недостаточное газовое горе

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

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

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

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

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

Реентерабельность

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

Однофункциональная повторная входимость

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

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

Межфункциональная повторная входимость

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

В этом примере хакер может использовать этот контракт, вызвав резервную функцию transfer() для перевода потраченных средств до того, как баланс будет установлен на 0 в функции withdraw().

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

При переводе средств в смарт-контракте используйте send или transfer вместо call. Проблема с использованием call отличается от других функций, у него нет ограничения газа 2300. Это означает, что call может использоваться во внешних вызовах функций, которые могут использоваться для выполнения атак повторного входа.

Еще один надежный метод предотвращения - это пометить ненадежные функции.

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

Функция должна начинаться с проверок -, например, операторов require и assert.

Затем должны быть выполнены эффекты контракта - например, изменения состояния.

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

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

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

Уязвимости

В этом разделе мы рассмотрим известные уязвимости смарт-контрактов и способы их предотвращения. Почти все перечисленные здесь уязвимости можно найти в Классификации слабых мест смарт-контрактов.

Целочисленное переполнение и потеря значимости

В твердости целочисленные типы имеют максимальные значения. Например:

uint8 => 255

uint16 => 65535

uint24 => 16777215

uint256 => (2^256) - 1

Ошибки переполнения и недостаточного заполнения могут возникать, когда вы превышаете максимальное значение (переполнение) или когда вы опускаетесь ниже минимального значения (недостаточное количество). Когда вы превышаете максимальное значение, вы возвращаетесь к нулю, а когда вы опускаетесь ниже минимального значения, оно возвращает вас к максимальному значению.

Поскольку меньшие целочисленные типы, такие как uint8, uint16 и т. Д., Имеют меньшие максимальные значения, может быть проще вызвать переполнение; таким образом, их следует использовать с большей осторожностью.

Вероятно, лучшим доступным решением для ошибок переполнения и потери значимости является использование библиотеки OpenZeppelin SafeMath при выполнении математических операций.

Зависимость от отметки времени

Временная метка блока, к которой обращается now или block.timestamp, может быть изменена майнером. При использовании метки времени для выполнения функции контракта следует учитывать три момента.

Манипулирование отметкой времени

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

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

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

Правило 15 секунд

Эталонная спецификация Ethereum, «Желтая книга», не устанавливает ограничения на то, сколько блоков может изменяться во времени - оно просто должно быть больше, чем временная метка его родительского элемента. При этом популярные реализации протокола отклоняют блоки с отметками времени более 15 секунд в будущем, поэтому, если ваше зависящее от времени событие может безопасно изменяться на 15 секунд, можно безопасно использовать отметку времени блока.

Не используйте block.number в качестве отметки времени

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

Авторизация через tx.origin

tx.origin - это глобальная переменная в Solidity, которая возвращает адрес, по которому была отправлена ​​транзакция. Важно, чтобы вы никогда не использовали tx.origin для авторизации, поскольку другой контракт может использовать резервную функцию для вызова вашего контракта и получения авторизации, поскольку авторизованный адрес хранится в tx.origin. Рассмотрим этот пример:

Здесь мы видим, что TxUserWallet контракт разрешает transferTo() функцию с tx.origin.

Теперь, если кто-то обманом заставит вас отправить эфир на TxAttackWallet адрес контракта, он сможет украсть ваши средства, проверив tx.origin, чтобы найти адрес, по которому была отправлена ​​транзакция.

Чтобы предотвратить подобные атаки, используйте msg.sender для авторизации.

Плавающая прагма

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

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

Видимость функции по умолчанию

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

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

Устаревшая версия компилятора

Разработчики часто находят ошибки и уязвимости в существующем программном обеспечении и вносят исправления. По этой причине важно использовать самую последнюю версию компилятора. Смотрите ошибки из прошлых версий компилятора здесь.

Непроверенное значение вызова-возврата

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

В Solidity вы можете использовать низкоуровневые вызовы, такие как address.call(), address.callcode(), address.delegatecall() и address.send(), или вы можете использовать вызовы контрактов, такие как ExternalContract.doSomething(). Низкоуровневые вызовы никогда не вызовут исключения - вместо этого они вернут false, если встретят исключение, тогда как вызовы контрактов будут автоматически вызывать.

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

Вывод незащищенного эфира

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

Незащищенная инструкция по самоуничтожению

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

Эта атака использовалась в Атаке паритета. Анонимный пользователь обнаружил и использовал уязвимость в смарт-контракте библиотеки, сделав себя владельцем контракта. Затем злоумышленник приступил к самоуничтожению контракта. Это привело к тому, что средства были заблокированы в 587 уникальных кошельках, содержащих в общей сложности 513 774,16 эфира.

Видимость переменной состояния по умолчанию

Обычно разработчики явно объявляют видимость функции, но не так часто объявляют видимость переменной. Переменные состояния могут иметь один из трех идентификаторов видимости: public, internal или private. К счастью, видимость по умолчанию для переменных является внутренней, а не общедоступной, но даже если вы намереваетесь объявить переменную как внутреннюю, важно быть явным, чтобы не было неверных предположений относительно того, кто может получить доступ к переменной.

Неинициализированный указатель хранилища

Данные хранятся в EVM как storage, memory или calldata. Важно, чтобы они были хорошо поняты и правильно инициализированы. Неправильная инициализация указателей хранилища данных или просто оставление их неинициализированными может привести к уязвимости контракта.

Начиная с Solidity 0.5.0, неинициализированные указатели хранилища больше не являются проблемой, поскольку контракты с неинициализированными указателями хранилища больше не будут компилироваться. При этом по-прежнему важно понимать, какие указатели хранилища следует использовать в определенных ситуациях.

Заявить о нарушении

В Solidity 0.4.10 были созданы следующие функции: assert(), require() и revert(). Здесь мы обсудим функцию assert и ее использование.

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

Если условие, отмеченное в assert(), на самом деле не является инвариантом, рекомендуется заменить его оператором require().

Использование устаревшего оружия

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

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

Делегировать вызов вызываемого абонента, которому не доверяют

Delegatecall - это особый вариант вызова сообщения. Он почти идентичен вызову обычного сообщения, за исключением того, что целевой адрес выполняется в контексте вызывающего контракта, а msg.sender и msg.value остаются неизменными. По сути, delegatecall делегирует другим контрактам изменение хранилища вызывающего контракта.

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

Фирменная пластичность

Часто люди предполагают, что использование системы криптографической подписи в смарт-контрактах подтверждает уникальность подписей; однако это не так. Подпись в Ethereum может быть изменена без закрытого ключа и останется действительной. Например, криптография с эллиптическим ключом состоит из трех переменных - v, r и s -, и если эти значения изменены только в нужном Таким образом, вы можете получить действительную подпись с недействительным закрытым ключом.

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

Неправильное имя конструктора

До Solidity 0.4.22 единственным способом определить конструктор было создание функции с именем контракта. В некоторых случаях это было проблематично. Например, если смарт-контракт повторно используется с другим именем, но функция-конструктор также не изменяется, она просто становится обычной вызываемой функцией.

Теперь, в современных версиях Solidity, вы можете определить конструктор с ключевым словом constructor, эффективно устраняя эту уязвимость. Таким образом, решение этой проблемы - просто использовать современные версии компилятора Solidity.

Переменные состояния затенения

В Solidity можно использовать одну и ту же переменную дважды, но это может привести к нежелательным побочным эффектам. Это особенно сложно при работе с несколькими контрактами. Возьмем следующий пример:

Здесь мы видим, что SubContract наследует SuperContract, а переменная a определяется дважды с разными значениями. Теперь предположим, что мы используем a для выполнения некоторой функции в SubContract. Функциональность, унаследованная от SuperContract, больше не будет работать, так как значение a было изменено.

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

Слабые источники случайности из цепных атрибутов

В Ethereum есть определенные приложения, которые для справедливости полагаются на генерацию случайных чисел. Однако генерация случайных чисел в Ethereum очень сложна, и есть несколько подводных камней, которые стоит учитывать.

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

Чтобы избежать манипуляций с майнерами при генерации случайных чисел, есть несколько решений:

  • Схема обязательств, такая как RANDAO, DAO, где случайное число генерируется всеми участниками DAO.
  • Внешние источники через оракулы - например, Oraclize
  • Использование хэшей блоков биткойнов, поскольку сеть более децентрализована, а майнинг блоков дороже.

Отсутствует защита от атак с повторением подписи

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

Для защиты от атак с повторением подписи контракт должен разрешать обработку только новых хэшей. Это не позволяет злоумышленникам многократно воспроизводить подпись другого пользователя.

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

  • Сохраните каждый хэш сообщения, обработанный контрактом, - затем сравните хэши сообщений с существующими перед выполнением функции.
  • Включите адрес контракта в хэш, чтобы гарантировать, что сообщение используется только в одном контракте.
  • Никогда не создавайте хэш сообщения, включая подпись. См. «Сигнатурная пластичность».

Нарушение требования

Метод require() предназначен для проверки условий, таких как входные данные или переменные состояния контракта, или для проверки возвращаемых значений из вызовов внешнего контракта. Для проверки внешних вызовов входные данные могут предоставляться вызывающими абонентами или возвращаться вызываемыми. В случае, если нарушение ввода произошло из-за возвращаемого значения вызываемого объекта, вероятно, что-то пошло не так:

  • В контракте обнаружена ошибка.
  • Условие требования слишком строгое.

Чтобы решить эту проблему, сначала подумайте, не является ли условие требования слишком строгим. При необходимости ослабьте его, чтобы разрешить любой допустимый внешний вход. Если проблема не в условии требования, в контракте должна быть ошибка, предусматривающая внешний ввод. Убедитесь, что этот контракт не предоставляет недопустимые входные данные.

Запись в произвольное место хранения

Только авторизованные адреса должны иметь доступ для записи в конфиденциальные места хранения. Если на протяжении всего контракта не проводятся надлежащие проверки авторизации, злоумышленник может перезаписать конфиденциальные данные. Однако даже при наличии проверок авторизации для записи в конфиденциальные данные злоумышленник все еще может перезаписать конфиденциальные данные с помощью нечувствительных данных. Это может дать злоумышленнику доступ для перезаписи важных переменных, таких как владелец контракта.

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

Неправильный порядок наследования

В Solidity возможно наследование из нескольких источников, что при неправильном понимании может внести двусмысленность. Эта неоднозначность известна как проблема алмаза: если два базовых контракта имеют одну и ту же функцию, какой из них следует отдать приоритет? К счастью, Solidity изящно справляется с этой проблемой - пока разработчик понимает решение.

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

Произвольный прыжок с переменной функционального типа

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

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

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

Наличие неиспользуемых переменных

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

  • Увеличение вычислений (ненужный расход газа)
  • Индикация ошибок или искаженных структур данных
  • Пониженная читаемость кода

Настоятельно рекомендуется удалить все неиспользуемые переменные из базы кода.

Неожиданный баланс эфира

Поскольку всегда можно отправить эфир в контракт - см. «Принудительная отправка эфира в смарт-контракт» - если контракт предполагает определенный баланс, он уязвим для атаки.

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

Незашифрованные секреты

Код смарт-контракта Ethereum всегда можно прочитать. Относитесь к нему как к такому. Даже если ваш код не проверен на Etherscan, злоумышленники могут декомпилировать или даже просто проверять транзакции к нему и от него, чтобы проанализировать его.

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

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

Обнаружение неисправного контракта

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

Незаборенная зависимость от блокчейна

Многие контракты полагаются на звонки, происходящие в течение определенного периода времени, но Ethereum может быть засыпан спамом с очень высокими транзакциями Gwei в течение приличного количества времени и относительно дешево.

Например, Fomo3D (игра с обратным отсчетом, в которой последний инвестор выигрывает джекпот, но каждая инвестиция добавляет время к обратному отсчету) была выиграна пользователем, который полностью забил блокчейн в течение небольшого периода времени, не позволяя другим инвестировать, пока не сработает таймер. аут, и он выиграл (см. «DoS с блокировкой лимита газа»).

В настоящее время существует много контрактов с крупье на азартные игры, которые полагаются на прошлые хеши блоков для обеспечения ГСЧ. По большей части это не ужасный источник ГСЧ, и они даже учитывают сокращение хэшей, которое происходит после 256 блоков. Но в этот момент многие из них просто аннулируют ставку. Это позволило бы кому-то делать ставки на многие из этих аналогичных действующих контрактов с определенным результатом в качестве победителя для всех, проверять представление крупье, пока оно еще не завершено, и, если это неблагоприятно, просто засорять блокчейн до тех пор, пока не произойдет обрезка, и они могли бы вернуть свои ставки.

Несоблюдение стандартов

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

Возьмем, к примеру, оригинальный токен BNB Binance. Он продавался как токен ERC20, но позже было указано, что он на самом деле не соответствовал ERC-20 по нескольким причинам:

  • Это предотвратило отправку на 0x0
  • Он заблокировал переводы на сумму 0
  • Он не возвращал true или false для успеха или неудачи

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

Хотя стандарты не всегда идеальны и могут когда-нибудь устареть, они способствуют созданию наиболее безопасных смарт-контрактов.

Заключение

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

Особая благодарность RobertMCForster за отличный вклад.

Дальнейшее чтение