Hintergrund
Am 22. August gab Balancer offiziell bekannt, dass es schwerwiegende Schwachstellenmeldungen erhalten hatte, die mehrere V2-Boost-Pools betrafen. Nur 1,4 % der TVL waren betroffen, und die Benutzer wurden aufgefordert, Liquiditäts-LP so schnell wie möglich abzuheben. [1] [2]
Am 27. August entdeckte das SlowMist MistEye-System eine Angriffstransaktion, bei der der Verdacht bestand, dass sie die Balancer-Schwachstelle ausnutzte. [3]
Da der Pool nicht ausgesetzt werden kann und einige Gelder immer noch von dem Angriff betroffen sind, erinnern die Beamten von Balancer die Benutzer noch einmal daran, die LP im betroffenen Pool abzurufen. [4] Anschließend veröffentlichte Balancer offiziell die Details der im August auf Medium [5] veröffentlichten Schwachstelle, und das SlowMist-Sicherheitsteam überprüfte sie. Die Details lauten wie folgt:
Einführung
Das Balancer-Team stellte in seiner Mitteilung einfach fest, dass das Problem bei der Abrundung im Linear-Pool und der virtuellen Versorgung des kombinierbaren Pools liegt, wodurch bptSupply auf 0 gesetzt wird. Lassen Sie uns zunächst die Inhalte des Balancer-Protokolls, die mit diesem Sicherheitsproblem zusammenhängen, näher betrachten.
Balancer V2 Vault
Das Balancer V2 [6] Protokoll ist ein dezentralisiertes automatisiertes Market-Maker (AMM) Protokoll, das auf Ethereum basiert und die flexiblen Bausteine programmierbarer Liquidität darstellt. Der Hauptbestandteil ist der Vault-Vertrag, der alle Aufzeichnungen der Pools verwaltet und das Buchhaltung und die Übertragung von Token, einschließlich der Verpackung und Entpackung von nativen ETH, verwaltet. Das bedeutet, dass die Implementierung des Vault die Buchhaltung und Verwaltung von Token von der Logik der Pools trennt.
Im Vault gibt es vier Schnittstellen: joinPool, exitPool, swap und batchSwap (das Hinzufügen, Verlassen und Tauschen sind separate Aufrufe und nicht in einer einzigen Transaktion kombinierbar). Eine herausragende Funktion ist batchSwap, das mehrere atomare Tauschvorgänge zwischen verschiedenen Pools ermöglicht und die Ausgabe eines Pools mit der Eingabe eines anderen Pools verbindet (GiveIn und GiveOut). Dieses System führt auch Flash-Swaps [7] ein, ähnlich einem internen Flash-Darlehen.
Linearpools
Um die Kapitalrendite der LP zu erhöhen und die hohen Kosten des Warps und Unwarps zu bewältigen, wurde in V2 der Linear-Pool als Lösung eingeführt, wodurch das BPT (ERC20 Balancer Pool Token) eingeführt wurde.
Linearpools [8] enthalten das Haupt-Token (Basisvermögen), das gewickelte Token (verpackte Token) und das BPT-Token, die Vermögenswerte und ihre verpackten, renditeträchtigen Entsprechungen zu den bekannten Wechselkursen tauschen. Je höher der Anteil der verpackten Token, desto höher sind die Rendite und die Kapitalrendite des Liquiditätspools. Im Warp-Prozess wird normalerweise ein Skalierungsfaktor verwendet, um sicherzustellen, dass verschiedene Token mit der gleichen Genauigkeit berechnet werden.
Kombinierbare Pools
Alle Balancer-Pools sind kombinierbare Pools, die andere Token enthalten, wobei der Pool selbst auch sein eigenes Token hat. Das BPT-Token bezeichnet den ERC20 Balancer Pool Token, der die Grundlage aller Pools darstellt. Benutzer können das BPT-Token in anderen Pools frei kombinieren, um einen Tausch durchzuführen. Der Tausch umfasst immer einen Pool und zwei Token: GiveIn und GiveOut. In steht für das Einspeisen von Komponenten-Token und den Empfang von BPT, während Out bedeutet, dass BPT eingespeist und Komponenten-Token empfangen werden. Wenn BPT selbst ein Komponenten-Token ist, kann es wie andere Token getauscht werden. Diese Implementierung bildet einen einfachen BatchSwap-Pfad zwischen den Basisvermögen und den Token in externen Pools, wobei Benutzer BPT verwenden können, um auf die Basisvermögen des Linear-Pools zuzugreifen, was ebenfalls die Grundlage des Balancer Boosted Pool [9] ist.
Durch diese Kombination entsteht der kombinierbare Pool von Balancer. Ein bb-a-USD kombinierbarer stabiler Pool besteht aus drei Linear-Pools, die gleichzeitig ungenutzte Liquidität an externe Protokolle (Aave) senden. Beispielsweise ist bb-a-DAI ein Linear-Pool, der DAI und waDAI (verpacktes aDAI) enthält. Wenn Benutzer einen BatchSwap durchführen müssen (z. B. USDT in DAI umtauschen), könnte ein Austauschpfad wie folgt aussehen:
1. Im USDT Linear-Pool wird USDT gegen bb-a-USDT getauscht (Eingang in den USDT Linear-Pool);
2. Im bb-a-USD tauscht bb-a-USDT bb-a-DAI (Tausch zwischen linearen BPTs);
3. Im DAI Linear-Pool wird bb-a-DAI in DAI getauscht (Ausgang aus dem DAI Linear-Pool).
Nachdem wir die obigen Grundlagen verstanden haben, kommen wir zur Analyse des Sicherheitsproblems.
Analyse
Am 27. August erhielt das SlowMist-Sicherheitsteam eine Warnung vom MistEye-System über einen verdächtigen Ausnutzungsversuch von Balancer. Die Transaktion [3] lautet wie folgt:
Der Angreifer leiht zunächst 300.000 USDC über einen Flash-Darlehen von AAVE. Anschließend ruft er die BatchSwap-Operation des Vaults auf, um die Berechnung des Tauschs zwischen den BPT-Token durch den kombinierten stabilen Pool bb-a-USD vorzunehmen, und tauscht schließlich 94.508 USDC in 59.964 bb-a-USDC, 68.201 bb-a-DAI und 74.280 bb-a-USDT um. Schließlich verlässt er den Pool über den exitPool des Vault-Vertrags, um die zugrunde liegenden Vermögenswerte zu erhalten, das Flash-Darlehen zurückzuzahlen und einen Gewinn von etwa 108.843,7 USD zu realisieren.
Daraus ist ersichtlich, dass der Schlüssel zu diesem Angriff im BatchSwap liegt, was genau im BatchSwap passiert ist? Lassen Sie uns das näher untersuchen.
Der Angreifer tauscht im gesamten BatchSwap-Prozess zuerst USDC im bb-a-USDC-Pool und tauscht dann zwischen den BPT-Token, indem er bb-a-USDC in bb-a-DAI, bb-a-USDT und USDC tauscht. Schließlich wird das zugrunde liegende Haupt-Token USDC in bb-a-USDT getauscht. Das bedeutet, dass bb-a-USDC als Schlüssel-BPT-Token sowohl als GiveOut als auch als GiveIn fungiert.
Im ersten Schritt tauscht der Angreifer mit einem festen Skalierungsfaktor im bb-a-USDC Linear-Pool USDC-Haupttoken gegen BPT-Token, dessen erhöhte Menge im bptBalance des Pools aufgezeichnet wird. Doch nach dem zweiten Tausch mit onSwap stellen wir fest, dass im gleichen Tauschprozess der amountOut-Wert von USDC 0 beträgt. Warum ist das so?
Wenn wir in die onSwap-Funktion eintauchen, stellen wir fest, dass in diesem Prozess zunächst eine Genauigkeitsverarbeitung nominal durchgeführt wird, um den entsprechenden Skalierungsfaktor des Tokens zu berechnen. Bei der anschließenden Aufruf der downscaleDown-Funktion kann es jedoch vorkommen, dass amountOut abgerundet wird. Wenn der Unterschied zwischen amountOut und scalingFactors[indexOut] sehr groß ist, wird der berechnete downscaleDown-Wert null.

Das bedeutet, wenn wir das BPT-Token verwenden, um das Haupt-Token zu tauschen, wird der Rückgabewert auf null gerundet, wenn amountOut zu klein ist, und dieser Wert ist kleiner als die durch scalingFactors berechneten 1e12. Aber die Menge von bb-a-USDC, die in amountIn kommt, wird weiterhin zur virtuellen Menge von bptBalance hinzugefügt, und dieser Vorgang erhöht das Guthaben im bb-a-USDC-Pool, was als einseitiges Hinzufügen von bb-a-USDC-Liquidität betrachtet werden kann.


Dann nutzt er die Eigenschaften kombinierbarer stabiler Pools, um die Umwandlung zwischen BPT-Token zu ermöglichen, indem er zuerst bb-a-USDC in andere BPT-Token tauscht. Im Verlauf dieses Tauschs zeigt der Aufrufpfad der kombinierbaren stabilen Pools bb-a-DAI onSwap -> swapGivenIn -> onSwapGivenIn, dass bb-a-USDC nacheinander in bb-a-DAI und bb-a-USDT umgewandelt wird. Im Gegensatz zu Linear-Pools muss bei kombinierbaren stabilen Pools vor der Durchführung einer onSwap-Operation der Wechselkurs aktualisiert werden. Aus dem Code können wir sehen, dass onSwap im kombinierbaren Pool zuerst prüft, ob der Wechselkurs des Tokens aktualisiert werden muss.


Nach den vorherigen Tauschvorgängen hat sich die Menge von bb-a-USDC geändert, und die nominalisierte Gesamtsumme beträgt totalBalance 994.010.000.000, die virtuelle Versorgung der BPT-Token beträgt 20.000.000.000. Daraus kann berechnet werden, dass der aktualisierte Wechselkurs nahezu 45-mal so hoch ist wie der ursprüngliche Wechselkurs von 1.100.443.876.587.504.549 im Linear-Pool, also 49.700.500.000.000.000.000.

Anschließend wird bb-a-USDC im Linear-Pool gegen USDC getauscht. Doch dieser Tausch führt, wie der zweite Tausch, erneut dazu, dass amountOut auf 0 gerundet wird, und der Austauschpfad ist derselbe wie zuvor.
Der nächste Tausch erfolgt umgekehrt, indem USDC in bb-a-USDC getauscht wird, und der Austauschpfad ist onSwap -> onSwapGivenIn -> _swapGivenMainIn. In diesem Prozess stellen wir fest, dass bei der Berechnung des benötigten amountOut die Berechnung der virtuellen Versorgung auf der Differenz zwischen dem nach dem Tausch verbleibenden BPT-Token totalsupply und dem verbleibenden Pool basiert, wobei diese Differenz 0 beträgt.

Das liegt daran, dass bptSupply 0 ist und bei der Berechnung von BPT Out direkt die Funktion _toNominal aufgerufen wird, wodurch der Umtauschkurs für USDC gegen bb-a-USDC nahezu 1:1 beträgt.


Zusammenfassung
BatchSwap ermöglicht es, durch mehrere Pools hindurch mehrere atomare Tauschvorgänge durchzuführen, indem die Ausgabe eines Pools mit der Eingabe eines anderen Pools verbunden wird (tokenIn und tokenOut), wobei USDC in BPT-Token getauscht wird. In diesem BatchSwap erfolgt kein tatsächlicher Token-Transfer, sondern die Anzahl der übertragenen und empfangenen Token wird aufgezeichnet, um die endgültige Tauschmenge zu bestätigen. Da Linear-Pools über die Basisvermögen-Token tauschen, erfolgt der Tausch über eine virtuelle Versorgung, und die Berechnung erfolgt mithilfe eines festen Algorithmus zur Bestimmung des Wechselkurses. Daher gibt es in BatchSwap zwei Sicherheitsanfälligkeiten:
Erstens das Problem der Abrundung im Linear-Pool, der Angreifer erhöht das Verhältnis der zwischengespeicherten Token, indem er durch Abrundung einseitig Haupt-Token in den Pool hinzufügt, und manipuliert dadurch den Wechselkurs der Token im entsprechenden kombinierbaren Pool.
Zweitens, aufgrund der Merkmale der virtuellen Versorgung des kombinierbaren Pools wird die virtuelle Versorgung berechnet, indem die BPT-Token von dem Guthaben im Pool abgezogen werden. Wenn GiveIn ein BPT-Token ist, wird diese Menge von der Versorgung abgezogen. Der Angreifer muss nur BPT als GiveIn verwenden, um den Tausch durchzuführen und die Versorgung auf 0 zu manipulieren, um anschließend umgekehrt zu tauschen, wobei BPT dann als GiveOut fungiert. In diesem Fall ist die Versorgung 0, und der Algorithmus wird den tatsächlichen Tausch unterhalb des linearen Pools nach einem nahezu 1:1-Verhältnis durchführen, wodurch die Menge an GiveOut BPT-Token indirekt manipuliert wird.
Wir können feststellen, dass die erste Sicherheitsanfälligkeit den Wechselkurs beim Tausch erhöht, während die zweite Sicherheitsanfälligkeit den Wechselkurs beim umgekehrten Tausch senkt, wodurch der Angreifer von der doppelten Buffernutzung profitiert.
Referenzlink:
[1]https://twitter.com/Balancer/status/1694014645378724280
[2]https://forum.balancer.fi/t/vulnerability-found-in-some-pools/5102?u=endymionjkb
[3]https://etherscan.io/tx/0x7020e0ccafff2c86db3df5a2af0cccb4e931fe948f69bf20ea517b0cc99c1f15
[4]https://twitter.com/Balancer/status/1695777503699435751
[5]https://medium.com/balancer-protocol/rate-manipulation-in-balancer-boosted-pools-technical-postmortem-53db4b642492
[6]https://docs.balancer.fi/concepts/overview/basics.html
[7]https://docs.balancer.fi/reference/swaps/flash-swaps.html#flash-swaps
[8]https://docs.balancer.fi/concepts/pools/linear.html
[9]https://docs.balancer.fi/concepts/pools/boosted.html
