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
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

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

BlockSec Audit