Aktualisiert am 15. September 2023: Balancer hat den offiziellen Post-Mortem veröffentlicht, der eine detaillierte Beschreibung der gesamten Geschichte dieses Vorfalls, einschließlich der gewonnenen Erfahrungen und Lektionen, liefert. Dieses Post-Mortem mit seiner komplexen und exzellenten Erzählung ist fesselnd und zweifellos Ihre Zeit wert.
Aus Sicherheitssicht enthüllt dieses Post-Mortem, dass zwei Fehler existieren. Der erste ist der Fehler bei der Abrundung, den wir in unserem Bericht besprochen haben, und der zweite ist das „Zurücksetzen der Rate bei 0 Angebot“, das in den Angriffsschritten 3.6 und 3.7 auftrat, wie in unserem Bericht beschrieben. Balancers Bericht betrachtet letzteres als das kritischste Problem, während das erste als beitragend gilt. Wir glauben jedoch, dass beide Fehler für eine profitable Ausnutzung gleichermaßen wichtig sind:
-
Der erste Fehler wird verwendet, um die Token-Rate zu erhöhen und dient als Hauptursache für den Gewinn. Ohne ihn wäre die Gewinnermittlung nicht möglich.
-
Der zweite Fehler ermöglicht den Exploit, indem er die Schulden der bb-a-Token ausgleicht. Ohne ihn würde der Angriff aufgrund der geringen Liquidität der bb-a-Token fehlschlagen, da keine anderen Quellen vorhanden sind, um diese Token zu erhalten (es sei denn, der Angreifer schafft es, sie auf irgendeine Weise zu beschaffen).
Am 22. August 2023 gab Balancer öffentlich bekannt, dass eine kritische Schwachstelle mehrere Boosted Pools betrifft, und forderte die Nutzer dringend auf, LPs sofort aus den betroffenen Pools abzuziehen. Balancer hatte Notfall-Minderungsmaßnahmen eingeleitet, um den Großteil des TVL zu sichern, doch einige Gelder blieben gefährdet. Leider bemerkten wir am 27. August, fünf Tage später, mehrere Angriffe. Seitdem wurden Vermögenswerte im Wert von über 2,12 Mio. $ gestohlen.
Zum Zeitpunkt der Erstellung dieses Berichts (mehr als drei Wochen nach der Ankündigung, zu einem Zeitpunkt, an dem wir glauben, dass es sicher ist, dies zu tun) hat Balancer keine eingehende Analyse dieser Schwachstelle veröffentlicht. In diesem Bericht zielen wir darauf ab, eine umfassende Analyse bereitzustellen, die hauptsächlich auf einer der Angriffstransaktionen basiert.
Schlüssel-Erkenntnisse (TL;DR)
- Unsere Untersuchung deutet darauf hin, dass die Hauptursache in der Preismanipulation liegt, die sich aus der Abrundungslogik im
linear-Pool ergibt. Dies wirkt sich folglich unangemessen auf die zwischengespeicherte Token-Rate aus, die vom entsprechendenboosted-Pool verwendet wird. - Dieser Vorfall unterstreicht die kritische Notwendigkeit einer zeitnahen Benachrichtigung von Projekten, die von einer anfälligen Quelle abgezweigt wurden, was tatsächlich eine erhebliche Herausforderung für die gesamte Community darstellt.
- Die zahlreichen laufenden Angriffe unterstreichen die Notwendigkeit einer proaktiven Bedrohungsprävention, die zwangsläufig dazu beitragen kann, potenzielle Verluste zu mindern.
In den folgenden Abschnitten geben wir zunächst einige wichtige Hintergrundinformationen zu Balancer. Anschließend führen wir eine umfassende Analyse der Schwachstelle und des damit verbundenen Angriffs durch. Abschließend geben wir eine kurze Zusammenfassung der bisher beobachteten Angriffe und ihrer entsprechenden Gewinne.
0x1 Hintergrund zu Balancer
Balancer V2 [1] ist ein dezentraler Automated Market Maker (AMM)-Protokoll, das als flexibler Baustein für programmierbare Liquidität dient. Im Gegensatz zu anderen AMMs, bei denen die Token-Buchhaltung mit der Pool-Logik gekoppelt ist, trennt Balancer die Token-Buchhaltung und -Verwaltung von der Pool-Logik, was die Tausch-Effizienz durch Reduzierung vieler Token-Übertragungen verbessern kann.
Balancer unterstützt verschiedene Pool-Typen. Jeder Pool ist mit einem LP-Token namens BPT (d. h. Balancer Pool Token) verbunden. Grundsätzlich wird der Wert von BPT basierend auf dem Gesamtwert aller zugrunde liegenden Token berechnet.
Balancer unterstützt Multi-Hop-Swaps, auch bekannt als batch swaps, die die besten Preise aus allen mit der Vault registrierten Pools nutzen. Insbesondere stellt die Vault die batchSwap-Funktion zur Erleichterung von Multi-Hop-Swaps bereit.
Ein flash swap in Balancers Pools eliminiert die Notwendigkeit, einen der traditionell für die Ausführung eines Swaps erforderlichen Eingabetoken zu halten. Stattdessen können Sie, sobald ein Ungleichgewicht erkannt wird, die Vault anweisen, den Swap auszuführen und anschließend die Belohnung zu erhalten.
0x1.1 Verschiedene Pools in Balancer
Im Folgenden stellen wir kurz einige Konzepte von Pools vor, die für diese Schwachstelle relevant sind.
-
Linear Pools:
LinearPools [2] sind Balancer Pools, die den Austausch eines Assets und seines eingewickelten, ertragsbringenden Gegenstücks zu einem bekannten Wechselkurs ermöglichen. Wie der Name schon sagt, verwendenLinearPools eine lineare Mathematik. Einlinear-Pool enthält drei Token, darunter:- zwei Assets, d.h.
mainundwrappedToken, die den gleichen zugrunde liegenden Wert haben; - der entsprechende
BPT(Balancer Pool Token). Beachten Sie, dassBPTERC-20 Token sind.
- zwei Assets, d.h.
-
Nesting Linear Pools: Linear Pool BPT kann in einem anderen Pool verschachtelt werden. Dies schafft einen einfachen
batchSwap-Pfad zwischen Basis-Assets und Token im äußeren Pool, da Tauscher vonBPTzu einem der zugrunde liegenden Token des Linear Pools tauschen können. -
Composable Stable Pools: Composable Stable Pools [3] sind für Assets konzipiert, die voraussichtlich konsistent zu annähernd gleichen Preisen oder zu einem bekannten Wechselkurs getauscht werden. Composable Stable Pools verwenden stabile Mathematik, die Swaps von erheblichem Umfang ermöglicht, bevor es zu erheblichen Preiseffekten kommt, was die Kapitaleffizienz für gleichartige und korrelierte Swaps drastisch erhöht.
Ein Pool ist komponierbar, wenn er Swaps zu und von seinem eigenen LP-Token zulässt. Das Einbringen seines LP-Tokens in andere Pools (oder "Verschachtelung") ermöglicht einfache
batchSwapvon verschachtelten Pool-Token zu Token im äußeren Pool. -
Boosted Pools:
BoostedPools [4] sind darauf ausgelegt, die Kapitaleffizienz von ungenutzter Liquidität für große Pools zu verbessern.BoostedPools sind tatsächlich eine Unterklasse anderer Pools. Zum Beispiel könnte einboostedPool auflinearPools aufbauen.Boosted Pools sind darauf ausgelegt, eine hohe Kapitaleffizienz zu liefern, indem sie es Nutzern ermöglichen, Swap-Liquidität für gängige Token bereitzustellen und gleichzeitig ungenutzte Token an externe Protokolle weiterzuleiten. Dies bietet Liquiditätsanbietern die Vorteile von Protokollen wie Aave zusätzlich zu den Swap-Gebühren, die sie aus Swaps erhalten.
0x1.2 Ein konkretes Beispiel für die anfälligen Boosted Pools: Balancer Boosted Aave USD
Balancer Boosted Aave USD (Symbol: bb-a-USD) ist ein Composable Stable Pool, der Swaps zwischen drei Stablecoins (d.h. USDC, USDT und DAI) ermöglicht, während ungenutzte Liquidität an Aave gesendet wird. Die zugrunde liegenden linear Pools sind:
bb-a-USDC(bestehend aus USDC und gewrapptem aUSDC)bb-a-USDT(bestehend aus USDT und gewrapptem aUSDT)bb-a-DAI(bestehend aus DAI und gewrapptem aDAI)
Insbesondere ist bb-a-USD eine Sammlung eines Composable Stable Pools, der die Pool-Token von drei verschiedenen linear Pools enthält, und jeder dieser linear Pools hat einen zugehörigen Stable-Token: DAI, USDC und USDT. Die folgende Abbildung aus dem offiziellen Dokument [5] zeigt die Struktur von bb-a-USD:

0x1.3 Berechnung des BPT-Preises
Eine wichtige Frage, die sich zwangsläufig stellt, ist, wie der Preis von BPT bei einem Tausch eines bestimmten Betrags (d.h. amountIn) von BPT gegen einen bestimmten Betrag (d.h. amountOut) eines anderen Tokens ermittelt wird.
Balancer bietet detaillierte Beschreibungen der von ihnen übernommenen mathematischen Formeln [6, 7] für verschiedene Pools. Der Einfachheit halber fassen wir hier die relevantesten Konzepte zusammen.
Nehmen wir als Beispiel den linear-Pool. Der BPT-Preis wird in der onSwap-Funktion des LinearPool-Vertrags berechnet.

Die Berechnung lässt sich wie folgt zusammenfassen:

Hier wird tokenRate mit der folgenden Formel berechnet:

ist ein konstanter Wert: .
In der obigen Formel kann der Zähler vereinfacht werden als die Summe des Saldos des main-Tokens und des Saldos des wrapped-Tokens, während der Nenner die Differenz zwischen einem vordefinierten Wert (d.h. _INITIAL_BPT_SUPPLY) und dem Saldo von BPT ist.
Es ist anzumerken, dass die Salden aller beteiligten Token nomalisiert werden müssen, bevor die Berechnung durchgeführt wird, da verschiedene Token unterschiedliche Dezimalstellen haben können. Insbesondere wird der Rohsaldo eines gegebenen Tokens mit einem entsprechenden Skalierungsfaktor multipliziert, der durch die Funktion _scalingFactors bestimmt wird.
(1) Skalierungsfaktoren von Linear Pools
Sowohl BPT als auch das main-Token haben einen regulären, konstanten Skalierungsfaktor.

(2) Skalierungsfaktoren von Boosted Pools wie bb-a-USD
Die Berechnung eines boosted-Pools ist etwas kompliziert. Insbesondere ist der zurückgegebene Skalierungsfaktor das Produkt aus dem rohen Skalierungsfaktor (z. B. 1e18) und der Token-Rate, die aus der zwischengespeicherten Token-Rate bezogen wird, falls vorhanden.

Woher kommt die zwischengespeicherte Token-Rate? Es gibt eine private Funktion namens _updateTokenRateCache. Offensichtlich ruft diese Funktion zuerst die Rate ab, indem sie die getRate-Funktion dieses Tokens aufruft, und speichert sie dann zwischen.

Nehmen wir wieder bb-a-USDC als Beispiel, die Kernlogik der entsprechenden getRate-Funktion folgt der zuvor besprochenen Formel.

Beachten Sie, dass es drei mögliche Pfade gibt, die die Funktion _updateTokenRateCache auslösen können:

Darüber hinaus gibt es bei der Aktualisierung von Pfaden, die über die onSwap-Funktion laufen, eine Ablaufprüfung:

0x2 Schwachstellenanalyse
Die Hauptursache liegt in der Preismanipulation, die durch die Abrundungslogik innerhalb der onSwap-Funktion des linear-Pools verursacht wird. Dies beeinträchtigt wiederum die zwischengespeicherte Token-Rate, die vom boosted-Pool verwendet wird.
Insbesondere wird amountOut abgerundet, wenn die Funktion _downscaleDown aufgerufen wird. Daher, wenn es einen erheblichen Unterschied zwischen amountOut und scalingFactors[indexOut] gibt, kann der Rückgabewert der Funktion _downscaleDown null sein.

Zum Beispiel, wenn wir bb-a-USDC (als BPT) verwenden, um USDC (als main-Token) im bb-a-USDC-Pool zu tauschen, wird der Rückgabewert immer auf Null abgerundet, wenn amountOut kleiner als 1.000.000.000.000 ist. Dies würde den Saldo von bb-a-USDC erhöhen, da es als einseitige Hinzufügung von Liquidität zu bb-a-USDC betrachtet werden könnte.
Folglich wird, wenn BPT das für den Tausch verwendete Token ist, dessen Rate entsprechend der Formel zur Berechnung der Rate steigen, da der Zähler gleich bleibt, während der Nenner sinkt. Dieser Fehler könnte ausgenutzt werden, um zu einer (riesigen) Preisdifferenz zu führen.
0x3 Angriffsanalyse
Die Angriffstransaktion besteht aus folgenden Angriffsschritten:
- Leihen von 300.000 USDC per Flashloan von Aave.
- Tausch von 1,067753 USDC gegen 0,970495 aUSDC im bb-a-USDC-Pool.
- Durchführung eines
batchSwapin den bb-a-USDC- und bb-a-USD-Pools, d.h. Ernte von 15.628bb-a-USDC, 139.431bb-a-DAIund 248.868bb-a-USDTmit 42.203USDC. Die detaillierten Schritte sind in der folgenden Tabelle zusammengefasst (mit Dezimalstellen):

- Tausch von LP-Token gegen die entsprechenden zugrunde liegenden Stable-Token:
- 139.431
bb-a-DAI-> 141.127DAIim bb-a-DAI-Pool - 15.628
bb-a-USDC-> 15.685USDCim bb-a-USDC-Pool - 248.868
bb-a-USDT-> 253.461USDTim bb-a-USDT-Pool
- Rückzahlung des Flashloans, und der endgültige Gewinn beträgt:
- 114.324
DAI - 253.461
USDT - 0,970495
aUSDC
Es ist erwähnenswert, dass der Angreifer in Schritt 2 aUSDC mit USDC aus dem bb-a-USDC-Pool abgezogen hat, was die Preismanipulation in Schritt 3 erheblich erleichterte, d.h. der Angreifer musste sich nur auf USDC und bb-a-USDC konzentrieren.
Hier spielt Schritt 3 die Schlüsselrolle. Lassen Sie uns nun die Details dieses Schritts untersuchen, um herauszufinden, warum der Angreifer Gewinne erzielen konnte. Insbesondere:
- Schritt 3.1 wird verwendet, um USDC mit bb-a-USDC aus dem bb-a-USDC-Pool abzuheben;
- Schritte 3.3 und 3.4 werden verwendet, um bb-a-USDC gegen bb-a-DAI zu tauschen, während Schritt 3.5 verwendet wird, um bb-a-USDC gegen bb-a-USDT zu tauschen.
- Schritt 3.7 wird verwendet, um USDC gegen bb-a-USDC aus dem bb-a-USDC-Pool zu tauschen.
Hier tauschen die Schritte 3.2 und 3.6 keine Ziel-Token (d. h.
USDC) zurück (aufgrund der zuvor diskutierten Abrundung), daher bleiben die Salden der Ziel-Token nach dem Tausch unverändert, was als Hinzufügung zusätzlicher Liquidität vonbb-a-USDCin den bb-a-USDC-Pool betrachtet werden kann.
Offensichtlich ereignen sich die abnormalen Tauschvorgänge hauptsächlich in den Schritten 3.4, 3.5 und 3.7. Im Folgenden gehen wir die Details jedes dieser Schritte nacheinander durch.
(1) bb-a-USDC -> bb-a-DAI
In Schritt 3.3 ist der Wechselkurs zwischen bb-a-USDC und bb-a-DAI fast 1, während er in Schritt 3.4 19 beträgt:
- Schritt 3.3: 1.000.339.378.515.783.699 / 1.000.000.000.000.000.000 = 1.00
- Schritt 3.4: 139.430.482.942.020.211.267.110 / 7.300.000.000.000.000.000.000 = 19.10
Erinnern wir uns an die zuvor besprochene Code-Logik: In Schritt 3.3 aktualisiert der Pool nach der Rückgabe der zwischengespeicherten Token-Rate zur Berechnung des Skalierungsfaktors (1.012.181.365.780.643.700) die Rate, um einen neuen Wert (40.240.000.000.000.000.000) zu berechnen. Dieser aktualisierte Wert wird dann in Schritt 3.4 als neuer Skalierungsfaktor verwendet. Da die rohen Skalierungsfaktoren unverändert bleiben (d. h. 1e18), impliziert dies, dass die neue Rate ungefähr 40 Mal höher ist als die alte Rate.

Woher kommt dieser signifikante Anstieg? Betrachten wir die Formel zur Berechnung von tokenRate. Da der Saldo von aUSDC in Schritt 2 aufgebraucht wurde, kann die Berechnung von tokenRate wie folgt vereinfacht werden:


Hier ist der tatsächliche Wert von nominalMainBalance auf die Abrundung in Schritt 3.2 zurückzuführen.
(2) bb-a-USDC -> bb-a-USDT
Schritt 3.5 nutzt denselben Trick, um mehr bb-a-USDT zu erhalten, und der Wechselkurs zwischen bb-a-USDC und bb-a-USDT beträgt mehr als 12:
- 248.868.905.733.352.246.491.156 / 20.000.000.000.000.000.000.000 = 12.44
(3) USDC -> bb-a-USDC
Darüber hinaus wird in Schritt 3.6 der bptBalance erhöht, und in Schritt 3.7 wird die bptSupply Null. Dadurch ist es möglich, USDC gegen bb-a-USDC zu einem Wechselkurs von fast 1:1 zu tauschen.

0x4 Zusammenfassung der Angriffe und Gewinne
Zum Zeitpunkt der Abfassung dieses Berichts haben wir Dutzende von Angriffen beobachtet, die zu Verlusten von über 2,12 Mio. $ geführt haben. Zusammenfassend lässt sich sagen, dass diese Angriffe von drei verschiedenen Konten ausgeführt wurden, wie folgt:

Balancer erlitt aufgrund dieser Schwachstelle einen Gesamtverlust von ca. 1 Million Dollar. Weniger als 12 Stunden nach dem ersten Angriff auf Balancer fiel sein abgezweigtes Protokoll, Beethoven X, ähnlichen Angriffen zum Opfer, was zu einem geschätzten Verlust von ca. 1,1 Million Dollar führte. Beethoven X verzeichnete sogar größere Verluste als Balancer! Der kumulative Verlust aus diesem Sicherheitsvorfall belief sich auf ca. 2,12 Mio. $.
Eine vollständige Liste dieser Angriffstransaktionen wurde in einem Dokument zusammengestellt, das wir vorbereitet haben. Bitte beachten Sie dieses für detailliertere Informationen.
Einige Beobachtungen zu den Angreifern Nach der Analyse der von jedem Netzwerk initiierten Transaktionen stellten wir erhebliche Abweichungen in der Spur der Angriffstransaktionen auf Fantom im Vergleich zu denen auf Ethereum und Optimism fest.
Insbesondere nutzte der Angreifer auf Fantom über die bemerkenswerten Unterschiede in den Schlüsselfunktionen hinaus zwei einzigartige Tricks, um Front-Running durch MEV-Bots zu vermeiden. Darüber hinaus wurden die für den Angriff auf Fantom verwendeten Gelder 163 Tage vor dem Angriff vorbereitet.
Aus den oben beschriebenen Beobachtungen können wir schließen:
-
Mindestens zwei unterschiedliche Angreifer waren beteiligt.
-
Der Angreifer auf Fantom ist ein erfahrener Serienverbrecher.
0x5 Fazit
Zusammenfassend handelt es sich um eine subtile Schwachstelle, die in der Abrundungslogik begründet liegt. Die Ausnutzung dieser Schwachstelle ist jedoch nicht einfach. Insbesondere konnte der Angreifer die zwischengespeicherte Token-Rate aufblähen, indem er das Abrundungsproblem im linear-Pool ausnutzte und somit den Token-Preis im entsprechenden boosted-Pool manipulierte.
Dieser Vorfall unterstreicht auch die Bedeutung einer rechtzeitigen Benachrichtigung von Projekten, die von der anfälligen Quelle abgezweigt wurden. Trotz Balancers Warnung setzen sich Angriffe auf abgezweigte Protokolle fort, was die Notwendigkeit für diese abgezweigten Projekte hervorhebt, über Sicherheitsupdates ihrer Quellprojekte informiert zu bleiben. Die Gewährleistung einer zeitnahen Benachrichtigung dieser abgezweigten Projekte stellt jedoch eine fortlaufende Herausforderung für die Community dar.
Darüber hinaus unterstreicht die kontinuierliche Reihe von Angriffen die Bedeutung der proaktiven Bedrohungsprävention, die potenzielle Verluste wirksam helfen kann, zu mindern.
Referenz
- [1] https://docs.balancer.fi/concepts/overview/basics.html
- [2] Linear Pools: https://docs.balancer.fi/concepts/pools/linear.html
- [3] Composable Stable Pools
- [4] Boosted Pools
- [5] https://docs.balancer.fi/concepts/pools/boosted.html#example
- [6] https://docs.balancer.fi/reference/math/linear-math.html
- [7] https://docs.balancer.fi/reference/math/stable-math.html



