Актуальні теми
#
Bonk Eco continues to show strength amid $USELESS rally
#
Pump.fun to raise $1B token sale, traders speculating on airdrop
#
Boop.Fun leading the way with a new launchpad on Solana.
Ласкаво просимо назад до Sherlock's Vulnerability Spotlight, де ми висвітлюємо вражаючу вразливість, виявлену під час аудиту Шерлока.
Цього тижня ми розглянемо неправильний розрахунок рівня застави, знайдений у конкурсі @plaza_finance за @0xadrii, @KupiaSecurity, @f,
@farman1094_ і @0xnovaman33.

Короткий опис вразливості:
У getRedeemAmount(...) контракт обчислює рівень застави для погашення BOND, використовуючи стан після угоди:
поточний код (вразливий)
collateralLevel =
((tvl - (depositAmount * BOND_TARGET_PRICE)) * ТОЧНІСТЬ)
/ ((bondSupply - сума депозиту) * BOND_TARGET_PRICE);
Оскільки ціноутворення викупу потім використовує цей розрахунковий рівень забезпечення, щоб вирішити, чи платити максимальну ціну (100 доларів США), зловмисник може купити BOND за змішаними (іноді дисконтованими) ставками створення, доки (поточний) рівень застави не впаде ≤ 1,2, а потім викупити всі BOND за обмеженням у 100 доларів США, розраховуючи за допомогою створеної суми депозиту, яка піднімає розрахунковий рівень застави вище 1,2. Це дозволяє зловмиснику отримувати ETH («безризиковий» спред), доки поточний рівень застави пулу не досягне порогу.
Етапи атаки:
1) Етап налаштування
Приклад параметрів пулу:
- poolReserve = 120 ETH, bondSupply = 3000, levSupply = 200, ціна ETH = $3075
Правила ціноутворення:
Створити (купити) ОБЛІГАЦІЇ:
- Якщо collateralLevel ≤ 1.2: creationRate = tvl * 0.8 / bondSupply
- Інакше: creationRate = $100
Погасити (продати) ОБЛІГАЦІЇ:
- Якщо розрахунковий рівень застави ≤ 1,2: redeemRate = tvl * 0,8 / bondSupply
- Інакше: redeemRate = $100
2) Фаза А – купівля BOND за змішаними/дисконтованими ставками
- Купівля BOND при моніторингу (поточного) рівня застави = tvl / (bondSupply * 100).
- Перша покупка відбувається, коли застава рівня > 1,2 → карбується близько $100 за BOND.
- Подальші покупки піднімають поточний рівень застави нижче 1,2 → карбують при дисконтованому курсі створення (наприклад, ~$94,07 in), дешево накопичуючи великий баланс BOND.
3) Фаза B – Погасіть усі ОБЛІГАЦІЇ за максимальною ціною
- Погашення виклику (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.
Системна неузгодженість ціноутворення: створює арбітраж, за який чесні користувачі платять через гірші резерви/результати пулу.
Першопричина:
Використання стану після торгівлі для погашення цін
Код обчислює рівень застави так, ніби викуп вже відбувся:
використовує (tvl - depositAmount*100) та (bondSupply - depositAmount)
collateralLevel =
((tvl - (depositAmount * BOND_TARGET_PRICE)) * ТОЧНІСТЬ)
/ ((bondSupply - сума депозиту) * BOND_TARGET_PRICE);
1. Це дозволяє зловмиснику вибрати depositAmount таким чином, щоб передбачуваний рівень перевищував поріг (> 1.2), розблоковуючи redeemRate у розмірі 100 доларів США, навіть якщо поточний стан пулу не виправдовує його.
2. Порогове визначення за маніпульованою метрикою
Рішення про обмеження в 100 доларів США залежить від цього маніпульованого розрахункового рівня застави, а не від поточного рівня (до торгівлі), що дозволяє грати в цінові ігри.
Пом'якшення наслідків:
Обчисліть ціну викупу на основі поточного стану пулу, а не приблизного балансу після викупу. Запропоноване проєктом виправлення є правильним:
- collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * ТОЧНІСТЬ)
- / ((bondSupply - depositAmount) * BOND_TARGET_PRICE);
+ collateralLevel = (tvl * PRECISION) / (bondSupply * BOND_TARGET_PRICE);
Додаткове загартовування (рекомендується):
1. Монотонність ціни: переконайтеся, що ціна викупу не збільшується зі збільшенням суми депозиту (без «продай більше, отримай кращу ціну за одиницю»).
2. Ціноутворення на основі інваріантів: Похідне створення/викуп з одного інваріанта, щоб симетрія купівлі/продажу запобігала односторонньому арбітражу.
3. Перевірки прослизання: вимагайте надані користувачем мінімальні/максимальні ставки як для створення, так і для використання.
4. Ковпачок і дросельна заслінка: ковпачки погашення за tx і за блок для обмеження шкоди в разі наближення порогових значень.
5. Узгодженість між контурами: Вирівняйте створення та активацію шляхів, щоб використовувати однакове визначення рівня застави (лише поточний стан).
Ми пишаємося тим, що допомогли убезпечити @plaza_finance завдяки цьому відкриттю.
Коли це конче потрібно убезпечити, Шерлок – правильний вибір.
3,27K
Найкращі
Рейтинг
Вибране

