Введение
Учитывая процветание экосистемы NFT в сети Ethereum и рост связанных с ней проблем безопасности, мы планируем выпустить серию публикаций, в которых расскажем об этих угрозах и дадим разработчикам рекомендации по защите смарт-контрактов. В этом блоге мы рассмотрим проблему перевхода (reentrancy) в NFT-контрактах и способы снижения соответствующих уязвимостей.
Что такое перевход (Reentrancy)
Согласно Wikipedia:
В вычислительной технике компьютерная программа или подпрограмма называется реентерабельной (повторно входимой), если несколько вызовов могут безопасно выполняться одновременно на нескольких процессорах или в однопроцессорной системе, где реентерабельная процедура может быть прервана в середине своего выполнения и затем безопасно вызвана снова («повторно войти») до того, как завершится выполнение её предыдущих вызовов.
В смарт-контракте перевход может произойти, когда функция выполняет внешний вызов другого контракта, который, в свою очередь, снова вызывает исходную функцию (или другие функции) до того, как первый вызов вернет управление. Если внешний вызов контролируется ненадежной сущностью (например, вредоносным контрактом), это может привести к неожиданным результатам. Это связано с тем, что в некоторых функциях результат зависит от состояния контракта. Состояние контракта должно обновляться после выполнения внешнего вызова. Однако повторный вызов функции будет использовать старое состояние вместо обновленного.
Перевход является распространенной проблемой в смарт-контрактах, например, атака на MakerDAO, уязвимость ERC777 в Uniswap и другие (ищите «Reentrancy» в BlockSec Academy).
Перевход в NFT

В NFT-контрактах существуют неявные вызовы внешних функций, о которых разработчики могут не подозревать. К ним относятся функции onERC721Received и onERC1155Received. Функция onERC721Received была разработана для проверки того, может ли принимающий контракт работать с NFT (чтобы предотвратить навсегда «заблокированные» внутри контракта NFT). Эта функция вызывается в safeTransferFrom и _safeMint в ERC721 контракте. Аналогичная функция существует в контракте ERC1155. Из-за таких вызовов внешних функций перевход может произойти незаметно для разработчиков контракта.
Перевход внутри одной функции
Перевход внутри одной функции — это наиболее простая форма атаки через перевход. В этом случае повторно вызывается та же самая функция, что была запущена изначально. Злоумышленник может вызывать функцию многократно до завершения первого вызова. В NFT-контрактах это обычно происходит в функциях, связанных с операцией mint (минтинга).
Например, некоторые NFT-проекты предоставляют каждому пользователю возможность бесплатно сминтить NFT, устанавливают максимальный объем всей коллекции или максимальное количество NFT, которое может владеть один пользователь. Обычно эти ограничения проверяются до самой операции минтинга. Однако если состояния, связанные с этими ограничениями, обновляются после вызова safeMint, злоумышленник может повторно войти в функцию минтинга и обойти ограничения, так как данные состояния при повторном вызове будут выглядеть так же, как и при первом. Атака HypeBears, описанная в нашем предыдущем блоге, является тому примером.
Более сложный случай перевхода внутри одной функции происходит, когда safeMint используется в цикле, а ограничения проверяются до начала цикла. В этом сценарии, даже если некоторые состояния будут автоматически обновлены до выполнения внешнего вызова, оставшиеся вызовы safeMint в цикле все равно могут обойти проверку, так как цикл уже запущен, а верификация происходит до начала цикла.
Например, в примере из другого поста, функция mintNFT проверяет, не превысит ли количество NFT, которые пользователь хочет получить, плюс текущий общий объем, лимит всей коллекции. Функция safeMint обновляет общий объем предложения перед внешним вызовом onERC721Received. Злоумышленник все равно может воспользоваться этим, так как safeMint увеличивает общий объем только на 1 за раз. Поэтому, если злоумышленник повторно войдет в mintNFT во время первого вызова safeMint в цикле, общий объем станет равен старому значению плюс 1, а не старому значению плюс количество NFT, которые планировалось сминтить в первом вызове mintNFT.
Перевход между функциями
Вместо повторного входа в ту же самую функцию злоумышленник может войти в другую функцию, которая использует или зависит от тех же состояний, что и исходная функция. Мы подробно разобрали некоторые из таких случаев в наших предыдущих блогах: инцидент с Revest и инцидент с OMNI.
Резюме и рекомендации
Вот несколько советов для разработчиков NFT-смарт-контрактов по снижению угрозы перевхода:
- Используйте паттерн «Проверки-Эффекты-Взаимодействия» (Checks-Effects-Interactions) в своем коде.
- Будьте осторожны при использовании сторонних библиотек, которые могут совершать внешние вызовы. Для NFT-контрактов с осторожностью относитесь к неявным обратным вызовам (callbacks) функций
onERC721ReceivedиonERC1155Received.
Читайте другие статьи из этой серии
О компании BlockSec
BlockSec — это передовая компания в сфере блокчейн-безопасности, основанная в 2021 году группой всемирно признанных экспертов по безопасности. Компания стремится повысить безопасность и удобство использования развивающегося мира Web3, чтобы способствовать его массовому внедрению. Для этого BlockSec предоставляет услуги аудита безопасности смарт-контрактов и блокчейнов EVM, платформу Phalcon для разработки с учетом безопасности и проактивного блокирования угроз, платформу MetaSleuth для отслеживания средств и расследований, а также расширение MetaSuites для эффективной работы Web3-строителей в криптомире.
На сегодняшний день компания обслужила более 300 уважаемых клиентов, таких как MetaMask, Uniswap Foundation, Compound, Forta и PancakeSwap, и привлекла десятки миллионов долларов США в двух раундах финансирования от ведущих инвесторов, включая Matrix Partners, Vitalbridge Capital и Fenbushi Capital.
Официальный сайт: https://blocksec.com/
Официальный Twitter-аккаунт: https://twitter.com/BlockSecTeam



