Witamy z powrotem w Sherlock’s Vulnerability Spotlight, gdzie podkreślamy istotną lukę wykrytą podczas audytu Sherlocka. W tym tygodniu przyglądamy się błędnemu obliczeniu poziomu zabezpieczenia, które znaleziono w konkursie @plaza_finance przez @0xadrii, @KupiaSecurity, @f, @farman1094_ i @0xnovaman33.
Podsumowanie podatności: W funkcji getRedeemAmount(...) kontrakt oblicza poziom zabezpieczenia dla wykupu BOND, używając stanu po transakcji: // aktualny kod (podatny) poziomZabezpieczenia = ((tvl - (kwotaDepozytu * CENA_CELU_BOND)) * PRECYZJA) / ((podażBOND - kwotaDepozytu) * CENA_CELU_BOND); Ponieważ wycena wykupu używa następnie tego oszacowanego poziomu zabezpieczenia do podjęcia decyzji, czy zapłacić maksymalną cenę (100 USD), atakujący może kupić BOND po mieszanych (czasami zniżkowych) stawkach tworzenia, aż (aktualny) poziom zabezpieczenia spadnie ≤ 1.2, a następnie wykupić wszystkie BOND po limicie 100 USD—obliczając z wykorzystaniem starannie dobranej kwoty depozytu, która podnosi oszacowany poziom zabezpieczenia z powrotem powyżej 1.2. To pozwala atakującemu na wyciągnięcie ETH („bezryzykownej” marży), aż aktualny poziom zabezpieczenia puli osiągnie próg.
Kroki ataku: 1) Faza przygotowawcza Przykładowe parametry puli: - poolReserve = 120 ETH, bondSupply = 3000, levSupply = 200, cena ETH = $3075 Zasady wyceny: Utwórz (kup) BOND: - Jeśli collateralLevel ≤ 1.2: creationRate = tvl * 0.8 / bondSupply - W przeciwnym razie: creationRate = $100 Wykup (sprzedaj) BOND: - Jeśli szacowany collateralLevel ≤ 1.2: redeemRate = tvl * 0.8 / bondSupply - W przeciwnym razie: redeemRate = $100
2) Faza A – Kup BOND po mieszanych/obniżonych stawkach - Kup BOND, monitorując (aktualny) poziom zabezpieczenia = tvl / (bondSupply * 100). - Pierwszy zakup następuje, gdy poziom zabezpieczenia > 1.2 → mintuj blisko 100 $ za BOND. - Kolejne zakupy obniżają aktualny poziom zabezpieczenia poniżej 1.2 → mintuj po obniżonej stawce tworzenia (np. ~94,07 $), gromadząc dużą ilość BOND po niskiej cenie.
3) Faza B – Wykup wszystkich BOND po maksymalnej cenie - Wywołaj redeem(BOND, depositAmount = attackerBondBalance, ...) - Kontrakt oblicza szacowany poziom zabezpieczenia, używając stanu po wykupie (bondSupply - depositAmount) oraz (tvl - depositAmount * 100) i (w tym przykładzie) uzyskuje wartość > 1.2 - Ponieważ ta szacowana wartość przekracza próg, redeemRate jest ustawione na maksymalne $100, co pozwala napastnikowi wypłacić wszystkie zgromadzone BOND po $100
4) Realizacja zysku - Różnica między zakupami z rabatem a sprzedażą za 100 USD daje netto zysk w ETH. - W liczbach PoC: wydanie 60 ETH na dwa zakupy zwraca ~61,89 ETH przy wykupie → ~1,89 ETH zysku. - Napastnik może iterować, aż obecny poziom zabezpieczenia puli spadnie do ~1,2, wydobywając w przybliżeniu: wydobywalne USD ≈ ethPrice × poolReserve − 120 × bondSupply
Jaki jest wpływ? Bezpośrednie wydobycie funduszy / wyciek wartości: Napastnik mintuje po obniżonych stawkach i wykupuje za 100 $, siphonując ETH z puli. Nieograniczone do progu: Może trwać, aż obecny poziom zabezpieczeń w puli zmniejszy się do ≈ 1,2. Systemowa niespójność cenowa: Tworzy arbitraż, za który uczciwi użytkownicy płacą gorszymi rezerwami/wynikami puli.
Przyczyna główna: Użycie stanu po transakcji do wyceny wykupu Kod oblicza collateralLevel tak, jakby wykup już się odbył: // używa (tvl - depositAmount*100) i (bondSupply - depositAmount) collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); 1. To pozwala atakującemu wybrać depositAmount w taki sposób, że szacowany poziom przekracza próg (> 1.2), odblokowując $100 redeemRate, nawet gdy obecny stan puli nie uzasadnia tego. 2. Próg oparty na manipulowanej metryce Decyzja o limicie $100 zależy od tego manipulowanego szacowanego poziomu zabezpieczenia, a nie od obecnego (przed transakcją) poziomu, co umożliwia manipulację ceną.
Łagodzenie: Oblicz cenę wykupu na podstawie aktualnego stanu puli, a nie szacowanych sald po wykupie. Sugerowane rozwiązanie projektu jest poprawne: - collateralLevel = ((tvl - (depositAmount * BOND_TARGET_PRICE)) * PRECISION) - / ((bondSupply - depositAmount) * BOND_TARGET_PRICE); + collateralLevel = (tvl * PRECISION) / (bondSupply * BOND_TARGET_PRICE);
Dodatkowe wzmocnienie (zalecane): 1. Monotoniczność ceny: Upewnij się, że cena wykupu nie wzrasta wraz ze wzrostem depositAmount (brak „sprzedaj więcej, uzyskaj lepszą cenę jednostkową”). 2. Ceny oparte na inwariancie: Wyprowadź tworzenie/wykup z jednego inwariantu, aby symetria kupna/sprzedaży zapobiegała jednostronnemu arbitrażowi. 3. Kontrola slippage: Wymagaj od użytkownika podania minimalnych/maksymalnych stawek zarówno dla tworzenia, jak i wykupu. 4. Ograniczenia i throttling: Limity wykupu na transakcję i na blok, aby ograniczyć szkody, jeśli zbliżają się do progów. 5. Spójność w ścieżkach: Dostosuj ścieżki tworzenia i wykupu, aby używały tej samej definicji poziomu zabezpieczenia (tylko bieżący stan).
Jesteśmy dumni, że mogliśmy pomóc zabezpieczyć @plaza_finance dzięki temu odkryciu. Kiedy bezpieczeństwo jest absolutnie konieczne, Sherlock to właściwy wybór.
3,4K