Back to Blog

Der Schmetterlingseffekt: Der durch einen Bugfix verursachte Sicherheitsvorfall

Code Auditing
October 10, 2021
8 min read

Von BlockSec Team (@BlockSecTeam)

Letzte Woche gab es im Compound-Protokoll einen Fehler, der dazu führte, dass versehentlich eine große Menge an COMP-Token an Benutzer gesendet wurde. Die Ursache dieses Fehlers (Bug 2 in diesem Blog) liegt in der fehlerhaften Behebung eines anderen, zuvor entdeckten Fehlers (Bug 1 in diesem Blog).

In diesem Blog werden wir die Grundursache des ersten Fehlers und den Grund erläutern, warum die Behebung des ersten Fehlers den zweiten Fehler verursacht hat.

Hintergrund

Das Compound-Protokoll basiert auf dem Compound Whitepaper. Über die cToken-Verträge stellen Konten auf der Blockchain Kapital (Ether oder ERC-20-Token) bereit, um cTokens zu erhalten, oder leihen Vermögenswerte vom Protokoll aus (unter Verwendung anderer Vermögenswerte als Sicherheit). Die Compound cToken-Verträge verfolgen diese Guthaben und legen algorithmisch Zinssätze für Kreditnehmer fest.

Um Benutzer zu incentivieren, erhalten Benutzer, die Liquidität an Compound bereitstellen (Kapital bereitstellen), die Zinsen. Insbesondere stellen Benutzer Vermögenswerte (z. B. Ether oder andere ERC20-Token) an Compound bereit und erhalten die entsprechenden cTokens. Wenn der cToken an Compound zurückgegeben wird, werden die zugrunde liegenden Vermögenswerte (Ether oder ERC20-Token) und die Zinsen an den Benutzer zurückerstattet, sofern der Benutzer keine Schulden bei Compound hat. Wenn ein Benutzer beispielsweise 1000 Ether hat, kann er den Vermögenswert über cEth.mint(1000) in Compound einzahlen, um den cToken zu erhalten.

Der cToken repräsentiert die bei Compound gesperrten zugrunde liegenden Vermögenswerte. Der Benutzer kann den cToken weiter als Sicherheit verwenden, um andere Vermögenswerte zu leihen. Beispielsweise kann ein Benutzer 1000 Ether über ceth.mint(1000) einzahlen und dann die erhaltenen cTokens verwenden, um x Dai im Wert von 75 Ether zu leihen (Überbesicherung – diese Zahl hängt vom Sicherheitenfaktor ab) über cDai.borrow(x).

Die Kernlogik ist im Comptroller-Vertrag implementiert. Er verwaltet die Zustände eines Benutzers, z. B. wie viele Token der Benutzer in Compound eingezahlt hat, wie viele Token der Benutzer ausgeliehen hat und ob der Benutzer mehr Token ausleihen kann. Zu den in diesem Prozess aufgerufenen Funktionen gehören unter anderem getHypotheticalAccountLiquidityInternal(), borrowAllowed() und mintAllowed().

Compound hat auch den Governance-Token namens COMP. Der COMP-Token kann zur Abstimmung über Vorschläge verwendet werden. Außerdem kann der COMP-Token an Börsen gehandelt werden. Derzeit liegt der Preis von COMP bei etwa 300 US-Dollar.

Bug 1

Am 31. September 2021 gab es einen neuen Vorschlag (Vorschlag 62) in der Compound DAO, der darauf abzielte, einen Fehler im Comptroller zu beheben.

Der Fehler betrifft CompSpeed, der die Anzahl der COMP-Token darstellt, die Benutzern in jedem Block verteilt werden können.

Der Ablauf der mint-Funktion

Im Folgenden beschreiben wir anhand der mint-Funktion die Ursache dieses Fehlers. Die Aufrufkette der mint-Funktion lautet: mintmintInternalmintFresh.

In der Funktion mintFresh wird mintAllowed aufgerufen und dann der Saldo des Benutzers für den cToken aktualisiert.

In der Funktion mintAllowed werden zuerst updateCompSupplyIndex und dann distributeSupplierComp aufgerufen, um 1) den compSupplyState des Marktes zu aktualisieren und 2) die COMP-Token an die Benutzer zu verteilen.

updateCompSupplyIndex

Die Funktion updateCompSupplyIndex aktualisiert den Status jedes Marktes, hauptsächlich den compSupplyState[cToken].

In der Struktur CompMarketState wird die Blocknummer (block) dieser Aktualisierung und der Bonusindex (index) aufgezeichnet, der die Anzahl der COMP-Token beeinflusst, die an die Benutzer (die den cToken halten) verteilt werden sollen.

Was ist der Bonusindex (index) für jeden Token? Dies ist der über die Zeit akkumulierte Wert (siehe folgende Formel).

Dies zeigt die Anzahl der COMP, die an Benutzer (für jeden von dem Benutzer gehaltenen cToken) verteilt werden sollen.

distributeSupplierComp

Die andere Funktion distributeSupplierComp ist dafür verantwortlich, die Anzahl der COMP-Token, die dem Benutzer (Lieferanten) in compAccrued[supplier] zugewiesen werden sollen, aufzuzeichnen.

Insbesondere aktualisiert sie den globalen Bonusindex in compSupplyState (in der Funktion updateCompSupplyIndex). Dann zeichnet in der Funktion distributeSupplierComp der supplyIndex den aktuellen Bonusindex auf, und der supplierIndex zeigt den letzten Bonusindex für den Benutzer (Lieferanten) an. Der Delta-Wert ((supplyIndex - supplierIndex)) * das cToken-Guthaben des Benutzers zeigt die Anzahl der COMP-Token an, die dem Benutzer zugewiesen werden sollen.

Die Ursache von Bug 1

Es gibt eine weitere Funktion setCompSpeed, um die supplySpeed des Marktes (compSpeeds[address[cToken]]) anzupassen.

Das liegt daran, dass, wenn wir die CompSpeed für einen Markt auf Null setzen, dies bedeutet, dass der COMP-Token in diesem Markt nicht an Benutzer verteilt wird. Wenn wir also die Verteilung von COMP für einen Markt zuerst deaktivieren und dann wieder aktivieren möchten, können wir folgende Schritte befolgen:

  • Schritt I: Setzen Sie CompSpeed[cToken] auf Null, um die Verteilung von COMP-Token zu deaktivieren.
  • Schritt II: Rufen Sie die Funktion setCompSpeed auf, um CompSpeed[cToken] auf einen Nicht-Null-Wert zu setzen.

Schritt I: Für Märkte, bei denen die Verteilung von COMP-Token in Schritt I deaktiviert wurde (supplySpeed == 0), ist der Block nicht Null, da der Block in updateCompSupplyIndex kontinuierlich aktualisiert wird (else if (deltaBlocks > 0)).

Schritt II: Bei der Ausführung der Operation in Schritt II durchläuft die Funktion setCompSpeedInternal die Anweisung else if (compSpeed != 0) (Zeile 1083). Dann gibt es in den Zeilen 1088 bis 1093 eine Prüfung if (compSupplyState[address(cToken)].index == 0 && compSupplyState[address(cToken)].block == 0), um den index und block für einen neuen Markt zu initialisieren. Da wir jedoch die Verteilung von COMP-Token in einem bestehenden Markt (nicht in einem neuen Markt) wieder aktivieren, werden die Anweisungen in den Zeilen 1090 und 1091 nicht ausgeführt, um den index und block zu initialisieren (da compSupplyState[address(cToken)].block nicht Null ist).

Zusammenfassend lässt sich sagen, dass für einen derzeit deaktivierten Markt der index Null ist. Der block ist jedoch nicht Null. Das bedeutet, dass bei der Wiederaktivierung des deaktivierten Marktes durch Aufruf von setCompSpeed, um CompSpeed[cToken] auf einen Nicht-Null-Wert zu setzen, der Indexwert NICHT auf CompInitialIndex (1e36) zurückgesetzt wird (Zeilen 1090 und 1091 werden nicht ausgeführt).

Die Auswirkung von Bug 1

Wir untersuchen weiter die Funktion distributeSupplierComp, die für die Verteilung der COMP-Token zuständig ist.

Der supplierIndex ist compInitialIndex. Der supplyIndex ist jedoch aufgrund des Fehlers immer noch Null, was zu einem Unterlauf für Double memory deltaIndex = sub_(supplyIndex=0, supplierIndex=1e36) führt.

Bug 2: Eingeführt durch die Behebung von Bug 1

Um den Fehler zu beheben, ändert der Projektbesitzer die Code-Logik. Insbesondere initialisiert er den index sofort mit compInitialIndex, wenn ein neuer Markt initialisiert wird.

Da der globale Bonusindex (index) mit compInitialIndex initialisiert wurde, sollte der Benutzerbonusindex ebenfalls mit diesem Wert initialisiert werden. Schauen wir uns die Funktion distributeSupplierComp an.

Die If-Bedingung in Zeile 1234 kann auch dann nicht erfüllt werden, wenn supplierIndex == 0, da supplyIndex gleich (nicht größer als) compInitialIndex (1e36) ist. Dadurch wird der supplierIndex NICHT ordnungsgemäß auf compInitialIndex initialisiert (sein Wert ist 0). Dann wird der deltaIndex (supplyIndex - supplierIndex) compInitialIndex statt Null. Die supplierTokens werden zu einem großen Wert, wenn das cToken-Guthaben des Benutzers nicht Null ist.

Zusammenfassend lässt sich sagen: Wenn ein Benutzer vor der Behebung von Bug 1 zufällig eine Mint-Operation durchführt, hat er/sie cTokens und der supplierIndex wird Null (da der COMP-Token bereits verteilt wurde). Dann, nach der Behebung von Bug 1 (der Bug 2 einführt), kann der Benutzer, wenn er die mint-Funktion erneut aufruft, eine große Menge an COMP-Token (1e36 * ctoken.balanceOf(user)) erhalten.

Reale Welt

Wir zeigen die betroffenen Märkte unten:

0xF5DCe57282A584D2746FaF1593d3121Fcac444dC: cSAI
0x12392F67bdf24faE0AF363c24aC620a2f67DAd86: cTUSD
0x95b4eF2869eBD94BEb4eEE400a99824BF5DC325b: cMKR
0x4B0181102A0112A2ef11AbEE5563bb4a3176c9d7: cSUSHI
0xe65cdB6479BaC1e22340E4E755fAE7E509EcD06c: cAAVE
0x80a2AE356fc9ef4305676f7a3E2Ed04e12C33946: cYFI

Für den Benutzer (0xa7b95d2a2d10028cc4450e453151181cbcac74fc) erhielt der Benutzer in dieser Transaktion 0x6416ed016c39ffa23694a70d8a386c613f005be18aa0048ded8094f6165e7308 4.466,542459954989867175 COMP-Token.

Die weitere Untersuchung der Transaktion zeigt, dass aufgrund von Bug 2 der deltaIndex 1e36 beträgt und der Benutzer zu diesem Zeitpunkt zufällig den cToken hatte.

Die Behebung von Bug 2

Die Behebung von Bug 2 ist einfach. Sie ändert die If-Bedingung in der Funktion distributeSupplierComp.

Lektionen

  • Dies ist ein Fehler, der durch die Behebung eines anderen Fehlers verursacht wurde. Wie Codeänderungen für Projekte mit hoher Sichtbarkeit gründlich überprüft werden können, ist nach wie vor eine offene Frage.
  • Die DAO kann das Risiko der Zentralisierung eliminieren. Sie macht jedoch auch die Reaktion auf Sicherheitsvorfälle zu einem langsamen Prozess.
  • Hochkarätige DeFi-Projekte können gute Sicherheitspraktiken aus traditionellen Programmen übernehmen, z. B. die Einführung eines effizienten Fuzzing-Systems mit einem kontinuierlichen Testprozess.

Über BlockSec

BlockSec ist ein führendes Unternehmen für Blockchain-Sicherheit, das 2021 von einer Gruppe weltweit herausragender Sicherheitsexperten gegründet wurde. Das Unternehmen engagiert sich für die Verbesserung der Sicherheit und Benutzerfreundlichkeit für die aufstrebende Web3-Welt, um deren Massenadoption zu fördern. Zu diesem Zweck bietet BlockSec Sicherheitsaudits für Smart Contracts und EVM-Ketten, die Phalcon-Plattform für die sichere Entwicklung und die proaktive Blockierung von Bedrohungen, die MetaSleuth-Plattform für die Nachverfolgung und Untersuchung von Geldern sowie die MetaSuites-Erweiterung für Web3-Entwickler, um effizient in der Krypto-Welt zu navigieren.

Bisher hat das Unternehmen über 300 angesehene Kunden wie MetaMask, Uniswap Foundation, Compound, Forta und PancakeSwap betreut und in zwei Finanzierungsrunden von führenden Investoren, darunter Matrix Partners, Vitalbridge Capital und Fenbushi Capital, zweistellige Millionenbeträge in US-Dollar erhalten.

Offizielle Website: https://blocksec.com/

Offizielles Twitter-Konto: https://twitter.com/BlockSecTeam

Sign up for the latest updates
~$15.9M Lost: Trusted Volumes & More | BlockSec Weekly
Security Insights

~$15.9M Lost: Trusted Volumes & More | BlockSec Weekly

This BlockSec bi-weekly security report covers 11 notable attack incidents identified between April 27 and May 10, 2026, across Sui, Ethereum, BNB Chain, Base, Blast, and Berachain, with total estimated losses of approximately $15.9M. Three incidents are analyzed in detail: the highlighted $1.14M Aftermath Finance exploit on Sui, where a signed/unsigned semantic mismatch in the builder-fee validation allowed an attacker to inject a negative fee that was converted into positive collateral during settlement; the $5.87M Trusted Volumes RFQ authorization mismatch on Ethereum; and the $5.7M Wasabi Protocol infrastructure-to-contract-control compromise across multiple EVM chains.

Newsletter - April 2026
Security Insights

Newsletter - April 2026

In April 2026, the DeFi ecosystem experienced three major security incidents. KelpDAO lost ~$290M due to an insecure 1-of-1 DVN bridge configuration exploited via RPC infrastructure compromise, Drift Protocol suffered ~$285M from a multisig governance takeover leveraging Solana's durable nonce mechanism, and Rhea Finance incurred ~$18.4M following a business logic flaw in its margin-trading module that allowed circular swap path manipulatio

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly
Security Insights

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly

This BlockSec weekly security report covers eight attack incidents detected between April 20 and April 26, 2026, across Ethereum, Avalanche, Sui, Base, HyperLiquid, and MegaETH, with total estimated losses of approximately $7.04M. The highlighted incident is the $1.3M GiddyDefi exploit, where the attacker did not break any cryptography or use a flash loan but simply replayed an existing on-chain EIP-712 signature with the unsigned `aggregator` and `fromToken` fields swapped out for a malicious contract, demonstrating how partial signature coverage turns any historical signature into a generic permit. Other incidents include a $3.5M Volo Vault operator key compromise on Sui, a $1.5M Purrlend privileged-role takeover, a $413K SingularityFinance oracle misconfiguration, a $142.7K Scallop cross-pool index injection, a $72.35K Kipseli Router decimal mismatch, a $50.7K REVLoans (Juicebox) accounting pollution, and a $64K Custom Rebalancer arbitrary-call exploit.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit