Bine ați revenit la Sherlock's Vulnerability Spotlight, unde evidențiem o vulnerabilitate de impact descoperită în timpul unui audit Sherlock. Săptămâna aceasta, examinăm un calcul incorect al nivelului de garanție găsit în concursul @plaza_finance de @0xadrii, @KupiaSecurity, @f, @farman1094_ și @0xnovaman33.
Rezumatul vulnerabilității: În getRedeemAmount(...), contractul calculează nivelul de garanție pentru răscumpărările BOND folosind starea post-tranzacționare: Codul curent (vulnerabil) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECIZIE) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); Deoarece prețul de răscumpărare folosește apoi acest nivel de garanție estimat pentru a decide dacă să plătească prețul maxim (100 USD), un atacator poate cumpăra BOND la rate de creare mixte (uneori reduse) până când nivelul de garanție (curent) scade ≤ 1,2 și apoi răscumpără toate BOND la limita de 100 USD - calculând cu un depositAmount creat care împinge nivelul de garanție estimat înapoi peste 1,2. Acest lucru permite atacatorului să extragă ETH ("spread-free risk") până când nivelul actual de garanție al pool-ului atinge pragul.
Pașii de atac: 1) Faza de configurare Parametri de exemplu de bazin: - poolReserve = 120 ETH, bondSupply = 3000, levSupply = 200, preț ETH = 3075 USD Reguli de prețuri: Creați (cumpărați) OBLIGAȚIUNI: - Dacă collateralLevel ≤ 1.2: creationRate = tvl * 0.8 / bondSupply - Altfel: creationRate = 100 USD Răscumpărați (vindeți) OBLIGAȚIUNI: - Dacă garanția estimatăNivel ≤ 1.2: redeemRate = tvl * 0.8 / bondSupply - Altfel: redeemRate = 100 USD
2) Faza A – Cumpărați OBLIGAȚIUNI la rate mixte/reduse - Cumpărați BOND în timp ce monitorizați (curent) collateralLevel = tvl / (bondSupply * 100). - Prima cumpărare are loc în timp ce collateralLevel > 1,2 → se apropie de 100 USD per BOND. - Achizițiile ulterioare împing collateralLevel curent sub 1,2 → monetă la o rată de creare redusă (de exemplu, ~ 94,07 USD), acumulând un sold mare de obligațiuni ieftin.
3) Faza B – Răscumpărați toate OBLIGAȚIUNILE la prețul maxim - Răscumpărare apel(BOND, depositAmount = atacantBondBalance, ...) - Contractul calculează nivelul estimat al garanției folosind starea post-răscumpărare (bondSupply - depositAmount) și (tvl - depositAmount * 100) și (în exemplu) obține o valoare > 1,2 - Deoarece valoarea estimată depășește pragul, redeemRate este setat la maximum 100 USD, permițând atacatorului să încaseze toate OBLIGAȚIUNILE acumulate la 100 USD
4) Realizarea profitului - Diferența dintre achizițiile reduse și vânzările de 100 USD produce profit net ETH. - În cifrele PoC: cheltuirea a 60 ETH în două achiziții returnează ~61,89 ETH la răscumpărare → ~1,89 ETH profit. - Atacatorul poate itera până când nivelul actual de garanție al pool-ului scade la ~1,2, extrângând aproximativ: extragibile USD ≈ ethPrice × poolReserve − 120 × bondSupply
Care este impactul? Extragerea directă a fondurilor / scurgerea valorii: Atacatorul emite la rate reduse și răscumpără la 100 USD, sifonând ETH din fond. Nelimitat până la prag: Poate continua până când nivelul actual de garanție al pool-ului se micșorează la ≈ 1,2. Inconsecvența sistemică a prețurilor: Creează arbitraj pentru care utilizatorii onești plătesc prin rezerve/rezultate mai proaste.
Cauza principală: Utilizarea stării post-tranzacționare pentru răscumpărarea prețurilor Codul calculează collateralLevel ca și cum răscumpărarea a avut loc deja: utilizări (tvl - depositAmount*100) și (bondSupply - depositAmount) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECIZIE) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); 1. Acest lucru permite unui atacator să aleagă depositAmount astfel încât nivelul estimat să depășească pragul (> 1.2), deblocând redemptionRate de 100 USD, chiar și atunci când starea curentă a pool-ului nu ar justifica acest lucru. 2. Limitarea pragului pe o metrică manipulată Decizia privind plafonul de 100 USD depinde de acest nivel de garanție estimat manipulabil, mai degrabă decât de nivelul actual (pre-tranzacționare), permițând jocul de preț.
Atenuarea: Calculați prețul de răscumpărare din starea curentă a pool-ului, nu din soldurile estimate post-răscumpărare. Remedierea sugerată de proiect este corectă: - collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECIZIE) - / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); + collateralLevel = (tvl * PRECIZIE) / (bondSupply * BOND_TARGET_PRICE);
Întărire suplimentară (recomandată): 1. Monotonia prețului: Asigurați-vă că prețul de răscumpărare nu crește pe măsură ce depositSuma crește (fără "vindeți mai mult, obțineți un preț unitar mai bun"). 2. Prețuri bazate pe invariante: Derivați creați/răscumpărați dintr-un singur invariant, astfel încât simetria de cumpărare/vânzare să prevină arbitrajul unilateral. 3. Verificări de alunecare: Solicitați rate minime/maxime furnizate de utilizator atât pentru creare, cât și pentru valorificare. 4. Cap & throttle: Limite de răscumpărare per tx și per bloc pentru a limita daunele dacă se apropie pragurile. 5. Consecvență între căi: Aliniați căile de creare și răscumpărare pentru a utiliza aceeași definiție a nivelului de garanție (numai starea curentă).
Suntem mândri că am ajutat la asigurarea @plaza_finance prin această descoperire. Când trebuie neapărat să fie sigur, Sherlock este alegerea potrivită.
3,27K