Vítejte zpět u Sherlockova článku o zranitelnosti, kde upozorňujeme na závažnou zranitelnost odhalenou během Sherlockova auditu. Tento týden se zabýváme nesprávným výpočtem výše kolaterálu, který byl zjištěn v soutěži @plaza_finance ze strany @0xadrii, @KupiaSecurity, @f, @farman1094_ a @0xnovaman33.
Shrnutí chyby zabezpečení: V příkazu getRedeemAmount(...) kontrakt vypočítá úroveň kolaterálu pro odkupy BOND pomocí stavu po uskutečnění obchodu: Aktuální kód (zranitelná) collateralLevel = ((tvl - (částka vkladu * BOND_TARGET_PRICE)) * PŘESNOST) / ((bondSupply - částka vkladu) * BOND_TARGET_PRICE); Vzhledem k tomu, že při zpětném odkupu se pak tato odhadovaná úroveň zajištění používá k rozhodnutí, zda zaplatit maximální cenu (100 USD), může útočník koupit BOND za smíšené (někdy diskontované) sazby za vytvoření, dokud (současná) úroveň kolaterálu neklesne ≤ 1,2, a poté vyplatit všechny DLUHOPISY za limit 100 USD – počítáno s vytvořenou částkou vkladu, která posune odhadovanou úroveň zajištění zpět nad 1,2. To umožňuje útočníkovi extrahovat ETH ("bezrizikový" spread), dokud aktuální úroveň kolaterálu poolu nedosáhne prahové hodnoty.
Kroky útoku: 1) Fáze nastavení Parametry příkladu poolu: - poolReserve = 120 ETH, bondSupply = 3000, levSupply = 200, cena ETH = 3075 $ Cenová pravidla: Vytvořit (koupit) BOND: - Pokud ≤ kolaterál 1,2: creationRate = tvl * 0,8 / bondSupply - Jinak: creationRate = 100 $ Vyplatit (prodat) DLUHOPIS: - Pokud je odhadovaný kolaterálÚroveň ≤ 1,2: redeemRate = tvl * 0,8 / dluhopisNabídka - Jinak: redeemRate = 100 USD
2) Fáze A – Nákup dluhopisů za smíšené/diskontní ceny - Nakupte BOND a zároveň sledujte (aktuální) kolaterálÚroveň = tvl / (bondSupply * 100). - K prvnímu nákupu dochází, zatímco kolaterálÚroveň > 1,2 → mint poblíž 100 USD za DLUHOPIS. - Následné nákupy stlačí aktuální úroveň zajištění pod 1,2 → mint při diskontované míře tvorby (např. ~94,07 $), čímž se levně hromadí velký zůstatek BOND.
3) Fáze B – Vyplacení všech DLUHOPISŮ za maximální cenu - Call redeem(BOND, depositAmount = attackerBondBalance, ...) - Kontrakt vypočítá odhadovanou úroveň kolaterálu pomocí stavu po splacení (bondSupply - depositAmount) a (tvl - depositAmount * 100) a (v příkladu) získá hodnotu > 1,2 - Protože tato odhadovaná hodnota překračuje prahovou hodnotu, redeemRate je nastavena na maximální hodnotu 100 USD, což útočníkovi umožňuje vybrat všechny nahromaděné dluhopisy za 100 USD.
4) Realizace zisku - Rozdíl mezi diskontovanými nákupy a prodeji za 100 USD přináší čistý zisk ETH. - V číslech PoC: utracení 60 ETH během dvou nákupů přináší ~61,89 ETH při zpětném odkupu → ~1,89 ETH zisk. - Útočník může iterovat, dokud aktuální úroveň zajištění poolu neklesne na ~1,2, a extrahuje zhruba: extrahovatelné USD ≈ ethPrice × poolReserve − 120 × bondSupply
Jaký to bude mít dopad? Přímá extrakce prostředků / únik hodnoty: Útočník razí za zvýhodněné sazby a vyplácí za 100 USD, čímž odčerpává ETH z poolu. Neomezeno do prahové hodnoty: Může pokračovat, dokud se aktuální úroveň zajištění poolu nesníží na ≈ 1,2. Systémová cenová nekonzistence: Vytváří arbitráž, za kterou poctiví uživatelé platí prostřednictvím horších rezerv/výsledků poolu.
Hlavní příčina: Použití poobchodního stavu pro ocenění odkupu Kód vypočítá collateralLevel, jako by k odkupu již došlo: použití (tvl - částka vkladu*100) a (bondSupply - částka vkladu) collateralLevel = ((tvl - (částka vkladu * BOND_TARGET_PRICE)) * PŘESNOST) / ((bondSupply - částka vkladu) * BOND_TARGET_PRICE); 1. To umožňuje útočníkovi zvolit depositAmount tak, aby odhadovaná úroveň překročila prahovou hodnotu (> 1,2), čímž se odemkne sazba redemptionRate 100 USD, i když by to aktuální stav poolu neodůvodňoval. 2. Bránění prahových hodnot u manipulované metriky Rozhodnutí o stropu 100 USD závisí na této manipulovatelné odhadované úrovni kolaterálu, nikoli na aktuální (předobchodní) úrovni, což umožňuje hraní cen.
Zmírnění rizik: Vypočítejte cenu za odkup z aktuálního stavu fondu, nikoli z odhadovaných zůstatků po vyplacení. Navrhovaná oprava projektu je správná: - collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) - / ((bondSupply - částka vkladu) * BOND_TARGET_PRICE); + collateralLevel = (tvl * PRECISION) / (bondSupply * BOND_TARGET_PRICE);
Dodatečné kalení (doporučeno): 1. Monotónnost ceny: Zajistěte, aby se cena za vyplacení nezvýšila se zvyšující se částkou vkladu (žádné "prodejte více, získejte lepší jednotkovou cenu"). 2. Ceny založené na invariantech: Odvodit vytvořit/vyplatit z jednoho invariantního účtu, aby symetrie nákupu/prodeje zabránila jednostranné arbitráži. 3. Kontrola skluzu: Vyžaduje uživatelem zadané minimální/maximální sazby pro vytvoření i uplatnění. 4. Omezení a omezení: Limity pro vyplacení na výkon a na blok pro omezení škod, pokud jsou dosaženy prahové hodnoty. 5. Konzistence napříč cestami: Zarovnejte cesty pro vytváření a uplatňování tak, aby používaly stejnou definici úrovně kolaterálu (pouze aktuální stav).
Jsme hrdí na to, že jsme tímto objevem pomohli zajistit @plaza_finance. Když je to nezbytně nutné zabezpečit, je Sherlock tou správnou volbou.
2,79K