Подготовка смарт контракта к аудиту
Я нашел прекрасную ветку твитов от человека, который регулярно работает с проверками смарт контрактов. Тут он описывает рекомендации, которыми должен пользоваться каждый уважающий себя разработчик. Предлагаю перевод:
1. Делайте подробные комментарии к своему коду так, чтобы другой человек, который будет проводить аудит, смог сразу понять, что делает данный код и зачем он нужен в контракте вообще.
2. Документируйте все, что является публичным API контракта.
3. Большой плюс, если вы будете использовать NatSpec формат (о нем подробнее в следующих постах).
4. Перед аудитом пропишите тесты каждой функции, require, event и т.д. Хороший инструмент для таких целей npm пакет solidity-coverage (входит в hardhat toolbox по умолчанию).
5. Избавьтесь от всех ошибок в контракте для его успешной компиляции.
6. Не старайтесь переоптимизировать свой код. Порой критические ошибки появляются именно там. При оптимизации пишите комментарий о том, для чего это делается в этом конкретном случае.
7. Используйте понятные переменные и названия функций. Сокращения, абстракции или символика не сделают ваш код лучше.
8. Удаляйте старые комментарии, неиспользуемый код и функции.
9. Не изобретайте велосипед там, где это не нужно. Есть прекрасные библиотеки для создания токенов, математических операций и многого другого. Безопаснее использовать проверенные методы, чем "городить" свои.
10. Не делайте изменения в коде (комите на GitHub) во время аудита кода. Это сильно усложняет работу.
11. Если вы сможете сами рассказать про свой код на созвоне или лично, это сильно поможет проверяющему.
12. В комментариях аудитору опишите свои планы, как вы планируете использовать свои контракт в будущем: обновлять, дополнять, наследовать и т.д.
13. Оставайтесь на связи! Даже с учетом подробной документации, у проверяющих всегда могут найтись дополнительные вопросы.
14. Не надейтесь, что аудит кода на 100% убережет вас от взлома. Это лишь еще одна ступень безопасности!
Если обобщать все твиты в одно предложение, то оно будет звучать так: пишите понятный код с подробными комментариями и уважайте работу проверяющих.
#аудит #безопасность #взлом #hack
Я нашел прекрасную ветку твитов от человека, который регулярно работает с проверками смарт контрактов. Тут он описывает рекомендации, которыми должен пользоваться каждый уважающий себя разработчик. Предлагаю перевод:
1. Делайте подробные комментарии к своему коду так, чтобы другой человек, который будет проводить аудит, смог сразу понять, что делает данный код и зачем он нужен в контракте вообще.
2. Документируйте все, что является публичным API контракта.
3. Большой плюс, если вы будете использовать NatSpec формат (о нем подробнее в следующих постах).
4. Перед аудитом пропишите тесты каждой функции, require, event и т.д. Хороший инструмент для таких целей npm пакет solidity-coverage (входит в hardhat toolbox по умолчанию).
5. Избавьтесь от всех ошибок в контракте для его успешной компиляции.
6. Не старайтесь переоптимизировать свой код. Порой критические ошибки появляются именно там. При оптимизации пишите комментарий о том, для чего это делается в этом конкретном случае.
7. Используйте понятные переменные и названия функций. Сокращения, абстракции или символика не сделают ваш код лучше.
8. Удаляйте старые комментарии, неиспользуемый код и функции.
9. Не изобретайте велосипед там, где это не нужно. Есть прекрасные библиотеки для создания токенов, математических операций и многого другого. Безопаснее использовать проверенные методы, чем "городить" свои.
10. Не делайте изменения в коде (комите на GitHub) во время аудита кода. Это сильно усложняет работу.
11. Если вы сможете сами рассказать про свой код на созвоне или лично, это сильно поможет проверяющему.
12. В комментариях аудитору опишите свои планы, как вы планируете использовать свои контракт в будущем: обновлять, дополнять, наследовать и т.д.
13. Оставайтесь на связи! Даже с учетом подробной документации, у проверяющих всегда могут найтись дополнительные вопросы.
14. Не надейтесь, что аудит кода на 100% убережет вас от взлома. Это лишь еще одна ступень безопасности!
Если обобщать все твиты в одно предложение, то оно будет звучать так: пишите понятный код с подробными комментариями и уважайте работу проверяющих.
#аудит #безопасность #взлом #hack
👍1
Что такое NatSpec в смарт контракте?
NatSpec (Ethereum Natural Language Specification Format) - это специальная форма комментариев для документирования вашего смарт контракта.
Когда мы учились создавать soulbound токен и разбирали код с GitHub, там было прекрасно показано использование таких комментариев.
Простой пример:
/**
* @noscript Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*/
Вообще, существуют следующие теги:
@noscript - описывает контракт, библиотеку или интерфейс;
@author - автор контракта, библиотеки или интерфейса;
@notice - заметка о функции, переменной, контракта и т.д.;
@dev - описывает для разработчиков какие-либо детали кода;
@param - документирует параметры функции или event;
@return - говорит, что возвращается в функции;
@inheritdoc - копирует теги из базового контракта (обязательно нужно указывать сам контракт);
@custom - пользовательский комментарий;
Больше про теги и комментирование можно прочитать в официальной документации Solidity тут.
#аудит #natspec
NatSpec (Ethereum Natural Language Specification Format) - это специальная форма комментариев для документирования вашего смарт контракта.
Когда мы учились создавать soulbound токен и разбирали код с GitHub, там было прекрасно показано использование таких комментариев.
Простой пример:
/**
* @noscript Counters
* @author Matt Condon (@shrugs)
* @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
*/
Вообще, существуют следующие теги:
@noscript - описывает контракт, библиотеку или интерфейс;
@author - автор контракта, библиотеки или интерфейса;
@notice - заметка о функции, переменной, контракта и т.д.;
@dev - описывает для разработчиков какие-либо детали кода;
@param - документирует параметры функции или event;
@return - говорит, что возвращается в функции;
@inheritdoc - копирует теги из базового контракта (обязательно нужно указывать сам контракт);
@custom - пользовательский комментарий;
Больше про теги и комментирование можно прочитать в официальной документации Solidity тут.
#аудит #natspec
👍1
Безопасность. Внешние вызовы. Часть 1
Вызовы (далее external calls) в другие контракты, код которых вы не знаете, могут привести к значительным проблемам безопасности вашего контракта.
Постарайтесь как можно реже использовать external calls в своем контракте. Однако, если все же это необходимо, то придерживайтесь следующих правил.
Помечайте контракты, которые вы не знаете
Когда вы используете чужие внешние контракты (далее untrusted contracts), используйте понятные обозначения переменных, которые "прямо говорят" об использовании рискованных функций.
Например:
// плохо
Bank.withdraw(100); // Unclear whether trusted or untrusted
function makeWithdrawal (uint amount) {
// Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}
// хорошо
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp
function makeUntrustedWithdrawal (uint amount) {
UntrustedBank.withdraw(amount);
}
#безопасность #externalcall #external
Вызовы (далее external calls) в другие контракты, код которых вы не знаете, могут привести к значительным проблемам безопасности вашего контракта.
Постарайтесь как можно реже использовать external calls в своем контракте. Однако, если все же это необходимо, то придерживайтесь следующих правил.
Помечайте контракты, которые вы не знаете
Когда вы используете чужие внешние контракты (далее untrusted contracts), используйте понятные обозначения переменных, которые "прямо говорят" об использовании рискованных функций.
Например:
// плохо
Bank.withdraw(100); // Unclear whether trusted or untrusted
function makeWithdrawal (uint amount) {
// Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}
// хорошо
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp
function makeUntrustedWithdrawal (uint amount) {
UntrustedBank.withdraw(amount);
}
#безопасность #externalcall #external
👍1
Безопасность. Внешние вызовы. Часть 2
Избегайте изменений переменных состояния при external calls
Всегда имейте ввиду, что внешние вызовы могут исполнять вредоносный код в другом контракте. В этом случае, изменение переменных, которые хранятся в блокчейне, а не памяти транзакции, могут привести к взлому контракта, захвата ownership или reentrancy.
#безопасность #externalcall #external
Избегайте изменений переменных состояния при external calls
Всегда имейте ввиду, что внешние вызовы могут исполнять вредоносный код в другом контракте. В этом случае, изменение переменных, которые хранятся в блокчейне, а не памяти транзакции, могут привести к взлому контракта, захвата ownership или reentrancy.
#безопасность #externalcall #external
👍1
Безопасность. Внешние вызовы. Часть 3
Не используйте transfer() или send()
Оба метода используют фиксированное количество газа (2300), как изначально предполагалось для защиты от reentrancy. Однако с одним из последних обновлений (EIP 1884) количество газа в этих функциях может быть увеличено.
Рекомендуется использовать call().
Пример:
// плохо
contract Vulnerable {
function withdraw(uint256 amount) external {
// This forwards 2300 gas, which may not be enough if the recipient
// is a contract and gas costs change.
msg.sender.transfer(amount);
}
}
// хорошо
contract Fixed {
function withdraw(uint256 amount) external {
// This forwards all available gas. Be sure to check the return value!
(bool success, ) = msg.sender.call.value(amount)("");
require(success, "Transfer failed.");
}
}
P.S. call() также не защищает от reentrancy, просто в документации советуют использовать именно его.
#безопасность #externalcall #external
Не используйте transfer() или send()
Оба метода используют фиксированное количество газа (2300), как изначально предполагалось для защиты от reentrancy. Однако с одним из последних обновлений (EIP 1884) количество газа в этих функциях может быть увеличено.
Рекомендуется использовать call().
Пример:
// плохо
contract Vulnerable {
function withdraw(uint256 amount) external {
// This forwards 2300 gas, which may not be enough if the recipient
// is a contract and gas costs change.
msg.sender.transfer(amount);
}
}
// хорошо
contract Fixed {
function withdraw(uint256 amount) external {
// This forwards all available gas. Be sure to check the return value!
(bool success, ) = msg.sender.call.value(amount)("");
require(success, "Transfer failed.");
}
}
P.S. call() также не защищает от reentrancy, просто в документации советуют использовать именно его.
#безопасность #externalcall #external
👍1
Безопасность. Внешние вызовы. Часть 4
Учитывайте ошибки внешних вызовов
// плохо
someAddress.send(55);
someAddress.call.value(55)("");
// опасно, так как использует весь газ и не проверяет на ошибки
someAddress.call.value(100)(bytes4(sha3("deposit()")));
// если deposit() получит ошибку, то call() вернет false, но транзакция все равно отправит деньги
// хорошо
(bool success, ) = someAddress.call.value(55)("");
if(!success) {
// handle failure code
}
Всегда проверяйте успешность низкоуровневых вызовов!
#безопасность #externalcall #external
Учитывайте ошибки внешних вызовов
// плохо
someAddress.send(55);
someAddress.call.value(55)("");
// опасно, так как использует весь газ и не проверяет на ошибки
someAddress.call.value(100)(bytes4(sha3("deposit()")));
// если deposit() получит ошибку, то call() вернет false, но транзакция все равно отправит деньги
// хорошо
(bool success, ) = someAddress.call.value(55)("");
if(!success) {
// handle failure code
}
Всегда проверяйте успешность низкоуровневых вызовов!
#безопасность #externalcall #external
👍1
Безопасность. Внешние вызовы. Часть 5
Используйте "запросы" вместо "автоматики"
Если вам по логике контракта нужно вернуть деньги пользователям, то лучше сделать так, чтобы они сами запрашивали возврат средств, вместо того, чтобы автоматически делать рассылку по достижению каких-либо условий.
Этот подход также помогает оптимизировать расход газа в контракте.
Не используйте delegatecall
delegatecall вообще опасная штука. Разбор кода со взломом контракта через этот метод есть на канале. Если вы на 100% не уверены, что и как будет выполнять функция с ним, то старайтесь не использовать его совсем.
Помните, что delegatecall выполняет сторонние функции внутри вашего контракта.
#безопасность #externalcall #external
Используйте "запросы" вместо "автоматики"
Если вам по логике контракта нужно вернуть деньги пользователям, то лучше сделать так, чтобы они сами запрашивали возврат средств, вместо того, чтобы автоматически делать рассылку по достижению каких-либо условий.
Этот подход также помогает оптимизировать расход газа в контракте.
Не используйте delegatecall
delegatecall вообще опасная штука. Разбор кода со взломом контракта через этот метод есть на канале. Если вы на 100% не уверены, что и как будет выполнять функция с ним, то старайтесь не использовать его совсем.
Помните, что delegatecall выполняет сторонние функции внутри вашего контракта.
#безопасность #externalcall #external
👍1
Безопасность. Инварианты
Попытаюсь объяснить простым языком: существуют переменные, которые должны быть неизменными (инварианты), типа как totalSupply в ERC20. Т.е. когда бы мы к ней не обратились, она будет иметь такое же значение.
Иногда разработчики делают проверку такого инварианта на баланс аккаунта, полагая, что никто кроме них не может управлять балансом контракта.
Вот тут и кроется уязвимость. Дело в том, что хакер может послать эфир на счет этого контракта, и тот примет его в любом случае, даже если будет fallback функция с revert.
Вместе с эфиром может быть отправлена другая нежелательная функция, которая может даже уничтожить ваш контракт.
Подробнее тут.
#безопасность #invariant
Попытаюсь объяснить простым языком: существуют переменные, которые должны быть неизменными (инварианты), типа как totalSupply в ERC20. Т.е. когда бы мы к ней не обратились, она будет иметь такое же значение.
Иногда разработчики делают проверку такого инварианта на баланс аккаунта, полагая, что никто кроме них не может управлять балансом контракта.
Вот тут и кроется уязвимость. Дело в том, что хакер может послать эфир на счет этого контракта, и тот примет его в любом случае, даже если будет fallback функция с revert.
Вместе с эфиром может быть отправлена другая нежелательная функция, которая может даже уничтожить ваш контракт.
Подробнее тут.
#безопасность #invariant
👍1
Безопасность. Ограничения и проверки
Помните, что смарт контракты должны создаваться по принципу zero-trust или ноль доверия. Поэтому каждое действие, которое вы хотите выполнять от имени администратора, должно содержать проверку на это.
Так же и с другими функциями: давайте доступ к ним только тем людям (адресам), которые по логике должны его иметь, как например, голосование по вашему контракту должно быть доступно только его разработчикам или сотрудникам компании.
Более того в работе с переводами обязательно должны существовать свои ограничения.
Например, деньги можно переводить в определенный срок (в течение пары дней до \ после голосования), или только определенную сумму в промежутке от и до, и т.д.
В общем, проверки и условия наше все!
#безопасность
Помните, что смарт контракты должны создаваться по принципу zero-trust или ноль доверия. Поэтому каждое действие, которое вы хотите выполнять от имени администратора, должно содержать проверку на это.
Так же и с другими функциями: давайте доступ к ним только тем людям (адресам), которые по логике должны его иметь, как например, голосование по вашему контракту должно быть доступно только его разработчикам или сотрудникам компании.
Более того в работе с переводами обязательно должны существовать свои ограничения.
Например, деньги можно переводить в определенный срок (в течение пары дней до \ после голосования), или только определенную сумму в промежутке от и до, и т.д.
В общем, проверки и условия наше все!
#безопасность
👍1
Безопасность. Атаки reentrancy и DoS
Сегодня мы продолжим говорить о вопросах безопасности смарт контрактов и затронем тему атак. Их существует достаточно большое разнообразие, но мы затронем только самые популярные. К сожалению, на примере я показать не смогу, так как еще у самого мало опыта в этом, поэтому будет описание самой атаки и способ, как ее избежать.
Есть прекрасное видео, где лектор подробно рассказывает и показывает про самую распространенную атаку reentrancy, а также Dos или Denial of Service.
Новый видео урок.
В последующий постах мы рассмотрим еще 6-7 различный атак, а уже завтра я расскажу вам про программы и сервисы, которые помогут с аудитом ваших контрактов на предмет возможности какой-либо уязвимости.
Приятного дня и легкого обучения.
#reentrancy #dos #безопасность
Сегодня мы продолжим говорить о вопросах безопасности смарт контрактов и затронем тему атак. Их существует достаточно большое разнообразие, но мы затронем только самые популярные. К сожалению, на примере я показать не смогу, так как еще у самого мало опыта в этом, поэтому будет описание самой атаки и способ, как ее избежать.
Есть прекрасное видео, где лектор подробно рассказывает и показывает про самую распространенную атаку reentrancy, а также Dos или Denial of Service.
Новый видео урок.
В последующий постах мы рассмотрим еще 6-7 различный атак, а уже завтра я расскажу вам про программы и сервисы, которые помогут с аудитом ваших контрактов на предмет возможности какой-либо уязвимости.
Приятного дня и легкого обучения.
#reentrancy #dos #безопасность
YouTube
Solidity и смарт-контракты Ethereum, урок #16 | Безопасность: Reentrancy, DoS
ХОТИТЕ СТАТЬ РАЗРАБОТЧИКОМ Solidity, узнать об Ethereum, блокчейне и многом другом ещё больше?!
Мои друзья из GUIDE DAO (бывшая школа MCS) предлагают скидку 0,1 ETH на ВСЕ СВОИ БУТКЕМЫ ПО КРИПТЕ! Материалы этих буткемов подготовлены мной и другими специалистами:…
Мои друзья из GUIDE DAO (бывшая школа MCS) предлагают скидку 0,1 ETH на ВСЕ СВОИ БУТКЕМЫ ПО КРИПТЕ! Материалы этих буткемов подготовлены мной и другими специалистами:…
👍1
Безопасность. Атаки honeypot, delegatecall и oracle manipulation
Я сейчас вспомнил, что на канале ранее мы уже затрагивали тему двух видов атак. Там все рассказано более чем подробно, поэтому предлагаю пересмотреть их еще раз:
Honeypot - https://news.1rj.ru/str/solidityset/204
Атаки через delegatecall (видео, начиная с 12:30) - https://news.1rj.ru/str/solidityset/116
Также тут еще хотел бы рассказать про другую атаку - Oracle Manipulation (Манипуляция Оракулами).
Как вы уже знаете, оракулы созданы для того, чтобы получать данные извне и передавать в контракт. Так вот эта атака как раз нацелена на оракул, для того чтобы передать неверные данные в контракт и, возможно, вывести все деньги оттуда.
Защита очень простая - не городить "свои костыли и оракулы", не пользоваться всего лишь одним-двумя оракулами для получения данных, или в лучшем случае использовать профессиональные сервисы, типа Chainlink.
#honeypot #delegatecall #oracle #manipulation #безопасность
Я сейчас вспомнил, что на канале ранее мы уже затрагивали тему двух видов атак. Там все рассказано более чем подробно, поэтому предлагаю пересмотреть их еще раз:
Honeypot - https://news.1rj.ru/str/solidityset/204
Атаки через delegatecall (видео, начиная с 12:30) - https://news.1rj.ru/str/solidityset/116
Также тут еще хотел бы рассказать про другую атаку - Oracle Manipulation (Манипуляция Оракулами).
Как вы уже знаете, оракулы созданы для того, чтобы получать данные извне и передавать в контракт. Так вот эта атака как раз нацелена на оракул, для того чтобы передать неверные данные в контракт и, возможно, вывести все деньги оттуда.
Защита очень простая - не городить "свои костыли и оракулы", не пользоваться всего лишь одним-двумя оракулами для получения данных, или в лучшем случае использовать профессиональные сервисы, типа Chainlink.
#honeypot #delegatecall #oracle #manipulation #безопасность
👍1
Безопасность. Атака frontrunning (опережение)
В Ethereum все транзакции сначала добавляются в пул, который виден всем участникам сети, а далее майнеры добавляют их в блок в произвольном порядке. Поэтому результат выполнения транзакции не должен зависеть от ее положения в блоке.
Допустим, наш контракт представляет собой конкурс на поиск числа, хеш от которого меньше заданного значения. Допустим, вы нашли подходящее решение и отправили транзакцию с ответом. Нечестный игрок может подсмотреть ваш ответ в пуле транзакций и отправить такой же. При отправке транзакции нужно указать цену за газ, которую вы готовы заплатить, чтобы ваша транзакция выполнилась. Чем выше цена газа, тем больше вероятность транзакции быть добавленной в блок раньше. Таким образом, злоумышленник, выставив цену газа выше вашей, сможет выиграть конкурс.
Частично решит проблему добавление в контракт ограничения на цену газа, но майнеры все равно смогут манипулировать порядком транзакций. Более правильным, но сложным решением будет схема Commit — Reveal, которую мы также рассматривали в одном из прошлых уроков.
#frontrunning #безопасность
В Ethereum все транзакции сначала добавляются в пул, который виден всем участникам сети, а далее майнеры добавляют их в блок в произвольном порядке. Поэтому результат выполнения транзакции не должен зависеть от ее положения в блоке.
Допустим, наш контракт представляет собой конкурс на поиск числа, хеш от которого меньше заданного значения. Допустим, вы нашли подходящее решение и отправили транзакцию с ответом. Нечестный игрок может подсмотреть ваш ответ в пуле транзакций и отправить такой же. При отправке транзакции нужно указать цену за газ, которую вы готовы заплатить, чтобы ваша транзакция выполнилась. Чем выше цена газа, тем больше вероятность транзакции быть добавленной в блок раньше. Таким образом, злоумышленник, выставив цену газа выше вашей, сможет выиграть конкурс.
Частично решит проблему добавление в контракт ограничения на цену газа, но майнеры все равно смогут манипулировать порядком транзакций. Более правильным, но сложным решением будет схема Commit — Reveal, которую мы также рассматривали в одном из прошлых уроков.
#frontrunning #безопасность
👍1
Безопасность. Атака timestamp dependence / time manipulation
В Solidity переменная "now" соответствует глобальной переменной "block.timestamp", которую задает майнер, поэтому ориентироваться на ее в качестве временного показателя нельзя.
Возьмем к примеру контракт:
contract Auction {
uint jackpot = 100 ether;
uint public lastBid = 1 ether;
constructor() public payable {}
function setBid() public payable {
require(msg.value >= lastBid);
if (now%20 == 0){
msg.sender.transfer(jackpot);
}
}
}
Контракт представляет собой гибрид аукциона и лотереи: участники отправляют ставки и выигрывает тот, время ставки которого удовлетворяет определенному условию. Для гарантированного выигрыша майнер может установить подходящее значение "block.timestamp" и смайнить блок со своей выигрышной транзакцией.
#timestamp #time #безопасность
В Solidity переменная "now" соответствует глобальной переменной "block.timestamp", которую задает майнер, поэтому ориентироваться на ее в качестве временного показателя нельзя.
Возьмем к примеру контракт:
contract Auction {
uint jackpot = 100 ether;
uint public lastBid = 1 ether;
constructor() public payable {}
function setBid() public payable {
require(msg.value >= lastBid);
if (now%20 == 0){
msg.sender.transfer(jackpot);
}
}
}
Контракт представляет собой гибрид аукциона и лотереи: участники отправляют ставки и выигрывает тот, время ставки которого удовлетворяет определенному условию. Для гарантированного выигрыша майнер может установить подходящее значение "block.timestamp" и смайнить блок со своей выигрышной транзакцией.
#timestamp #time #безопасность
👍1
Безопасность. Атака Arithmetic
Уязвимости целочисленного переполнения, как и в других языках, возникают из-за ограниченного размера памяти, выделенного на переменную. Максимальное значение для переменной типа uint (uint256) равно 2256−1, минимальное — 0. Выйти за эти границы не получится: значение либо обнулится (при переполнении вверх), либо станет максимальным (при переполнении вниз).
Очень хорошо про этот процесс было рассказано в этом видео.
Для примера можно привести такую функцию:
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value;
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
return true;
}
Функция batchTransfer() перечисляет заданное в _value количество ether на адреса из массива _receivers. Обрати внимание на строку вычисления amount, переполнение в которой никак не проверяется. Если задать значение _value равным 2256 / _receivers.length, то в результате переполнения amount примет значение 0. Обе проверки далее пройдут успешно, в том числе и проверка того, что баланс отправителя больше amount. В итоге балансы получателей будут пополнены на величину _value. Если получателей было два, то каждый из них получит по
2^256/2=2^255=0x8000000000000000000000000000000000000000000000000000000000000000 токенов.
Для исключения подобных уязвимостей рекомендуется использовать библиотеку SafeMath от OpenZeppelin.
#arithmetic #time #безопасность
Уязвимости целочисленного переполнения, как и в других языках, возникают из-за ограниченного размера памяти, выделенного на переменную. Максимальное значение для переменной типа uint (uint256) равно 2256−1, минимальное — 0. Выйти за эти границы не получится: значение либо обнулится (при переполнении вверх), либо станет максимальным (при переполнении вниз).
Очень хорошо про этот процесс было рассказано в этом видео.
Для примера можно привести такую функцию:
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) {
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value;
require(cnt > 0 && cnt <= 20);
require(_value > 0 && balances[msg.sender] >= amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i < cnt; i++) {
balances[_receivers[i]] = balances[_receivers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
return true;
}
Функция batchTransfer() перечисляет заданное в _value количество ether на адреса из массива _receivers. Обрати внимание на строку вычисления amount, переполнение в которой никак не проверяется. Если задать значение _value равным 2256 / _receivers.length, то в результате переполнения amount примет значение 0. Обе проверки далее пройдут успешно, в том числе и проверка того, что баланс отправителя больше amount. В итоге балансы получателей будут пополнены на величину _value. Если получателей было два, то каждый из них получит по
2^256/2=2^255=0x8000000000000000000000000000000000000000000000000000000000000000 токенов.
Для исключения подобных уязвимостей рекомендуется использовать библиотеку SafeMath от OpenZeppelin.
#arithmetic #time #безопасность
👍1
Безопасность. Атака Short Address
Для вызова функций контрактов используется ABI (Application Binary Interface) — бинарный интерфейс приложения, согласно которому вызов функции представляется в виде последовательности байтов. Первые четыре байта — это начало хеша keccak256 от подписи функции (ее название и типы входных данных), далее идут значения входных параметров.
function transfer(address to, uint amount) public returns (bool success);
Адрес в Ethereum состоит из 20 байт. Для осуществления атаки злоумышленнику нужно сгенерировать адрес, который заканчивается на нулевой байт, например abcdabcdabcdabcdabcdabcdabcdabcdabcdab00. В обычном случае вызов функции на перевод 1 ether будет выглядеть так (вертикальной чертой разделены составляющие последовательности):
a9059cbb|000000000000000000000000abcdabcdabcdabcdabcdabcdabcdabcd
abcdab00|000000000000000000000000000000000000000000000000000
0000000000001
Но если указать адрес abcdabcdabcdabcdabcdabcdabcdabcdabcdab без последнего байта, то недостающий байт адреса будет взят из следующего аргумента (amount), а в конец EVM допишет нулевой байт:
a9059cbb|000000000000000000000000abcdabcdabcdabcdabcdabcdabcdabcd
abcdab|00000000000000000000000000000000000000000000000000000
0000000000100
Получается, вместо 1 ether жертва отправит 0x100=256 ether. Защититься от такой атаки поможет только тщательная проверка параметров перед загрузкой их в блокчейн.
#shortaddress #безопасность
Для вызова функций контрактов используется ABI (Application Binary Interface) — бинарный интерфейс приложения, согласно которому вызов функции представляется в виде последовательности байтов. Первые четыре байта — это начало хеша keccak256 от подписи функции (ее название и типы входных данных), далее идут значения входных параметров.
function transfer(address to, uint amount) public returns (bool success);
Адрес в Ethereum состоит из 20 байт. Для осуществления атаки злоумышленнику нужно сгенерировать адрес, который заканчивается на нулевой байт, например abcdabcdabcdabcdabcdabcdabcdabcdabcdab00. В обычном случае вызов функции на перевод 1 ether будет выглядеть так (вертикальной чертой разделены составляющие последовательности):
a9059cbb|000000000000000000000000abcdabcdabcdabcdabcdabcdabcdabcd
abcdab00|000000000000000000000000000000000000000000000000000
0000000000001
Но если указать адрес abcdabcdabcdabcdabcdabcdabcdabcdabcdab без последнего байта, то недостающий байт адреса будет взят из следующего аргумента (amount), а в конец EVM допишет нулевой байт:
a9059cbb|000000000000000000000000abcdabcdabcdabcdabcdabcdabcdabcd
abcdab|00000000000000000000000000000000000000000000000000000
0000000000100
Получается, вместо 1 ether жертва отправит 0x100=256 ether. Защититься от такой атаки поможет только тщательная проверка параметров перед загрузкой их в блокчейн.
#shortaddress #безопасность
👍1
Безопасность. Атака force feeding
Одна из атак, которая далась мне сложно для понимания, пока я не встретил этот код контракта.
Представим, что у нас есть игра, где каждый участник может вносить по 1 Эфиру. Тот, кто внесет Эфир 7 по счету - забирает джекпот. Уязвимость здесь кроется в "address(this).balance" - проверке условия основанной на балансе контракта.
contract EtherGame {
uint public targetAmount = 7 ether;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
uint balance = address(this).balance;
require(balance <= targetAmount, "Game is over");
if (balance == targetAmount) {
winner = msg.sender;
}
}
function claimReward() public {
require(msg.sender == winner, "Not winner");
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function attack() public payable {
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
Предположим, что есть два участника, которые уже внесли по 1 Эфиру. И на балансе контракта получается 2 Эфира. Мошенник может через свой контракт отправить 5 Эфиров, чем в итоге вызовет функцию selfdestruct() в контракте игры, и застопорит контракт.
Здесь нужно понимать работу функции selfdestruct() и то, что нельзя полагаться на address(this).balance в каких-либо функциях своего контракта. Просто избегайте этого.
#forcefeeding #безопасность
Одна из атак, которая далась мне сложно для понимания, пока я не встретил этот код контракта.
Представим, что у нас есть игра, где каждый участник может вносить по 1 Эфиру. Тот, кто внесет Эфир 7 по счету - забирает джекпот. Уязвимость здесь кроется в "address(this).balance" - проверке условия основанной на балансе контракта.
contract EtherGame {
uint public targetAmount = 7 ether;
address public winner;
function deposit() public payable {
require(msg.value == 1 ether, "You can only send 1 Ether");
uint balance = address(this).balance;
require(balance <= targetAmount, "Game is over");
if (balance == targetAmount) {
winner = msg.sender;
}
}
function claimReward() public {
require(msg.sender == winner, "Not winner");
(bool sent, ) = msg.sender.call{value: address(this).balance}("");
require(sent, "Failed to send Ether");
}
}
contract Attack {
EtherGame etherGame;
constructor(EtherGame _etherGame) {
etherGame = EtherGame(_etherGame);
}
function attack() public payable {
address payable addr = payable(address(etherGame));
selfdestruct(addr);
}
}
Предположим, что есть два участника, которые уже внесли по 1 Эфиру. И на балансе контракта получается 2 Эфира. Мошенник может через свой контракт отправить 5 Эфиров, чем в итоге вызовет функцию selfdestruct() в контракте игры, и застопорит контракт.
Здесь нужно понимать работу функции selfdestruct() и то, что нельзя полагаться на address(this).balance в каких-либо функциях своего контракта. Просто избегайте этого.
#forcefeeding #безопасность
👍1
Безопасность. Атака griefing
Еще одна атака, которая трудна для понимания с первого раза, и что еще хуже, практически не заметна для новичков.
В ру сегменте я не смог найти достойной статьи про данную атаку, поэтому сделал перевод основной части отсюда.
Griefing Attack или griefing gas attack, как мы поняли из второго названия этой атаки, тесно связана с использованием газа в транзакции в момент вызова функции в другом контракте. Из-за того, что не был установлен и подтвержден лимит газа, мошенник может подключиться к функции и использовать имеющийся газ в своих целях.
Эта атака не столько приносит "радости" мошеннику, сколько может принести убытков атакуемому контракту.
Рассмотрим пример.
contract Relayer {
function relay (Target target, bytes memory _data) public returns (bool) {
(bool, success,) = address(target).call(abi.encodeWithSignature("execute(bytes)", _data));
return success;
}
}
contract Target {
bool piblic result = false;
function execute(bytes memory _data) public {
uint j = 0;
for (uint i; i < 100; i++) {
j++;
}
result = true;
}
function setresult (bool v) public {
result = v;
}
}
Если мы развернем оба контракта и вызовем в первом relay(), то все пройдет успешно. Но если мы попробуем узнать значение переменной result во втором контракте, то оно останется неизменным.
Это происходит потому, что второму контракту не хватило газа на выполнение функции. Проверить это можно, повысив gasLimit для функции в первом контракте, и вызвал result еще раз.
Мне пока сложно представить, как мошенник может использовать этот газ в своих целях, так как опыта в этом мало, но из статьи понятно, что такая утечка может привести к значительным финансовым потерям владельца первого контракта.
Как я понял из статьи, на данный момент нет 100% защиты от этой атаки, но ее шансы можно значительно сократить следующим образом.
Во-первых, проверять в первом контракте, что транзакция успешно прошла, например добавить после строки (bool success,) условие "if\else".
Во-вторых, проверять лимит газа. Например так.
В первом контракте ввести две дополнительные переменные:
uint estimateGasValue = 1000000;
uint gasNeededBetweenCalls = 5000;
а затем модифицировать функцию relay():
uint gasAvailable = gasLeft() - gasNeededBetweenCalls;
require (gasAvailable - gasAvailable \ 64 >= estimateGasValue, 'not enough gas provided');
(bool success,) = address(target).call(abi.encodeWithSignature("execute(bytes)", _data, estimateGasValue));
Решения с оценкой газа могут стать не эффективными со временем, когда цена на газ вырастет.
Лучшим решением будет позволить EVM рассчитать необходимый газ для каждого вызова и откатить транзакцию, в случае его нехватки.
#griefing #безопасность
Еще одна атака, которая трудна для понимания с первого раза, и что еще хуже, практически не заметна для новичков.
В ру сегменте я не смог найти достойной статьи про данную атаку, поэтому сделал перевод основной части отсюда.
Griefing Attack или griefing gas attack, как мы поняли из второго названия этой атаки, тесно связана с использованием газа в транзакции в момент вызова функции в другом контракте. Из-за того, что не был установлен и подтвержден лимит газа, мошенник может подключиться к функции и использовать имеющийся газ в своих целях.
Эта атака не столько приносит "радости" мошеннику, сколько может принести убытков атакуемому контракту.
Рассмотрим пример.
contract Relayer {
function relay (Target target, bytes memory _data) public returns (bool) {
(bool, success,) = address(target).call(abi.encodeWithSignature("execute(bytes)", _data));
return success;
}
}
contract Target {
bool piblic result = false;
function execute(bytes memory _data) public {
uint j = 0;
for (uint i; i < 100; i++) {
j++;
}
result = true;
}
function setresult (bool v) public {
result = v;
}
}
Если мы развернем оба контракта и вызовем в первом relay(), то все пройдет успешно. Но если мы попробуем узнать значение переменной result во втором контракте, то оно останется неизменным.
Это происходит потому, что второму контракту не хватило газа на выполнение функции. Проверить это можно, повысив gasLimit для функции в первом контракте, и вызвал result еще раз.
Мне пока сложно представить, как мошенник может использовать этот газ в своих целях, так как опыта в этом мало, но из статьи понятно, что такая утечка может привести к значительным финансовым потерям владельца первого контракта.
Как я понял из статьи, на данный момент нет 100% защиты от этой атаки, но ее шансы можно значительно сократить следующим образом.
Во-первых, проверять в первом контракте, что транзакция успешно прошла, например добавить после строки (bool success,) условие "if\else".
Во-вторых, проверять лимит газа. Например так.
В первом контракте ввести две дополнительные переменные:
uint estimateGasValue = 1000000;
uint gasNeededBetweenCalls = 5000;
а затем модифицировать функцию relay():
uint gasAvailable = gasLeft() - gasNeededBetweenCalls;
require (gasAvailable - gasAvailable \ 64 >= estimateGasValue, 'not enough gas provided');
(bool success,) = address(target).call(abi.encodeWithSignature("execute(bytes)", _data, estimateGasValue));
Решения с оценкой газа могут стать не эффективными со временем, когда цена на газ вырастет.
Лучшим решением будет позволить EVM рассчитать необходимый газ для каждого вызова и откатить транзакцию, в случае его нехватки.
#griefing #безопасность
👍1
Smart Contract Security Verification Standard
Мини-пост заметка. Нашел pdf файл на английском языке по стандартам проверки безопасности смарт контрактов. На чтение уйдет время, и если будет что-то новое, то сделаю отдельным постом.
Также есть еще реестр уязвимостей в смарт контрактах с примерами. Кому интересно, можете покопаться на досуге тут.
#безопасность #swc #weakness #standard #security #scsvs
Мини-пост заметка. Нашел pdf файл на английском языке по стандартам проверки безопасности смарт контрактов. На чтение уйдет время, и если будет что-то новое, то сделаю отдельным постом.
Также есть еще реестр уязвимостей в смарт контрактах с примерами. Кому интересно, можете покопаться на досуге тут.
#безопасность #swc #weakness #standard #security #scsvs
👍1
Безопасность и аудит
Сегодня мы продолжим говорить о безопасности смарт контрактов и поднимем тему использования сторонних программ для тестов на уязвимости.
Повторю, что эти посты не углубят вас в суть аудита, а только дадут направление для дальнейшей работы. Если подумать, то аудит - это отдельная хорошо оплачиваемая работа. И нужно долго практиковаться на реальных контрактах, чтобы постичь нюансы проверок.
Итак, для начала нужно понять сам ход проведения тестов, ведь было бы глупо после каждой написанной функции "врубать" проверку.
1. Посмотрите на популярные уязвимости и пройдитесь по своему коду еще раз.
2. Напишите в контракте комментарии к коду, если вдруг захотите отдать другими людям на проверку.
3. Напишите свои тесты в hardhat (или в той среде, где вы работаете) с solidity coverage на 100%.
4. Запустите быстрые тесты со сторонними программами slither, linters, static analysis и др.).
5. Запустите более продвинутые тесты (echidna, manticore, symbolic execution, MythX).
6. Передайте на аудит, если есть хоть "грамм" сомнений.
Даже все эти шаги не смогут гарантировать 100% безопасность вашего смарт контракта!
#безопасность #тесты
Сегодня мы продолжим говорить о безопасности смарт контрактов и поднимем тему использования сторонних программ для тестов на уязвимости.
Повторю, что эти посты не углубят вас в суть аудита, а только дадут направление для дальнейшей работы. Если подумать, то аудит - это отдельная хорошо оплачиваемая работа. И нужно долго практиковаться на реальных контрактах, чтобы постичь нюансы проверок.
Итак, для начала нужно понять сам ход проведения тестов, ведь было бы глупо после каждой написанной функции "врубать" проверку.
1. Посмотрите на популярные уязвимости и пройдитесь по своему коду еще раз.
2. Напишите в контракте комментарии к коду, если вдруг захотите отдать другими людям на проверку.
3. Напишите свои тесты в hardhat (или в той среде, где вы работаете) с solidity coverage на 100%.
4. Запустите быстрые тесты со сторонними программами slither, linters, static analysis и др.).
5. Запустите более продвинутые тесты (echidna, manticore, symbolic execution, MythX).
6. Передайте на аудит, если есть хоть "грамм" сомнений.
Даже все эти шаги не смогут гарантировать 100% безопасность вашего смарт контракта!
#безопасность #тесты
👍1
Подготовка к тестам
Вчера мне потребовалось время, чтобы разобраться с настройкой Python, так как никогда ранее не работал с ним, и появились вопросы по использованию библиотек в проекте. Но сейчас не об этом.
Зачем нам Python в смарт контрактах?
А затем, что популярные программы для анализа контрактов, типа Slither или Mythril, написаны на питоне. Поэтому дальше пост будет для тех, кто НЕ знаком с ним, и краткий для тех, кто имел с ним дело.
Для работающих с Python
Проверьте, чтобы была установлена версия выше 3.10, а также установлен pip3.
Для новичков
1. Для начала нужно скачать сам Python. При этом следите, чтобы версия была ^3.0.
Скачать можно с официального сайта тут (https://www.python.org/downloads/).
2. Затем проверьте, чтобы он у вас правильно установился и имел в своем распоряжении pip3, выполнив команды:
$ python --version
$ pip3 -- version
Там должен появиться номер установленной версии.
3. Далее рекомендуется установить компилятор для Solidity, на случай работы с разными его версиями. Это можно сделать командой:
$ pip3 install solc-select
И вот тут могут пойти проблемы.
У меня постоянно возникала ошибка "running setup.py install for pysha3 did not run successfully". Я попробовал разные советы, которые находил в гугле, но помог только один.
Нужно скачать и установить Visual Studio Build Tools 2022 (пакеты С++).
После этого еще раз попробовать выполнить команду в консоли:
$ pip3 install solc-select
Если и тут не выходит, то попробуйте так:
$ pip3 install solc-select --user
После этого у меня все заработало. Я не знаю этот язык и работу с ним. Поэтому, если вы знаете, в чем были проблемы, можете в комментариях помочь другим участникам советом или гайдом. Далее переходим к самим программам.
#python #pip3
Вчера мне потребовалось время, чтобы разобраться с настройкой Python, так как никогда ранее не работал с ним, и появились вопросы по использованию библиотек в проекте. Но сейчас не об этом.
Зачем нам Python в смарт контрактах?
А затем, что популярные программы для анализа контрактов, типа Slither или Mythril, написаны на питоне. Поэтому дальше пост будет для тех, кто НЕ знаком с ним, и краткий для тех, кто имел с ним дело.
Для работающих с Python
Проверьте, чтобы была установлена версия выше 3.10, а также установлен pip3.
Для новичков
1. Для начала нужно скачать сам Python. При этом следите, чтобы версия была ^3.0.
Скачать можно с официального сайта тут (https://www.python.org/downloads/).
2. Затем проверьте, чтобы он у вас правильно установился и имел в своем распоряжении pip3, выполнив команды:
$ python --version
$ pip3 -- version
Там должен появиться номер установленной версии.
3. Далее рекомендуется установить компилятор для Solidity, на случай работы с разными его версиями. Это можно сделать командой:
$ pip3 install solc-select
И вот тут могут пойти проблемы.
У меня постоянно возникала ошибка "running setup.py install for pysha3 did not run successfully". Я попробовал разные советы, которые находил в гугле, но помог только один.
Нужно скачать и установить Visual Studio Build Tools 2022 (пакеты С++).
После этого еще раз попробовать выполнить команду в консоли:
$ pip3 install solc-select
Если и тут не выходит, то попробуйте так:
$ pip3 install solc-select --user
После этого у меня все заработало. Я не знаю этот язык и работу с ним. Поэтому, если вы знаете, в чем были проблемы, можете в комментариях помочь другим участникам советом или гайдом. Далее переходим к самим программам.
#python #pip3
👍1
Подготовка рабочей среды для тестов
Нам потребуются несколько программ для тестов, которые следует установить через консоль. О самих программах в последующих постах подробнее.
Slither - pip3 install slither-analyzer
Mythril - pip3 install mythril
Manticore - pip install manticore
Есть и другие программы, типа Echidna или MythX, но для начала рассмотрим только указанные три.
Проверить правильность их установки можно командой
pip list
Далее, по совету друга разработчика, можно создать файл и рабочую среду, чтобы использовать эти программы не глобально, а на конкретном проекте.
Для этого создаем файл requirements.txt, куда записываем необходимые программы, которые планируем использовать в своем проекте, по типу:
solc-select== 1.0.1
slither-analyzer==0.9.0
и сохраняем файл. Можно в директории проекта.
Затем выполняем команду для создания виртуального окружения:
python -m venv venv
Потом активируем:
source venv/noscripts/activate
Возможно, эту команду потребуется выполнять каждый раз при загрузке вашего редактора кода.
И в конце выполняем подключение программ в проект:
pip install -r requirements.txt
Теперь проверить, что все у вас подключилось правильно, можно, выполнив, например, такую команду:
slither --help
Если появятся длинный список команда и опций, то все ок. Если нет - постарайтесь пройти все шаги заново.
#python #pip3 #venv
Нам потребуются несколько программ для тестов, которые следует установить через консоль. О самих программах в последующих постах подробнее.
Slither - pip3 install slither-analyzer
Mythril - pip3 install mythril
Manticore - pip install manticore
Есть и другие программы, типа Echidna или MythX, но для начала рассмотрим только указанные три.
Проверить правильность их установки можно командой
pip list
Далее, по совету друга разработчика, можно создать файл и рабочую среду, чтобы использовать эти программы не глобально, а на конкретном проекте.
Для этого создаем файл requirements.txt, куда записываем необходимые программы, которые планируем использовать в своем проекте, по типу:
solc-select== 1.0.1
slither-analyzer==0.9.0
и сохраняем файл. Можно в директории проекта.
Затем выполняем команду для создания виртуального окружения:
python -m venv venv
Потом активируем:
source venv/noscripts/activate
Возможно, эту команду потребуется выполнять каждый раз при загрузке вашего редактора кода.
И в конце выполняем подключение программ в проект:
pip install -r requirements.txt
Теперь проверить, что все у вас подключилось правильно, можно, выполнив, например, такую команду:
slither --help
Если появятся длинный список команда и опций, то все ок. Если нет - постарайтесь пройти все шаги заново.
#python #pip3 #venv
👍1