Хак Hyperlane
Обо мне
- Веду канал @findmeonchain о крипте. Там можно найти актуальные инвест идеи, забавные истории моих успехов и факапов, а также немного кода.
- Есть опыт работы Solidity разработчиком, последнее время пишу на js/ts.
TLDR
- Нашел баг в контракте Hyperlane уровня medium. С его помощью можно было отправлять средства из одной сети в другую бесплатно.
- Связался с командой и получил выплату в 5000$ на площадке Immunefi.
Как был найден баг?
Мой товарищ делает небольшие проекты, связанные с мостами. Некоторое время назад я помогал ему с контрактами для LayerZero, а в начале апреля пришла очередь Hyperlane.
Написанный контракт работал. Транзакции проходили, всё было в порядке. Однако мы заметили, что они иногда долго исполняются. Подумали, что это фича моста и забили.
На следующий день приходит товарищ и говорит:
У нас какая-то ошибка в контракте. Посмотри, у конкурентов транзакция имеет 4 трансфера эфира, а у нас один. Причем с нас не взымается плата.
Правильная транзакция через чужой контракт:
Сначала я не поверил в наличие бага, ведь транзакции проходят в обеих сетях. Но товарищ меня убедил, что мы действительно используем мост бесплатно.
Откуда он взялся?
Пришлось детально изучить различие вызовов при помощи tenderly. Тогда и стало ясно, что баг не у нас, а в контрактах Hyperlane. Тем не менее, я пошел дальше и изучил свой код. Подумал где бы могла быть ошибка. Оказалось, что я по невнимательности поставил нулевую оплату за мост.
function transferRemote(
uint32 _dstChain,
bytes32 _receiver,
uint256 _tokenId
) external payable override(TokenRouter) returns (bytes32 messageId) {
... // very important bridge checks
messageId = _transferRemote(
_dstChain,
_receiver,
_tokenId,
0 // !HERE IT IS!
);
}Согласно комментарию в коде контракта TokenRouter, написанным командой Hyperlane, параметры должны быть такими:
/**
* @notice Transfers `_amountOrId` token to `_recipient` on `_destination` domain.
* @dev Delegates transfer logic to `_transferFromSender` implementation.
* @dev Emits `SentTransferRemote` event on the origin chain.
* @param _destination The identifier of the destination chain.
* @param _recipient The address of the recipient on the destination chain.
* @param _amountOrId The amount or identifier of tokens to be sent to the remote recipient.
* @param _gasPayment The amount of native token to pay for interchain gas.
* @return messageId The identifier of the dispatched message.
*/@param _gasPayment The amount of native token to pay for interchain gas — параметр, в котором была ошибка. Туда должна идти какая-то плата за услуги моста, но я поставил 0!
Казалось бы, это точно должно выдавать ошибку транзакции, но у нас всё проходило и контракты работали.
Почему это всё тестируется на проде, а не в тестнете или локально?
Зачастую, тестнет транзакции у мостов исполняются очень медленно. Поскольку они бесплатные, проекты ставят ограничения на их исполнение. Локально же ситуация может отличаться от той, что есть в реальности. К тому же в мейннете есть удобные инструменты вроде etherscan/tenderly, через которые легче задебагать какие-то моменты. Я пришел к выводу, что лучший путь — деплоить контракты в дешевых сетях и тестировать их там.
Это не относится к логике контракта, которую можно протестировать локально. Так я тестирую только cross chain функционал.
Обращение к команде
Покопавшись в архитектуре Hyperlane я понял, что у них довольно сложная система. На изучение всех контрактов и нюансов ушло бы очень много времени, которого у меня не было. Недолго думая я пишу им в дискорд сообщение такого содержания:
I've found high/critical bug in mailbox/hook contract. User funds are not affected. Is there anyone I could talk to?
В ответ на которое мне кидают ссылку на immunefi — платформу для репорта багов и выплат наград по ним. Команда довольно быстро посмотрела мой отчет но не поняла суть (как и я в первый раз). Они тоже не заметили ничего странного!
Пришлось также на примерах им объяснить, что я совершаю транзакции бесплатно. Они подтвердили, что что-то не так и взяли больше времени на изучение.
Финал
Спустя некоторое время я получаю ответ:
After reviewing your bug report, we believe that it is in scope for our bug bounty program and the threat level is Medium.
Определение уязавимости medium уровня:
- Smart contract unable to operate due to lack of token funds
- Block stuffing for profit
- Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol) — В эту категорию попадает найденный баг
- Theft of gas — В эту категорию попадает найденный баг
- Unbounded gas consumption
В архитектуре Hyperlane есть набор контрактов-хуков, которые позволяют выполнять какую-либо особую и заданную логику. В частности, делать refuel, считать комиссии за транзакцию и тд.
Вызываются все хуки по очереди после вызова функции моста. Ни в одном из стандартных хуков не было проверок на достаточное количество оплаты перед переходом дальше, поэтому вызов доходил вплоть до самого последнего, который и оплачивал транзакцию из своего кармана (если на нем остались средства после refuel-ов). Обычно команда быстро забирала средства с контракта, однако иногда оставались 30$-50$, которыми можно было воспользоваться.
Таким образом удавалось практически бесплатно отправить транзакцию через Hyperlane — оплатив лишь комиссию сети.
Успех! 🎉
Через некоторое время росыпаюсь, проверяю телегу и понимаю, что день начался хорошо:
Списываемся с товарищем и делим награду пополам.
Итоги
Для меня это первый подобный опыт. Раньше мне никогда не удавалось найти багов в уже используемых контрактах проектов. В этой ситуации мне, безусловно, повезло обнаружить его из-за ошибки с моей стороны. Прямо ощутил себя крутым Whitehat-ом из крипто твиттера!
Спасибо команде Hyperlane за сотрудничество, приятно было с ними работать. На мой вгляд, они создают важный продукт — один из самых децентрализованных и технологически многогранных мостов (как для средств, так и сообщений), желаю им успеха в дальнейшем развитии.
Если вам понравилась статья, подписывайтесь на мой канал. Вряд ли я найду ещё один баг на проде у крупного проекта, но ещё точно будет что почитать и обсудить :)