Добро пожаловать обратно в «В центре внимания уязвимостей Шерлока», где мы подчеркиваем значимую уязвимость, обнаруженную во время аудита Шерлока. На этой неделе мы рассматриваем неправильный расчет уровня залога, найденный в конкурсе @plaza_finance от @0xadrii, @KupiaSecurity, @f, @farman1094_ и @0xnovaman33.
Резюме уязвимости: В функции getRedeemAmount(...) контракт вычисляет уровень залога для выкупа BOND, используя состояние после сделки: // текущий код (уязвимый) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); Поскольку цена выкупа затем использует этот оцененный уровень залога для решения о том, платить ли максимальную цену (100 долларов), злоумышленник может купить BOND по смешанным (иногда со скидкой) ставкам создания, пока (текущий) уровень залога не упадет ≤ 1.2, а затем выкупить все BOND по потолку в 100 долларов — рассчитывая с помощью специально подобранной суммы депозита, которая поднимает оцененный уровень залога обратно выше 1.2. Это позволяет злоумышленнику извлекать ETH ("безрисковую" разницу) до тех пор, пока текущий уровень залога пула не достигнет порога.
Шаги атаки: 1) Этап настройки Пример параметров пула: - poolReserve = 120 ETH, bondSupply = 3000, levSupply = 200, цена ETH = $3075 Правила ценообразования: Создание (покупка) BOND: - Если уровень залога ≤ 1.2: creationRate = tvl * 0.8 / bondSupply - В противном случае: creationRate = $100 Выкуп (продажа) BOND: - Если оценочный уровень залога ≤ 1.2: redeemRate = tvl * 0.8 / bondSupply - В противном случае: redeemRate = $100
2) Фаза A – Покупка BOND по смешанным/скидочным ставкам - Покупайте BOND, следя за (текущим) уровнем залога = tvl / (bondSupply * 100). - Первая покупка происходит, когда уровень залога > 1.2 → чеканка около $100 за BOND. - Последующие покупки снижают текущий уровень залога ниже 1.2 → чеканка по сниженной ставке создания (например, ~$94.07), накапливая большой баланс BOND по низкой цене.
3) Фаза B – Выкупить все BOND по максимальной цене - Вызов redeem(BOND, depositAmount = attackerBondBalance, ...) - Контракт вычисляет оценочный уровень залога, используя состояние после выкупа (bondSupply - depositAmount) и (tvl - depositAmount * 100) и (в примере) получает значение > 1.2 - Поскольку это оценочное значение превышает порог, redeemRate устанавливается на максимальные $100, позволяя атакующему вывести все накопленные BOND по $100
4) Реализация прибыли - Разница между покупками со скидкой и продажами на $100 дает чистую прибыль в ETH. - В числах PoC: потратив 60 ETH на две покупки, вы получите ~61.89 ETH при выкупе → ~1.89 ETH прибыли. - Нападающий может повторять процесс, пока текущий уровень залога пула не упадет до ~1.2, извлекая примерно: извлекаемый USD ≈ ethPrice × poolReserve − 120 × bondSupply
Каков эффект? Прямое извлечение средств / утечка стоимости: Нападающий создает токены по сниженным ставкам и выкупает их за $100, откачивая ETH из пула. Неограниченное до порога: Может продолжаться до тех пор, пока текущий уровень залога пула не уменьшится до ≈ 1.2. Системная ценовая несогласованность: Создает арбитраж, за который честные пользователи платят через худшие резервы/результаты пула.
Коренная причина: Использование состояния после сделки для оценки выкупов Код вычисляет collateralLevel так, как будто выкуп уже произошел: // использует (tvl - depositAmount*100) и (bondSupply - depositAmount) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); 1. Это позволяет злоумышленнику выбрать depositAmount таким образом, чтобы оцененный уровень пересек порог (> 1.2), разблокируя $100 redeemRate, даже когда текущее состояние пула этого не оправдывает. 2. Пороговое ограничение на манипулируемую метрику Решение о лимите в $100 зависит от этого манипулируемого оцененного уровня залога, а не от текущего (предварительной сделки) уровня, что позволяет манипулировать ценами.
Смягчение: Вычисляйте цену выкупа на основе текущего состояния пула, а не предполагаемых остатков после выкупа. Предложенное проектом исправление верно: - collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) - / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); + collateralLevel = (tvl * PRECISION) / (bondSupply * BOND_TARGET_PRICE);
Дополнительное усиление (рекомендуется): 1. Монотонность цен: Убедитесь, что цена выкупа не увеличивается с увеличением суммы депозита (нет "продавайте больше, получайте лучшую цену за единицу"). 2. Ценообразование на основе инвариантов: Получите создание/выкуп из одного инварианта, чтобы симметрия покупки/продажи предотвратила односторонний арбитраж. 3. Проверки проскальзывания: Требуйте от пользователя предоставления минимальных/максимальных ставок как для создания, так и для выкупа. 4. Ограничение и дросселирование: Ограничения на выкуп по каждой транзакции и по каждому блоку, чтобы ограничить ущерб, если пороги будут достигнуты. 5. Согласованность по путям: Согласуйте пути создания и выкупа, чтобы использовать одно и то же определение уровня обеспечения (только текущее состояние).
Мы гордимся тем, что помогли обеспечить безопасность @plaza_finance благодаря этому открытию. Когда безопасность абсолютно необходима, Шерлок — правильный выбор.
3,27K