0x.1 Предисловие
15 ноября 2021 года наша внутренняя система мониторинга обнаружила подозрительные транзакции с использованием флэш-лоанов (flashloan) в сети BSC. После расследования мы выяснили, что это была атака на Nerve Bridge, включая пулы MetaPools для fUSDT и UST.

На момент написания данной статьи злоумышленник исчерпал ликвидность fUSDT и UST в стейкинг-пулах Nerve и получил прибыль в размере 900 BNB.
Удивительно, но мы обнаружили, что уязвимый код является форком Saddle.Finance, что уже привело к потере 800 миллионов долларов на Synapse Bridge 6 ноября 2021 года. В частности, первопричина уязвимости кроется в несогласованной реализации расчетов объема обмена токенов в различных библиотеках.
Тем не менее, в открытом доступе нет ни одного отчета, анализирующего этот инцидент безопасности. Поэтому в данном блоге мы стремимся предоставить всесторонний анализ, включая механизм работы проекта, саму уязвимость и ход атаки.
0x2. Предыстория
0x2.1 Что такое MetaPool?
В основном Curve предоставляет два типа пулов для обмена стейблкоинов: стандартный пул StableSwap (Standard StableSwap Pool) и MetaPool. Первый представляет собой полноценный AMM для создания кросс-рынков между различными стейблкоинами [1]. Это наиболее широко используемый тип пула, например, Curve.3pool, состоящий из DAI, USDC и USDT. Однако такой пул не может изолировать риски между стейблкоинами, что может привести к огромным убыткам у LP-провайдеров.
Для решения этой проблемы был предложен MetaPool.
Как заявляет Curve [2], «он позволяет объединять один токен со всеми токенами в другом (базовом) пуле без размытия его ликвидности». По сути, это пул обмена между стейблкоином и LP-токеном стандартного пула StableSwap (который состоит из нескольких других стейблкоинов). В нашем контексте мы называем эти два типа стейблкоинов пульный стейблкоин (pool stablecoin) и базовый стейблкоин (underlying stablecoin) соответственно.
Например, одной из жертв этого инцидента стал MetaPool из fUSDT и LP-токена Nerve.3pool (включающего BUSD, USDT и USDC), и структура этого пула по сути представляет собой [fUSDT, LP-токен (BUSD, USDT, USDC)]. Таким образом, fUSDT является пульным стейблкоином, а BUSD, USDT и USDC — базовыми стейблкоинами.

0x2.2 Источник уязвимого кода
MetaPool от Curve реализован на Vyper. Для поддержки разработки на Solidity команда Saddle.Finance переписала код на Solidity. Это стало отправной точкой данной уязвимости, код был скопирован и внедрен в Synapse и Nerve соответственно. 6 ноября проект Synapse был атакован.

Из MetaPool было выведено около 8,2 млн долларов, хотя фактически средства не были потеряны из-за «глупой» ошибки, допущенной злоумышленником [3].
После этого Saddle.Finance приняла экстренные меры для обеспечения сохранности своих средств, приостановив все контракты MetaPool. Однако Nerve Bridge не предпринял никаких действий, что неизбежно привело к данному инциденту безопасности.
Соответствующие адреса контрактов перечислены ниже:
- MetaSwap: 0xd0fBF0A224563D5fFc8A57e4fdA6Ae080EbCf3D3
- SwapUtils: 0x02338Ee742ddCDe44488640F4edf1Aa947E670E7
0x3. Анализ уязвимости
В MetaPool есть две важные функции: swap и swapUnderlying. Первая используется для обмена LP-токена и пульного стейблкоина, а вторая — для обмена пульного стейблкоина и базовых стейблкоинов.


Однако эти две функции реализованы несогласованно. Как показано на двух рисунках выше, фрагмент кода в красном прямоугольнике используется для корректировки стоимости LP-токена путем измерения «виртуальной цены» LP-токена (которая увеличивается с базового значения 1 по мере накопления комиссий). В то же время функция swap игнорирует влияние виртуальной цены, что означает, что стоимость LP-токена будет недооценена. Другими словами, можно обменять большее количество LP-токенов.
В результате становится возможным получить больше пульных стейблкоинов, сначала вернув ликвидность базовых стейблкоинов с помощью соответствующего LP-токена, а затем обменивая пульные стейблкоины путем вызова функции swapUnderlying.
0x4. Анализ атаки
Мы воспользуемся образцовой транзакцией в качестве примера для иллюстрации атаки.

Рисунок 6 показывает, что злоумышленник предпринял следующие пять шагов для осуществления атаки:
- Шаг 1: заимствование 50 000 BUSD через флэш-лоан у Fortube.
- Шаг 2: обмен 50 000 BUSD на 50 351 fUSDT через Ellipsis.
- Шаг 3: вызов функции
swapв MetaSwap для обмена 50 351 fUSDT на 36 959 Nerve 3-LP с относительно большим проскальзыванием. - Шаг 4: вызов функции
removeLiquidityOneCoinв Nerve.3pool с использованием LP-токенов (полученных на предыдущем шаге) для извлечения ликвидности BUSD, т.е. 37 071 BUSD. - Шаг 5: вызов функции
swapUnderlyingв MetaSwap для обмена BUSD на fUSDT, в результате чего получено 51 494 fUSDT.
Злоумышленник многократно повторял вышеуказанные пять шагов (более 200 транзакций), чтобы истощить ликвидность MetaPool, и в конечном итоге присвоил 900 BNB.
Интересно, что злоумышленник использовал тот же подход, что и в инциденте с Synapse, который не является оптимизированным способом достижения цели. Альтернативно, можно было провести атаку более эффективно, например, применив оптимизированные параметры для вывода ликвидности за одну транзакцию. Результат предполагает, что злоумышленник, возможно, НЕ до конца понял первопричину этой уязвимости.
Ссылки
[1] https://curve.fi/files/stableswap-paper.pdf
[2] https://resources.curve.fi/lp/depositing/depositing-into-a-metapool/
[3] https://synapseprotocol.medium.com/11-06-2021-post-mortem-of-synapse-metapool-exploit-3003b4df4ef4
Авторы: Hailin Wang, Lei Wu, Yajin Zhou @BlockSec
Twitter: https://twitter.com/BlockSecTeam



