Aktualisiert am 15. September 2023: Balancer hat das offizielle Post-Mortem veröffentlicht, das eine detaillierte Beschreibung der gesamten Geschichte dieses Vorfalls sowie die gesammelten Erfahrungen und Erkenntnisse enthält. Dieses Post-Mortem ist mit seiner komplexen und exzellenten Erzählweise überzeugend und sicherlich Ihre Zeit wert.
Aus Sicherheitsperspektive enthüllt dieses Post-Mortem, dass zwei Fehler existieren. Der erste ist der Rundungsfehler nach unten, den wir in unserem Bericht diskutiert haben, und der zweite ist das „Zurücksetzen der Rate bei 0 Supply“, das in den Angriffsschritten 3.6 und 3.7 auftrat, wie in unserem Bericht beschrieben. Balancers Bericht betrachtet den zweiten Fehler als das kritischere Problem, wobei der erste dazu beigetragen hat. Wir sind jedoch der Meinung, dass beide Fehler für eine profitable Ausnutzung gleichermaßen wichtig sind:
-
Der erste Fehler wird verwendet, um die Token-Rate in die Höhe zu treiben, was die Hauptursache für den Profit darstellt. Ohne ihn wäre die Generierung von Profit nicht machbar gewesen.
-
Der zweite Fehler ermöglicht den Exploit durch das Ausgleichen der Schulden der bb-a-Token. Ohne ihn wäre der Angriff aufgrund der geringen Liquidität der bb-a-Token gescheitert, da es keine anderen Quellen gibt, um diese Token zu erhalten (es sei denn, der Angreifer schafft es, sie auf anderem Wege zu beschaffen).
Am 22. August 2023 gab Balancer öffentlich bekannt, dass eine kritische Schwachstelle mehrere Boosted Pools betrifft, und forderte Benutzer dazu auf, LPs sofort aus den betroffenen Pools abzuziehen. Balancer hatte Notfallmaßnahmen eingeleitet, um den Großteil des TVL zu sichern, doch einige Gelder blieben gefährdet. Leider bemerkten wir fünf Tage später, am 27. August, mehrere Angriffe in freier Wildbahn. Seitdem wurden Vermögenswerte in Höhe 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 es für sicher halten) hat Balancer noch keine eingehende Analyse dieser Schwachstelle veröffentlicht. In diesem Bericht möchten wir eine umfassende Analyse liefern, 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 Abrundungslogik im
linearPool liegt. Dies wirkt sich folglich unangemessen auf die zwischengespeicherte Token-Rate aus, die vom entsprechendenboostedPool verwendet wird. - Dieser Vorfall unterstreicht die kritische Notwendigkeit einer zeitnahen Benachrichtigung für Projekte, die von einer anfälligen Quelle abgezweigt (geforkt) wurden, was in der Tat eine große Herausforderung für die gesamte Gemeinschaft darstellt.
- Die zahlreichen laufenden Angriffe unterstreichen die Notwendigkeit einer proaktiven Bedrohungsprävention, die zwangsläufig dazu beitragen könnte, drohende Verluste zu mindern.
In den folgenden Abschnitten werden wir zunächst einige grundlegende Hintergrundinformationen zu Balancer bereitstellen. Danach werden wir eine umfassende Analyse der Schwachstelle und des damit verbundenen Angriffs durchführen. Abschließend geben wir eine kurze Zusammenfassung der bisher beobachteten Angriffe sowie deren jeweilige Profite.
0x1 Hintergrund zu Balancer
Balancer V2 [1] ist ein dezentrales Protokoll für automatisierte Market Maker (AMM), das einen flexiblen Baustein für programmierbare Liquidität darstellt. Im Gegensatz zu anderen AMMs, bei denen die Token-Bilanzierung mit der Pool-Logik gekoppelt ist, trennt Balancer die Token-Bilanzierung und -Verwaltung von der Pool-Logik, was die Effizienz von Swaps durch die Reduzierung zahlreicher Token-Transfers verbessern kann.
Balancer unterstützt verschiedene Arten von Pools. Jeder Pool ist mit einem LP-Token namens BPT (d. h. Balancer Pool Token) verknüpft. Grundsätzlich wird der BPT-Wert basierend auf dem Gesamtwert aller zugrunde liegenden Token berechnet.
Balancer unterstützt Multi-Hop-Swaps, auch bekannt als batch swaps, die die besten Preise aller bei der Vault registrierten Pools nutzen. Insbesondere bietet die Vault die Funktion batchSwap, um Multi-Hop-Swaps zu erleichtern.
Ein flash swap in Balancer-Pools eliminiert die Notwendigkeit, einen der Input-Token zu halten, die traditionell für die Ausführung eines Swaps erforderlich sind. Stattdessen können Sie beim Erkennen eines Ungleichgewichts die Vault anweisen, den Swap auszuführen und anschließend die Belohnung zu erhalten.
0x1.1 Verschiedene Pools bei 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 Vermögenswerts und seines verzinsten, verpackten Gegenstücks zu einem bekannten Wechselkurs erleichtern. Wie der Name schon sagt, verwendenLinearPools lineare Mathematik. EinlinearPool hält drei Token, darunter:- zwei Vermögenswerte, d. h.
main(Haupt-) undwrapped(verpackte) Token, die einen zugrunde liegenden Token mit gleichem Wert haben; - den entsprechenden
BPT(Balancer Pool Token). Beachten Sie, dassBPTERC-20-Token sind.
- zwei Vermögenswerte, 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 Tauschende vonBPTzu einem der zugrunde liegenden Token des Linear Pools wechseln können. -
Composable Stable Pools: Composable Stable Pools [3] sind für Vermögenswerte konzipiert, von denen erwartet wird, dass sie konsistent nahe der Parität oder zu einem bekannten Wechselkurs getauscht werden. Composable Stable Pools verwenden stabile Mathematik, die Swaps von erheblicher Größe ermöglicht, ohne auf nennenswerte Preisimpulse zu stoßen, was die Kapitaleffizienz für gleichartige und korrelierte Swaps erheblich erhöht.
Ein Pool ist "composable" (zusammensetzbar), wenn er Swaps von und zu seinem eigenen LP-Token ermöglicht. Das Einbringen seines LP-Tokens in andere Pools (oder "Nesting") ermöglicht einfache
batchSwap-Vorgänge von verschachtelten Pool-Token zu Token im äußeren Pool. -
Boosted Pools:
BoostedPools [4] wurden entwickelt, um die Kapitaleffizienz ungenutzter Liquidität für große Pools zu verbessern.BoostedPools sind eigentlich eine Unterklasse anderer Pools. Beispielsweise könnte einboostedPool auflinearPools aufgebaut sein.Boosted Pools wurden entwickelt, um eine hohe Kapitaleffizienz zu bieten, indem sie Benutzern ermöglichen, Swap-Liquidität für gängige Token bereitzustellen, während ungenutzte Token an externe Protokolle weitergeleitet werden. Dies gibt Liquiditätsanbietern die Vorteile von Protokollen wie Aave zusätzlich zu den Swap-Gebühren, die sie aus Swaps sammeln.
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) erleichtert und gleichzeitig ungenutzte Liquidität an Aave sendet. Die zugrunde liegenden linear Pools sind:
bb-a-USDC(bestehend aus USDC und verpacktem aUSDC)bb-a-USDT(bestehend aus USDT und verpacktem aUSDT)bb-a-DAI(bestehend aus DAI und verpacktem aDAI)
Insbesondere ist bb-a-USD eine Sammlung von einem Composable Stable Pool, der die Pool-Token von drei verschiedenen linear Pools enthält, und jeder dieser linear Pools hat einen zugehörigen stabilen Token: DAI, USDC und USDT. Die untenstehende Abbildung aus dem offiziellen Dokument [5] zeigt die Struktur von bb-a-USD:

0x1.3 So wird der Preis von BPT berechnet
Eine wichtige Frage, die sich natürlich stellt, ist, wie der Preis von BPT bestimmt wird, wenn eine bestimmte Menge (d. h. amountIn) von BPT gegen eine bestimmte Menge (d. h. amountOut) eines anderen Tokens getauscht wird.
Balancer bietet eine detaillierte Beschreibung der mathematischen Formeln, die sie für [6, 7] verschiedene Pools übernommen haben. Der Einfachheit halber abstrahieren und fassen wir hier die relevantesten Konzepte zusammen.
Nehmen wir den linear Pool als Beispiel: Der Preis von BPT wird in der Funktion onSwap des LinearPool-Kontrakts berechnet.

Die Berechnung lässt sich wie folgt zusammenfassen:

Hier wird die tokenRate mit der folgenden Formel berechnet:

ist ein konstanter Wert: .
In der obigen Formel kann der Zähler als die Summe des Saldos des main-Tokens und des Saldos des wrapped-Tokens vereinfacht werden, während der Nenner die Differenz zwischen einem vordefinierten Wert (d. h. _INITIAL_BPT_SUPPLY) und dem Saldo von BPT ist.
Es ist erwähnenswert, dass die Salden aller beteiligten Token normalisiert werden müssen, bevor die Berechnung durchgeführt wird, da verschiedene Token unterschiedliche Dezimalzahlen haben können. Insbesondere wird der Rohsaldo eines bestimmten Tokens mit einem entsprechenden Upscale-Faktor 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 komplizierter. 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 (falls vorhanden) erhalten wird.

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

Nehmen wir noch einmal bb-a-USDC als Beispiel: Die Kernlogik der entsprechenden getRate-Funktion folgt der Formel, die wir zuvor besprochen haben.

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

Außerdem gibt es eine Ablaufprüfung, wenn Updates für Pfade durchgeführt werden, die über die onSwap-Funktion laufen:

0x2 Schwachstellenanalyse
Die Ursache liegt in der Preismanipulation, die durch die Abrundungslogik innerhalb der onSwap-Funktion des linear Pools verursacht wird. Dies wirkt sich wiederum unangemessen auf die zwischengespeicherte Token-Rate aus, die vom boosted Pool verwendet wird.
Insbesondere wird amountOut abgerundet, wenn die Funktion _downscaleDown aufgerufen wird. Wenn also ein signifikanter Größenunterschied zwischen amountOut und scalingFactors[indexOut] besteht, 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 so betrachtet werden könnte, als würde man einseitig Liquidität von bb-a-USDC hinzufügen.
Als Ergebnis wird, wenn BPT als der Token für den Swap verwendet wird, seine Rate steigen, in Übereinstimmung mit der Formel zur Berechnung der Rate, da der Zähler gleich bleibt, während der Nenner abnimmt. Dieser Fehler könnte ausgenutzt werden, um zu einem (riesigen) Preisunterschied zu führen.
0x3 Angriffsanalyse
Die Angriffstransaktion besteht aus den folgenden Angriffsschritten:
- Leihen von 300.000 USDC per Flashloan von Aave.
- Tauschen von 1,067753 USDC gegen 0,970495 aUSDC im bb-a-USDC-Pool.
- Ausführen von
batchSwapin den Poolsbb-a-USDCundbb-a-USD, d. h. Ernten von 15.628bb-a-USDC, 139.431bb-a-DAIund 248.868bb-a-USDTmit 42.203USDC. Die detaillierten Schritte sind in der folgenden Tabelle (mit Dezimalzahlen) zusammengefasst:

- Tauschen von LP-Token gegen die entsprechenden zugrunde liegenden stabilen Token:
- 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 finale Profit ist:
- 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 abzog, was die Preismanipulation in Schritt 3 viel einfacher machen würde, 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 in die Details dieses Schrittes eintauchen, um herauszufinden, warum der Angreifer Profite erzielen konnte. Insbesondere:
- Schritte 3.1 werden verwendet, um
USDCmitbb-a-USDCaus dembb-a-USDC-Pool abzuziehen; - Schritte 3.3 und 3.4 werden verwendet, um
bb-a-USDCgegenbb-a-DAIzu tauschen, während Schritt 3.5 verwendet wird, umbb-a-USDCgegenbb-a-USDTzu tauschen. - Schritt 3.7 wird verwendet, um
USDCgegenbb-a-USDCaus dembb-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 besprochenen Abrundung, daher bleiben die Salden der Ziel-Token nach dem Swap 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 werden wir die Details jedes dieser Schritte der Reihe nach durchgehen.
(1) bb-a-USDC -> bb-a-DAI
In Schritt 3.3 liegt der Wechselkurs zwischen bb-a-USDC und bb-a-DAI bei fast 1, während der Wechselkurs in Schritt 3.4 19 wird:
- 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 Code-Logik erinnern, die wir zuvor besprochen haben, wird in Schritt 3.3, nachdem die zuvor zwischengespeicherte Token-Rate zur Berechnung des Skalierungsfaktors (1.012.181.365.780.643.700) zurückgegeben wurde, die Rate aktualisiert, 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 etwa 40-mal größer ist als die alte Rate.

Woher kommt jedoch dieser signifikante Anstieg? Lassen Sie uns die Formel zur Berechnung der tokenRate noch einmal besuchen. Da der Saldo von aUSDC in Schritt 2 erschöpft wurde, kann die Berechnung der tokenRate wie folgt vereinfacht werden:


Hier liegt der tatsächliche Wert des nominalMainBalance an der Abrundung, die in Schritt 3.2 auftritt.
(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 liegt bei 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 bptBalance in Schritt 3.6 erhöht, dann wird bptSupply in Schritt 3.7 zu Null. Auf diese Weise ist es möglich, USDC gegen bb-a-USDC zu einem Wechselkurs von fast 1:1 zu tauschen.

0x4 Zusammenfassung der Angriffe und Profite
Zum Zeitpunkt der Erstellung dieses Berichts haben wir dutzende Angriffe in freier Wildbahn beobachtet, die Verluste von über 2,12 Mio. $ verursacht haben. Zusammenfassend wurden diese Angriffe von drei verschiedenen Konten wie folgt ausgeführt:

Balancer erlitt aufgrund dieser Schwachstelle einen Gesamtverlust von ~1 Mio. $. Weniger als 12 Stunden nach dem ersten Angriff auf Balancer erlag dessen geforktes Protokoll, Beethoven X, ähnlichen Angriffen, was zu einem geschätzten Verlust von ~1,1 Mio. $ führte. Beethoven X verzeichnete noch größere Verluste als Balancer! Der kumulierte Verlust aus diesem Sicherheitsvorfall belief sich auf ~2,12 Mio. $.
Eine vollständige Liste dieser Angriffstransaktionen wurde in einem von uns vorbereiteten Dokument gesammelt. Bitte beziehen Sie sich darauf für detailliertere Informationen.
Einige Beobachtungen über die Angreifer
Bei 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 über die bemerkenswerten Unterschiede in den Schlüsselfunktionen hinaus zwei einzigartige Tricks, um nicht von MEV-Bots "front-run" (vorlaufen) zu werden. Darüber hinaus wurden die Gelder, die für den Angriff auf Fantom verwendet wurden, 163 Tage vor dem Angriff vorbereitet.
Aus den oben detaillierten Beobachtungen können wir ableiten:
- Mindestens zwei verschiedene Angreifer waren beteiligt.
- Der Angreifer auf Fantom ist ein erfahrener Serientäter.
0x5 Fazit
Zusammenfassend handelt es sich um eine subtile Schwachstelle, die in der Abrundungslogik verwurzelt ist. Das Ausnutzen dieser Schwachstelle ist jedoch nicht einfach. Insbesondere konnte der Angreifer die zwischengespeicherte Token-Rate durch Ausnutzen des Rundungsfehlers im linear Pool erhöhen und dadurch den Token-Preis im entsprechenden boosted Pool manipulieren.
Dieser Vorfall unterstreicht auch die Bedeutung einer zeitnahen Benachrichtigung für Projekte, die von der anfälligen Quelle ge-forkt wurden. Trotz Balancers Warnung gehen Angriffe auf geforkte Protokolle weiter, was unterstreicht, wie notwendig es für diese Projekte ist, über Sicherheitsupdates ihrer Quellprojekte informiert zu bleiben. Die Sicherstellung, dass diese geforkten Projekte zeitnahe Benachrichtigungen erhalten, stellt jedoch eine ständige Herausforderung für die Gemeinschaft dar.
Darüber hinaus unterstreicht die kontinuierliche Serie von Angriffen die Bedeutung einer proaktiven Bedrohungsprävention, die effektiv dazu beitragen könnte, potenzielle Verluste zu mildern.
Referenz
- [1] https://docs-v2.balancer.fi/concepts/overview/basics.html
- [2] Linear Pools: https://docs-v2.balancer.fi/concepts/pools/linear.html
- [3] Composable Stable Pools
- [4] Boosted Pools
- [5] https://docs-v2.balancer.fi/concepts/pools/boosted.html#example
- [6] https://docs-v2.balancer.fi/reference/math/linear-math.html
- [7] https://docs-v2.balancer.fi/reference/math/stable-math.html



