0x.1 Предыстория
15 октября 2021 года в 02:38 (UTC+8) наша внутренняя система мониторинга зафиксировала подозрительные транзакции с использованием флэш-кредитов (flashloan):

После расследования мы обнаружили, что это была атака с манипулированием ценами, направленная на Indexed Finance. В частности, злоумышленник осуществил атаку, воспользовавшись ошибочной формулой (используемой для расчета цены) этого проекта, и получил прибыль в размере 16 миллионов долларов США.
В социальных сетях уже существуют определенные обсуждения, а сам проект выпустил официальный отчет об инциденте (Indexed Attack Post-Mortem). Тем не менее, существующие анализы не дают полного понимания этого инцидента безопасности. Поэтому в этом блоге мы стремимся предоставить всесторонний анализ, включая механизм работы проекта, уязвимость, саму атаку и полученную прибыль.
0x1.1 Соответствующие адреса контрактов
-
MarketCapSqrtController: 0x120c6956d292b800a835cb935c9dd326bdb4e011
-
DEFI5: 0xfa6de2697d59e88ed7fc4dfe5a33dac43565ea41
-
CC10: 0x17ac188e09a7890a1844e5e65471fe8b0ccfadf3
0x1.2 Транзакции атаки
-
Атака TX-I: 0x44aad3b853866468161735496a5d9cc961ce5aa872924c5d78673076b1cd95aa
-
Атака TX-II: 0xbde4521c5ac08d0033019993b0e7e1d29b1457e80e7743d318a3c27649ca4417
0x2. Механизм работы Indexed Finance
Чтобы лучше понять уязвимость/атаку, мы воспользуемся DEFI5 (т.е. пулом, который был взломан) для демонстрации механизма работы Indexed Finance.
0x2.1 Токен привязки
DEFI5 разработан для предоставления торговых услуг для Топ-5 токенов DeFi-проектов в сети Ethereum. В частности, Indexed Finance обновляет рейтинги токенов на основе их рыночной капитализации через MarketCapSqrtController. Поскольку состав Топ-5 токенов может меняться со временем, количество токенов, используемых пулом DEFI5, может быть больше 5, как показано в следующем коде:

Рисунок 1 показывает, что для привязки нового токена DEFI5 должен вызвать функцию _bind, которая запускается функцией reindexTokens. Та, в свою очередь, может быть вызвана только функцией reindexPools контракта MarketCapSqrtController:

Рисунок 2 демонстрирует, что MarketCapSqrtController сначала получает информацию о токене (включая общий объем предложения TotalSupply и цену) с рынков, а затем рассчитывает рейтинг на основе их рыночной капитализации.
При вызове функции reindexPool адреса топовых токенов передаются в качестве аргументов для вызова функции reindexTokens.
Обратите внимание, что новый добавленный токен будет привязан к DEFI5 без замены исходных токенов пула.
0x2.2 Что происходит далее?
После привязки токена DEFI5 должен установить переменную с именем ready (которая определяет статус токена) в значение true, чтобы активировать торговлю:

Согласно логике кода, помимо инициализации контракта, статус ready может быть установлен только в функции gulp.
Как показано на Рисунке 3, это происходит, когда баланс токенов в DEFI5 становится больше или равен _minimumBalances.
В это же время начальный вес токена (т.е. denorm) будет рассчитан по следующей формуле:

0x3. Анализ уязвимости
Уязвимый код находится в функции updateMinimumBalance контракта MarketCapSqrtController.

Как показано на Рисунке 4, updateMinimumBalance может изменить minimumBalance токена, чей статус ready равен false, до 1/100 от poolValue. Расчет poolValue является ключом к данной уязвимости.

Расчет на Рисунке 5 просто реализует следующую формулу:

Однако в этой формуле существуют две потенциальные проблемы:
- использование ликвидности одного токена для оценки стоимости всего пула;
- веса пула (
_totalWeight) и токена (token.denorm) не зависят от изменения ликвидности. На самом деле на них влияет рыночная капитализация на внешних рынках. Кроме того, их изменение ограничено временным периодом (т.е. увеличение или уменьшение на 1% в час).
Короче говоря, злоумышленник может манипулировать poolValue, используя флэш-кредит для мгновенного значительного изменения ликвидности одного токена, в то время как веса пула и токена остаются неизменными.
Таким образом, minimumBalance токена (чей статус ready равен false) может быть искажен для осуществления атаки манипулирования ценами.
0x4. Анализ атаки
Атака состоит из следующих 9 шагов:
Шаг 1: вызов функции reindexPool для привязки SUSHI. Перед этим вызовом в пуле DEFI5 находится 6 токенов: UNI, AAVE, COMP, SNX, CRV и MKR. Поскольку рыночная капитализация SUSHI входит в Топ-5, SUSHI добавляется в пул.

Шаг 2: заимствование всех 6 токенов (UNI, AAVE, COMP, SNX, CRV и MKR), поддерживаемых IndexPool, через SushiSwap.

Шаг 3: обмен UNI путем многократного вызова функции swapExactAmountIn с использованием заемных токенов (например, COMP).
Примечание 1: многократные вызовы вызваны ограничением
MAX_IN_RATIO, можно обменять максимум половину баланса токена.Примечание 2: на этом шаге
poolValueбудет сильно недооценена, так как баланс UNI (какfirstToken) в пуле значительно уменьшается.

Шаг 4: изменение minimumBalance для SUSHI путем вызова функции updateMinimumBalance.
Заметьте, что
minimumBalanceбудет меньше обычного значения из-за аномально рассчитанной на шаге 3poolValue.

Шаг 5: подготовка LP-токенов путем вызова функции joinswapExternAmountIn для предоставления ликвидности. Мы увидим, что эти LP-токены используются для выкупа большего количества SUSHI.
Примечание: функцию
joinswapExternAmountInтакже нужно вызвать несколько раз из-за ограниченияMAX_IN_RATIO.

Шаг 6: манипулирование весом SUSHI в пуле DEFI5 путем заимствования огромного количества SUSHI и перевода их в пул, а затем вызова функции gulp для установки статуса ready для SUSHI в true. Таким образом, начальный вес SUSHI (denorm) становится очень высоким.

Шаг 7: обмен LP-токенов обратно на базовые токены (UNI, AAVE, COMP, SNX, CRV, MKR и SUSHI) путем вызова функции exitPool.
Заметьте, что функция
exitPoolНЕ учитывает вес каждого токена, в результате чего базовые токены возвращаются в равной пропорции.
Шаг 8: обмен LP-токенов путем вызова функции joinswapExternAmountIn с использованием SUSHI для предоставления ликвидности. Можно получить больше LP-токенов из-за аномального веса SUSHI в этот момент.


Обратите внимание, что функция joinswapPoolAmountIn выпустит (mint) LP-токены на основе веса полученного базового токена (в данном случае SUSHI).
Шаг 9: вывод средств из пула путем вызова функции exitPool с LP-токенами, полученными на шаге 8.
0x5. Анализ прибыли
Наше расследование показало, что все средства (включая транзакционные комиссии), использованные злоумышленником, поступили из Tornado Cash.
Всего было совершено две транзакции атаки:
-
В первой транзакции злоумышленник получил: 6,226.8 AAVE, 15 ETH, 192,358.6 UNI, 5,459.5 COMP, 721,611.3 CRV, 16,680.6 SNX, 406.5 MKR.
-
Во второй транзакции злоумышленник получил: 109.6 MKR, 17,844 UMA, 1,002.4 COMP, 34,602.5 UNI, 131,645.4 BAT, 28,754.1 SNX, 1,273.6 AAVE, 124,194.2 CRV, 33,215.4 LINK, 5.24 YFI.
Кроме того, у злоумышленника было несколько неудачных попыток.
На момент написания этой статьи прибыль, полученная злоумышленником, оценивается в 16 миллионов долларов США и еще не была выведена.
О компании 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



