Aktualisiert am 15. September 2023: Balancer hat den offiziellen Post-Mortem veröffentlicht, der eine detaillierte Beschreibung der gesamten Geschichte dieses Vorfalls enthält, einschließlich der gesammelten Erfahrungen und Lehren. Dieser Post-Mortem mit seiner komplexen und hervorragenden Erzählung ist fesselnd und sicherlich Ihre Zeit wert.
Aus sicherheitstechnischer Sicht offenbart dieser Post-Mortem, dass es zwei Fehler gibt. Der erste ist der Rundungsfehler nach unten, den wir in unserem Bericht besprochen haben, und der zweite ist der "Resets rate on 0 supply", der in den Angriffsschritten 3.6 und 3.7 auftrat, wie in unserem Bericht beschrieben. Balancers Bericht betrachtet den zweiten als das kritischste Problem, wobei der erste mitwirkend war. Wir glauben jedoch, dass beide Fehler für eine profitable Ausnutzung gleich wichtig sind:
-
Der erste Fehler wird verwendet, um den Token-Kurs in die Höhe zu treiben, und dient als Ursache für den Gewinn. Ohne ihn wäre die Erzielung von Gewinnen nicht möglich.
-
Der zweite Fehler ermöglicht den Exploit, indem er die Schulden der bb-a-Tokens ausgleicht. Ohne ihn würde der Angriff aufgrund der schlechten Liquidität der bb-a-Tokens fehlschlagen, da keine anderen Quellen zur Beschaffung dieser Tokens zur Verfügung stehen (es sei denn, der Angreifer schafft es, sie auf irgendeine Weise zu erhalten).
Am 22. August 2023 gab Balancer öffentlich bekannt, dass eine kritische Schwachstelle mehrere Boosted Pools betrifft, und forderte die Benutzer dringend auf, LP sofort aus den betroffenen Pools abzuziehen. Balancer hatte Notfallmaßnahmen zur Sicherung des Großteils des TVL eingeleitet, jedoch blieben einige Gelder gefährdet. Bedauerlicherweise bemerkten wir am 27. August, fünf Tage später, mehrere Angriffe in freier Wildbahn. 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, an einem Punkt, an dem wir glauben, dass dies sicher ist) 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.
Wichtigste Erkenntnisse (TL;DR)
- Unsere Untersuchung deutet darauf hin, dass die Ursache in der Preismanipulation durch die Rundungslogik im
linearPool liegt. Dies beeinflusst folglich den zwischengespeicherten Token-Kurs, der vom entsprechendenboostedPool unangemessen verwendet wird. - Dieser Vorfall unterstreicht die dringende Notwendigkeit einer schnellen Benachrichtigung von Projekten, die von einer anfälligen Quelle abgeleitet 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 letztendlich zur Minderung zukünftiger Verluste beitragen könnte.
In den folgenden Abschnitten geben wir zunächst einige grundlegende Hintergrundinformationen zu Balancer. Danach 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 jeweiligen Gewinne.
0x1 Hintergrund zu Balancer
Balancer V2 [1] ist ein dezentraler Automated Market Maker (AMM)-Protokoll, das einen flexiblen Baustein für programmierbare Liquidität darstellt. 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 Tauschleistung verbessern kann, indem viele Token-Transfers reduziert werden.
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 auf der Grundlage des Gesamtwerts aller zugrunde liegenden Tokens berechnet.
Balancer unterstützt Multi-Hop-Swaps, auch bekannt als batch swaps, die die besten Preise von allen bei 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 macht es überflüssig, traditionell benötigte Eingabetoken zu halten, um einen Swap auszuführen. Stattdessen können Sie, sobald ein Ungleichgewicht festgestellt wurde, die Vault anweisen, den Swap auszuführen und anschließend die Belohnung zu erhalten.
0x1.1 Verschiedene Pools in Balancer
Im Folgenden führen wir kurz einige Poolkonzepte ein, die für diese Schwachstelle relevant sind.
-
Linear Pools:
LinearPools [2] sind Balancer Pools, die den Austausch eines Assets und seines gewickelten, ertragsbringenden Gegenstücks zu einem bekannten Wechselkurs ermöglichen. Wie der Name schon sagt, verwendenLinearPools lineare Mathematik. EinlinearPool enthält drei Tokens, darunter:- zwei Assets, d. h.
mainundwrappedTokens, die den gleichen zugrunde liegenden Wert haben; - das entsprechende
BPT(Balancer Pool Token). Beachten Sie, dassBPTERC-20-Tokens 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 Tokens im äußeren Pool, da Swapper vonBPTzu einem der zugrunde liegenden Tokens des Linear Pools tauschen können. -
Composable Stable Pools: Composable Stable Pools [3] sind für Assets konzipiert, die konsistent zu nahezu gleichen Preisen oder zu einem bekannten Wechselkurs getauscht werden sollen. Composable Stable Pools verwenden eine stabile Mathematik, die Swaps von erheblichem Umfang ermöglicht, bevor es zu erheblichen Preiseinflüssen kommt, was die Kapitaleffizienz für gleichartige und korrelierte Swaps erheblich steigert.
Ein Pool ist komponierbar, wenn er Swaps von und zu seinem eigenen LP-Token ermöglicht. Das Einbringen seines LP-Tokens in andere Pools (oder "Verschachtelung") ermöglicht einfache
batchSwapvon verschachtelten Pool-Tokens zu Tokens im äußeren Pool. -
Boosted Pools:
BoostedPools [4] sind darauf ausgelegt, die Kapitaleffizienz von ruhender Liquidität für große Pools zu verbessern.BoostedPools sind tatsächlich eine Unterklasse anderer Pools. Beispielsweise könnte einboostedPool auflinearPools aufgebaut sein.Boosted Pools sind darauf ausgelegt, eine hohe Kapitaleffizienz zu erzielen, indem sie es Benutzern ermöglichen, Swap-Liquidität für gängige Token bereitzustellen, während ruhende Token an externe Protokolle weitergeleitet werden. Dies verschafft 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 ruhende Liquidität an Aave weitergeleitet wird. Die zugrunde liegenden linear Pools sind:
bb-a-USDC(bestehend aus USDC und gewickeltem aUSDC)bb-a-USDT(bestehend aus USDT und gewickeltem aUSDT)bb-a-DAI(bestehend aus DAI und gewickeltem aDAI)
Konkret ist bb-a-USD eine Sammlung eines Composable Stable Pools, der die Pool-Tokens 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 Wie der Preis von BPT berechnet wird
Eine wichtige Frage, die sich natürlich 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 bestimmt wird.
Balancer bietet detaillierte Beschreibungen der von ihnen übernommenen mathematischen Formeln [6, 7] für verschiedene Pools. Zur Vereinfachung abstrahieren und fassen wir die relevantesten Konzepte hier zusammen.
Am Beispiel des linear Pools wird der BPT-Preis in der onSwap-Funktion des LinearPool-Vertrags berechnet.

Die Berechnung lässt sich wie folgt zusammenfassen:

$$amountOut=amountIn*tokenRate$$
Hier wird tokenRate mit der folgenden Formel berechnet:

$_INITIAL_BPT_SUPPLY$ ist ein konstanter Wert: $2^{112} - 1$.
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 Tokens vor der Berechnung nominalisiert werden müssen, da unterschiedliche Tokens unterschiedliche Dezimalstellen haben können. Konkret wird der Rohsaldo eines gegebenen Tokens mit einem entsprechenden Aufskalierungsfaktor multipliziert, der durch die Funktion _scalingFactors bestimmt wird.
(1) Skalierungsfaktoren von `Linear` Pools
Sowohl BPT als auch der 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 Rohskalierungsfaktor (z. B. 1e18) und dem Token-Kurs, der aus dem zwischengespeicherten Token-Kurs bezogen wird, falls vorhanden.

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

Auch hier folgt die Kernlogik der entsprechenden getRate-Funktion für bb-a-USDC der zuvor diskutierten Formel.

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

Darüber hinaus gibt es bei Updates für Pfade, die über die onSwap-Funktion verlaufen, eine Ablaufprüfung:

0x2 Schwachstellenanalyse
Die Hauptursache liegt in der Preismanipulation, die durch die Rundungslogik in der onSwap-Funktion des linear Pools verursacht wird. Dies beeinträchtigt wiederum den zwischengespeicherten Token-Kurs, der vom boosted Pool verwendet wird.
Insbesondere wird der amountOut beim Aufruf der Funktion _downscaleDown nach unten abgerundet. Wenn es also einen erheblichen Unterschied zwischen amountOut und scalingFactors[indexOut] gibt, kann der Rückgabewert der Funktion _downscaleDown null sein.

Wenn wir beispielsweise bb-a-USDC (als BPT) verwenden, um USDC (als main-Token) im bb-a-USDC-Pool zu tauschen, und amountOut kleiner als 1.000.000.000.000 ist, wird der Rückgabewert immer auf Null abgerundet. Dies würde den Saldo von bb-a-USDC erhöhen, da es als einseitige Hinzufügung von Liquidität von bb-a-USDC betrachtet werden könnte.
Folglich wird bei Verwendung von BPT für den Tausch sein Kurs ansteigen, entsprechend der Formel zur Berechnung des Kurses, da der Zähler gleich bleibt, während der Nenner sinkt. Dieser Fehler könnte ausgenutzt werden, um einen (riesigen) Preisunterschied zu erzielen.
0x3 Angriffsanalyse
Die Angriffstransaktion besteht aus folgenden Angriffsschritten:
- Ausleihen von 300.000 USDC über Flashloan von Aave.
- Tausch von 1,067753 USDC gegen 0,970495 aUSDC im bb-a-USDC-Pool.
- Durchführung von
batchSwapin den Poolsbb-a-USDCundbb-a-USD, 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-Tokens gegen die entsprechenden zugrunde liegenden Stable-Tokens:
- 139.431
bb-a-DAI-> 141.127DAIimbb-a-DAI-Pool - 15.628
bb-a-USDC-> 15.685USDCimbb-a-USDC-Pool - 248.868
bb-a-USDT-> 253.461USDTimbb-a-USDT-Pool
- Rückzahlung des Flashloans, und der Endgewinn beträgt:
- 114.324
DAI - 253.461
USDT - 0.970495
aUSDC
Es ist anzumerken, dass der Angreifer im Schritt 2 aUSDC mit USDC aus dem bb-a-USDC-Pool abgezogen hat, was die Preismanipulation im 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. Tauchen wir nun in die Details dieses Schritts ein, um herauszufinden, warum der Angreifer Gewinne erzielen konnte. Insbesondere:
- Schritte 3.1 werden verwendet, um USDC mit bb-a-USDC aus dem bb-a-USDC-Pool abzuziehen;
- 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-Tokens (d. h.
USDC) zurück, aufgrund der zuvor diskutierten Rundung nach unten. Daher bleiben die Salden der Ziel-Tokens nach dem Tausch unverändert, was als Hinzufügen zusätzlicher Liquidität vonbb-a-USDCin denbb-a-USDC-Pool betrachtet werden kann.
Offensichtlich treten die abnormalen Swaps hauptsächlich in den Schritten 3.4, 3.5 und 3.7 auf. 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
Wenn wir uns an die zuvor besprochene Code-Logik erinnern, aktualisiert er nach der Rückgabe des zuvor zwischengespeicherten Token-Kurses zur Berechnung des Skalierungsfaktors (1.012.181.365.780.643.700) in Schritt 3.3 den Kurs, um einen neuen Wert zu berechnen (40.240.000.000.000.000.000). Dieser aktualisierte Wert wird dann in Schritt 3.4 als neuer Skalierungsfaktor verwendet. Da die Rohskalierungsfaktoren unverändert bleiben (d. h. 1e18), impliziert dies, dass der neue Kurs ungefähr 40-mal höher ist als der alte Kurs.

Woher kommt jedoch diese erhebliche Steigerung? Betrachten wir noch einmal die Formel zur Berechnung von tokenRate. Da der Saldo von aUSDC in Schritt 2 erschöpft wurde, lässt sich die Berechnung von tokenRate vereinfachen:


Hier ist der tatsächliche Wert von nominalMainBalance auf die Rundung nach unten in Schritt 3.2 zurückzuführen.
(2) bb-a-USDC -> bb-a-USDT
Schritt 3.5 verwendet 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 der bptBalance in Schritt 3.6 erhöht, dann wird bptSupply in Schritt 3.7 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 Erstellung dieses Berichts haben wir Dutzende von Angriffen beobachtet, die zu Verlusten von über 2,12 Mio. $ geführt haben. Zusammenfassend wurden diese Angriffe von drei verschiedenen Konten durchgeführt, wie folgt:

Balancer erlitt aufgrund dieser Schwachstelle einen Gesamtverlust von ca. 1 Million $. Weniger als 12 Stunden nach dem ersten Angriff auf Balancer fiel sein abgeleitetes Protokoll, Beethoven X, ähnlichen Angriffen zum Opfer, was zu einem geschätzten Verlust von ca. 1,1 Million $ führte. Beethoven X erlitt 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 beziehen Sie sich darauf für detailliertere Informationen.
Einige Beobachtungen zu den Angreifern
Nach der Analyse der von jedem Netzwerk initiierten Transaktionen stellten wir eine signifikante Abweichung in der Spur der Angriffstransaktionen auf Fantom im Vergleich zu denen auf Ethereum und Optimism fest.
Insbesondere nutzte der Angreifer auf Fantom, abgesehen von den bemerkenswerten Unterschieden bei den Kernfunktionen, zwei einzigartige Tricks, um Frontrunning 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 genannten Beobachtungen können wir ableiten:
-
Mindestens zwei verschiedene Angreifer waren beteiligt.
-
Der Angreifer auf Fantom ist ein erfahrener Serienverbrecher.
0x5 Schlussfolgerung
Zusammenfassend lässt sich sagen, dass es sich um eine subtile Schwachstelle handelt, die in der Rundungslogik wurzelt. Die Ausnutzung dieser Schwachstelle ist jedoch nicht einfach. Insbesondere konnte der Angreifer den zwischengespeicherten Token-Kurs aufblähen, indem er das Problem der Rundung nach unten 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 abgeleitet wurden. Trotz Balancers Warnung werden weiterhin Angriffe auf abgeleitete Protokolle verübt, was die Notwendigkeit hervorhebt, dass diese abgeleiteten Projekte über Sicherheitsupdates ihrer Quellprojekte informiert bleiben. Die Gewährleistung einer schnellen Benachrichtigung dieser abgeleiteten Projekte stellt jedoch eine fortlaufende Herausforderung für die Community dar.
Darüber hinaus unterstreicht die kontinuierliche Reihe von Angriffen die Bedeutung einer proaktiven Bedrohungsprävention, die wirksam zur Minderung potenzieller Verluste beitragen kann.
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



