Утром 3 февраля (часовой пояс +8) наша система зафиксировала транзакцию атаки 0xfa97c3476aa8aeac662dae0cc3f0d3da48472ff4e7c55d0e305901ec37a2f704, направленную на NFT-контракт HypeBears. После расследования мы выяснили, что это была атака типа re-entrancy (повторный вход), вызванная функцией _safeMint стандарта ERC721.
Первопричина
В проекте установлено ограничение на количество NFT, которое может минтить один аккаунт. По сути, используется карта addressMinted, которая фиксирует, выпустил ли аккаунт NFT.
При минтинге NFT код использует функцию _safeMint из эталонной реализации OpenZeppelin. Эта функция называется «безопасной» (safe), так как она проверяет, может ли получатель принимать токены ERC721. Это предотвращает ситуацию, когда NFT отправляется на контракт, не способный обрабатывать токены ERC721. Согласно документации:
Если параметр
toуказывает на смарт-контракт, он должен реализовывать интерфейс IERC721Receiver.onERC721Received, который вызывается при безопасном переводе. Следующий код демонстрирует реализацию функции_safeMintот OZ.

Однако этот вызов внешней функции создает лазейку в безопасности. В частности, злоумышленник может выполнить повторный вызов внутри обратного вызова (callback) onERC721Received. Например, в уязвимом контракте HypeBears злоумышленник может снова вызвать функцию mintNFT внутри обратного вызова onERC721Received (поскольку запись addressMinted еще не обновлена).

Атака
На снимке экрана ниже показана транзакция атаки.

Уроки
Риски, связанные с использованием SafeMint, обсуждались исследователями безопасности link1 link2. Тем не менее, мы до сих пор встречаем уязвимый код и атаки в реальности. Как было показано в случае с safeTransfer во время инцидента безопасности QBridge, использование «безопасной» (safe) функции не гарантирует «безопасность» (safe) контракта 😃.



