Im Laufe der letzten Woche (17.08.2026 - 23.08.2026) werden die folgenden 2 bemerkenswerten Sicherheitsvorfälle vorgestellt, die Gesamtverluste von etwa 10,26 Mio. USD betreffen.
| Datum | Vorfall | Typ | Geschätzter Verlust |
|---|---|---|---|
| 18.08.2026 | MAYAChain | Fehler in der Geschäftslogik | ~1,76 Mio. USD |
| 23.08.2026 | Term Finance | Fehlerhaftes Governance-Design | ~8,5 Mio. USD |
Gründe für die Auswahl
- MAYAChain: Ausgewählt, weil ein verkettetes Versagen bei der Buchführung und der Zustandsvalidierung einer einzelnen präparierten Einzahlung ermöglichte, die ausgehende Abstimmung zu korrumpieren und den erfassten Kontostand eines Pools mit geringer Liquidität ohne echte Deckung aufzublähen. Dies zeigt, wie mehrere geringgradige Schwachstellen, die für sich genommen begrenzt sind, sich zu einem Abfluss in einem Cross-Chain-Liquiditätsnetzwerk zusammensetzen können.
- Term Finance: Ausgewählt, weil eine nahezu inexistente Beteiligung an der Governance keine Wählerschaft übrig ließ, um einen bösartigen Vorschlag abzulehnen, und es keinen Guardian- oder Stornierungspfad gab, der die Ausführungsverzögerung abgesichert hätte. Dies ermöglichte es einem Angreifer mit minimalem Kapitaleinsatz, die Abstimmung zu dominieren, einen Vorschlag durchzusetzen und rund 8,5 Mio. USD aus sechs Protokoll-Vaults abzuziehen.
Best Security Auditor for Web3
Validate design, code, and business logic before launch
Wöchentliches Highlight: Term Finance
Term Finance wird diese Woche vorgestellt, weil das Versagen kein Programmierfehler war, sondern ein Governance-Design-Fehler – eine Risikoklasse, die wächst, je mehr Protokolle mit jedem Vault eine separate On-Chain-DAO ausliefern: Wenn kaum jemand teilnimmt, kann die Kontrolle über die Abstimmung günstig erkauft werden, und die eigene Governance des Vaults wird selbst zur Angriffsfläche.
Am 23. August 2026 verlor Term Finance, ein Protokoll für festverzinsliche Kreditvergabe auf Ethereum, etwa 8,5 Mio. USD, als ein Angreifer die Kontrolle über die On-Chain-Governance der Vaults übernahm. Da praktisch keine Einleger jemals den Governance-Token eines Vaults geprägt hatten, konnte der Angreifer für rund 0,5 ETH eine übergroße Mehrheit der Stimmrechte erwerben, die Schwellenwerte für Zustimmung und Mindestbeteiligung des Protokolls überwinden und einen bösartigen Vorschlag ausführen, der die Strategien des Vaults widerrief und dessen Vermögenswerte transferierte; sechs der Vaults von Term wurden auf diese Weise geleert [1].
Hintergrund
Term Finance ist ein Protokoll für festverzinsliche Kreditvergabe auf Ethereum. Term Vaults sind ein separates Produkt, das darüber aufgesetzt ist: Jeder Vault ist ein ERC-4626-Vault, der auf Yearn-V3-Code basiert, wobei ein Meta-Vault einen einzelnen Vermögenswert entgegennimmt und ihn über eine Reihe von Strategie-Vaults verteilt. Das Framework von Term stellt zusammen mit jedem Vault eine Aragon-OSx-DAO und einen TokenVoting-Vertrag bereit, und diese DAO besitzt die Upgrade- und Rollenautorität über den Vault, mit dem sie ausgeliefert wird. Ein Vault trägt daher von Geburt an seine eigene Governance-Angriffsfläche, unabhängig davon, wer ihn kuratiert oder ob jemals Kapital in ihn investiert wird.
Governance-Entscheidungen werden von TokenVoting getroffen. Jeder Vault besitzt seinen eigenen Anteilstoken und einen passenden Aragon-GovernanceWrappedERC20-Governance-Wrapper; im analysierten ETH-Meta-Vault sind dies tmvETH und gtmvETH. Jeder Inhaber eines Anteilstokens kann depositFor() aufrufen, um den Governance-Token im Verhältnis eins zu eins zu erhalten, und jeder Inhaber eines Governance-Tokens kann die daraus resultierenden Stimmen mittels delegate() delegieren. Wenn ein Vorschlag erstellt wird, erfasst TokenVoting.createProposal() einen snapshotBlock, einen supportThreshold und ein minVotingPower, das aus dem Token-Angebot zu diesem Snapshot abgeleitet wird; die Stimmkraft wird dann zu diesem Block aus dem Wrapper ausgelesen.
Schwachstellenanalyse
Der zentrale Governance-Vertrag dieses Vorfalls ist TokenVoting. Die Ausführung von Vorschlägen wird nur durch _canExecute() abgesichert, was bei einem normalen (nicht-vorzeitigen) Vorschlag erfordert, dass die Abstimmung noch nicht ausgeführt wurde, dass der Vorschlag abgeschlossen ist, und dass zwei Prüfungen erfolgreich sind: isSupportThresholdReached() und isMinParticipationReached().

Beide Prüfungen sind korrekte Implementierungen einer Mehrheitsabstimmung, aber jede misst einen relativen Anteil statt eines absoluten Betrags. isSupportThresholdReached() erfordert lediglich, dass die Ja-Stimmen die Nein-Stimmen über das konfigurierte Verhältnis hinaus überwiegen:

isMinParticipationReached() erfordert, dass die abgegebenen Stimmen den Wert minVotingPower erreichen, der selbst als minParticipation * totalSupply zum Snapshot-Zeitpunkt abgeleitet wird:

Die Grundursache liegt darin, dass dieses Governance-Design keine absolute Untergrenze für das Kapital oder die Breite festlegte, die nötig sind, um einen Vorschlag durchzusetzen. Beide Kontrollmechanismen sind rein relativ zu den abgegebenen Stimmen und dem Token-Angebot, und weil fast kein tmvETH-Inhaber jemals an der Governance teilgenommen hatte, indem er in gtmvETH gewrappt hätte, lag dieses Angebot – und damit die Untergrenze für die Mindestbeteiligung – nahe null. Eine relative Mehrheit einer nahezu leeren Wählerschaft reichte daher aus, um beide Prüfungen zu überwinden, und bei so wenigen Wählern gab es niemanden, der die Gegenstimmen abgeben konnte, die den Vorschlag hätten scheitern lassen können. Eine Mindestabstimmungsdauer verzögerte die Ausführung, aber ohne Guardian- oder Stornierungspfad verzögerte diese Frist den durchkommenden Vorschlag nur, statt ihn zu verhindern.
Angriffsanalyse
Der Angreifer erwarb einen bestimmenden Anteil des Governance-Tokens eines Vaults und nutzte ihn dann, um einen Vorschlag durchzusetzen und auszuführen, der diesen Vault leerte; dieselbe Methode wurde bei sechs der Vaults von Term angewendet. Die folgende Analyse verfolgt einen Vault anhand der Transaktionen 0xd354a1...d3014129 und 0x9f273f...44c2e8a0.
-
Schritt 1: Der Angreifer tauschte über einen Forwarder von Mayan Finance
0,5 ETHgegen0,485 tmvETH, wobei der Forwarder den Tausch abwickelte und die Vault-Anteile an den Angreifer auslieferte. -
Schritt 2: Der Angreifer wrappte die
0,485 tmvETHim Verhältnis eins zu eins in0,485 gtmvETHund erhielt so die Stimmkraft, die in den folgenden Schritten verwendet wurde. -
Schritt 3: Der Angreifer rief
propose()auf, was seine eigene Stimmkraft auslas undTokenVoting.createProposal()aufrief. Der Vertrag erfasste den Snapshot und las das gesamte Governance-Angebot zu diesem Block als nur0,535 gtmvETHaus, sodass der Anteil des Angreifers etwa 90,66 % der gesamten Wählerschaft ausmachte. -
Schritt 4: Der Angreifer rief
vote()auf, um seine gesamte Stimmkraft zugunsten des Vorschlags einzusetzen, und wurde dadurch der einzige Teilnehmer. -
Schritt 5: Nach der Abstimmungsperiode rief der Angreifer
executeProposal()auf. Die AbsicherungcanExecute()prüfteisSupportThresholdReached(): Da der Angreifer der einzige Ja-Stimmer war, überschritt das Zustimmungsverhältnis bei weitem den Schwellenwert von 50 %. Anschließend prüfte sieisMinParticipationReached(): Die eigene Stimmkraft des Angreifers allein überschritt bereitsminVotingPower. Beide Prüfungen wurden bestanden. -
Schritt 6: Der bösartige Vorschlag wurde daraufhin ausgeführt, entzog dem
tmvETH-Vault die seiner Strategie zugewiesenen Mittel, wandelte sie zurück in liquidesWETH, das der Vault hielt, und ermöglichte dem Angreifer die Auszahlung der Vermögenswerte. Über sechs der Vaults von Term hinweg angewendet, verursachte der Angriff Gesamtverluste von etwa 8,5 Mio. USD.
Fazit
Dieser Vorfall wurde durch ein fehlerhaftes Governance-Design verursacht und nicht durch einen Programmierfehler: Die Zustimmungs- und Beteiligungsprüfungen waren rein relativ, sodass bei einer nahezu leeren Wählerschaft eine billig erworbene Mehrheit auf keine Gegenstimmen traf, und die Ausführungsverzögerung hatte keinen Guardian- oder Stornierungspfad hinter sich. Governance, die die Verwahrung von Vermögenswerten kontrolliert, sollte ein absolutes Quorum oder eine Beteiligungsuntergrenze erzwingen und jede Ausführungsverzögerung durch einen Guardian- oder Stornierungspfad sowie einen überwachten Vorschlags-Feed absichern, da eine Zeitsperre einen durchkommenden Vorschlag nur verzögert, sofern niemand in dieser Zeit eingreifen kann.
Weitere Vorfälle dieser Woche
MAYAChain
Am 18. August 2026 verlor MAYAChain, ein Cosmos-SDK-basiertes Cross-Chain-Liquiditätsnetzwerk, etwa 1,76 Mio. USD, als eine einzelne präparierte Einzahlung die interne Buchführung der Chain korrumpierte. Indem gültige Auszahlungen als fehlgeschlagen erscheinen ließ, löste die Einzahlung einen Wiederherstellungspfad aus, der den erfassten Kontostand des nativen Tokens eines Pools mit geringer Liquidität aufblähte, ohne dass eine echte Deckung bestand; der Angreifer entzog diesen aufgeblähten Wert anschließend, indem er Liquidität hinzufügte und wieder abzog [2]. Die bestätigten On-Chain-Abflüsse beliefen sich auf etwa 1,36 Mio. USD, überwiegend 20,83 BTC, und mit den verbleibenden Beständen des nativen Tokens ergibt sich die offizielle Schätzung von etwa 1,76 Mio. USD.
Hintergrund
MAYAChain ist ein Cosmos-SDK-basiertes Cross-Chain-Liquiditätsnetzwerk, dessen natives Asset CACAO ist. Ereignisse auf externen Chains werden von Validatoren beobachtet und als beobachtete Transaktionen in MAYAChain wieder eingespielt. Wenn eine eingehende Aktion Vermögenswerte nach außen senden muss, plant der Node ein oder mehrere TxOutItems ein und stimmt später die beobachteten ausgehenden Transaktionen mit diesen geplanten Datensätzen ab. Alle Pool-Vermögenswerte werden gemeinsam in gemeinsamen Asgard-Vaults verwahrt; jeder Liquiditätspool ist eine Buchhaltungsposition über diese gemeinsame Verwahrung und kein separierter Kontostand, und Auszahlungen werden aus Asgard gegen die erfassten Kontostände eines Pools bezahlt.
Trade-Konten sind native MAYAChain-Buchhaltungspositionen für Handelsassets wie ARB~ETH und ARB~LINK. Eine Auszahlung von einem Trade-Konto wird über eine native MsgDeposit mit einem Memo wie trade-:ARB~LINK initiiert. Obwohl der Nutzer eine einzelne native Transaktion signiert, rekonstruiert der Einzahlungshandler eine interne beobachtete Transaktion und speichert einen ObservedTxVoter, der anhand des Hashes der nativen Transaktion indiziert wird.
Schwachstellenanalyse
Die Grundursache war nicht ein einzelner isolierter Fehler; es war eine Kette von Buchhaltungs- und Zustandsvalidierungsfehlern, die sich zusammensetzten. Die fehlerhafte Logik befindet sich in den Einzahlungs- und Ausgangshandlern von MAYANode.
Zunächst erstellt in handler_deposit.go jede Nachricht in einer nativen Transaktion einen neuen ObservedTxVoter, der anhand des Transaktionshashes indiziert wird, und speichert diesen mit SetObservedTxInVoter():
txIn := ObservedTx{Tx: tx}
txInVoter := NewObservedTxVoter(txIn.Tx.ID, []ObservedTx{txIn})
txInVoter.Height = ctx.BlockHeight()
txInVoter.FinalisedHeight = ctx.BlockHeight()
txInVoter.Tx = txIn
h.mgr.Keeper().SetObservedTxInVoter(ctx, txInVoter)
Da jede Nachricht in einem Batch die gleiche tx.ID teilt, überschreibt eine spätere Nachricht den von früheren Nachrichten geschriebenen Voter-Zustand und verwirft dabei deren Metadaten zur Planung der ausgehenden Transaktionen.
Zweitens beginnt in handler_common_outbound.go die Abstimmung der ausgehenden Transaktionen bei voter.OutboundHeight (oder bei voter.FinalisedHeight, wenn Ersteres null ist) und scannt vorwärts in der Signaturperiode:
outHeight := voter.OutboundHeight
if outHeight == 0 {
outHeight = voter.FinalisedHeight
}
for height := outHeight; height <= ctx.BlockHeight(); height += signingTransPeriod {
txOut, err = h.mgr.Keeper().GetTxOut(ctx, height)
...
}
Wenn der Voter auf diese Weise überschrieben wurde, beginnt der Scan bei der Höhe der finalisierten Einzahlung und prüft niemals den Block, der die geplanten ausgehenden Transaktionen enthält, sodass der Handler diese als fehlend behandelt und den Slash-Recovery-Pfad auslöst.
Drittens bewertet in helpers.go der Wiederherstellungspfad das „fehlende" Asset als CACAO-Zuschuss anhand des rohen beobachteten Betrags, ohne Obergrenze gegen die tatsächliche Assettiefe des Pools:
f.stolenAsset = f.stolenAsset.Add(coin.Amount)
runeValue := pool.AssetValueInRune(coin.Amount)
f.subsidiseRune = f.subsidiseRune.Add(runeValue)
In einem Pool mit nur etwa 0,11 LINK Assettiefe konnte diese ungedeckelte Umrechnung einen beobachteten LINK-Betrag auf einen enormen CACAO-Wert abbilden. Dieselbe Routine schreibt dann den aufgeblähten Poolstand in den Zustand, bevor der Versuch der Finanzierungsübertragung erfolgt:
pool.BalanceCacao = pool.BalanceCacao.Add(f.subsidiseRune)
pool.BalanceAsset = common.SafeSub(pool.BalanceAsset, f.stolenAsset)
if err = mgr.Keeper().SetPool(ctx, pool); err != nil {
...
}
runeToAsgard := common.NewCoin(common.BaseAsset(), f.subsidiseRune)
if err = mgr.Keeper().SendFromModuleToModule(ctx, ReserveName, AsgardName, common.NewCoins(runeToAsgard)); err != nil {
ctx.Logger().Error("fail to send subsidy from bond to asgard", "error", err)
return err
}
Da das Schreiben des Pools vor der Übertragung erfolgt, wird der BalanceCacao-Wert des Pools niemals zurückgesetzt, wenn die Übertragung nicht finanziert werden kann. Schließlich schluckt in handler_observed_txout.go der Aufrufer den zurückgegebenen Fehler, markiert den Voter als abgeschlossen und fährt fort, sodass ein inkonsistenter Poolzustand fortbestehen kann:
_, err = handler(ctx, m)
if err != nil {
ctx.Logger().Error("handler failed:", "error", err)
slashObservedOutbound("failed_outbound")
voter.SetDone()
h.mgr.Keeper().SetObservedTxOutVoter(ctx, voter)
continue
}
Angriffsanalyse
Der Angreifer kombinierte diese Schwachstellen in einer einzelnen nativen Transaktion und entzog anschließend Wert aus dem aufgeblähten Pool. Die folgende Analyse basiert auf der MAYAChain-Transaktion 516BA14D...E9B7.
-
Schritt 1: Der Angreifer reichte die native Transaktion
516BA14D...E9B7mit 23 Nachrichten ein: 20trade-:ARB~ETH-Auszahlungen, 2trade-:ARB~LINK-Auszahlungen und eine abschließendeDONATE:ARB.LINK-Nachricht über eine Basiseinheit. -
Schritt 2: Die Trade-Auszahlungen erzeugten gültige geplante ausgehende Transaktionen, aber die abschließende
DONATE-Nachricht überschrieb den gemeinsamen Eingangs-Voter für denselben nativen Transaktionshash und löschte damit die Planung der ausgehenden Transaktionen der früheren Auszahlungen. -
Schritt 3: Als die
ARB.LINK-Ausgangstransaktionen später beobachtet wurden, verwendete die Abstimmung die Höhenfelder des überschriebenen Voters und erreichte niemals den Block mit den geplanten ausgehenden Transaktionen, sodass diese als fehlend behandelt wurden. -
Schritt 4: Der Slash-Recovery-Pfad bewertete das fehlende LINK gegen den dünnen
ARB.LINK-Pool und blähte dieCACAO-Seite des Pools um etwa49,45 Mio. CACAOauf. Die Übertragung von der Reserve zu Asgard, die den Zuschuss hätte finanzieren sollen, schlug fehl, da die Reserve nur über etwa168.000 CACAOverfügte – weit weniger als der aufgeblähte Zuschuss; der veränderte Poolstand blieb jedoch bestehen, und der fehlgeschlagene Handler wurde als abgeschlossen markiert. -
Schritt 5: In einem späteren Block fügte der Angreifer dem verzerrten Pool
100 CACAOund eine kleine Menge LINK als Liquidität hinzu. Da eine Seite des Pools weit außer Proportion getrieben worden war, gewährte die Formel zum Hinzufügen von Liquidität dem Angreifer etwa1 BillionLP-Einheiten gegenüber rund731 Mio.bestehenden Einheiten. -
Schritt 6: Der Angreifer zog seine Position umgehend fast vollständig zurück und erhielt dabei etwa
48,87 Mio. CACAOund98,82 LINK, ausgezahlt aus den gemeinsamen Asgard-Vaults gegen den aufgeblähten Poolstand, und tauschte das entzogeneCACAOüber MAYAChain-Pools in BTC und andere Vermögenswerte. DerCACAO-Preis fiel während des Ausverkaufs von etwa0,115 USDauf einen Tiefstwert von nahezu0,013 USD.
Die vom Angreifer abgezogenen 48,87 Mio. CACAO waren zum Vor-Exploit-Preis nominell mehr als 5 Mio. USD wert, wurden aber nicht vollständig als externer Erlös realisiert: Ein Großteil davon wurde zurück in Pools getauscht, wodurch der CACAO-Preis abstürzte, und opportunistische Arbitrageure, die nichts mit dem Angreifer zu tun hatten, entzogen während des Absturzes ebenfalls Wert. Der direkt bestätigte On-Chain-Abfluss belief sich auf etwa 1,36 Mio. USD, überwiegend 20,83 BTC, und unter Einbeziehung der verbleibenden CACAO- und Trade-Konto-Bestände des Angreifers ergibt sich die offizielle Schätzung von etwa 1,76 Mio. USD.
Fazit
Dieser Vorfall wurde durch eine Kette von Buchhaltungs- und Zustandsvalidierungsfehlern verursacht und nicht durch einen einzelnen Fehler: Kein Schritt war für sich genommen katastrophal, aber zusammen ermöglichten sie es, dass eine präparierte Einzahlung den erfassten Kontostand eines dünnen Pools in ungedeckten Wert verwandelte. Die zentrale Anforderung ist Atomarität und begrenztes Vertrauen: Eine Buchhaltungsänderung und die Übertragung, die sie finanzieren soll, müssen gemeinsam gelingen oder scheitern, ein fehlgeschlagener Wiederherstellungspfad darf niemals als abgeschlossen markiert werden, und Bewertungen gegen Pools mit geringer Liquidität müssen gedeckelt sein. Gemeinsamer Eingangszustand sollte außerdem niemals stillschweigend von einer späteren Nachricht in derselben Transaktion überschrieben werden.
Referenzen
Über BlockSec
BlockSec ist ein Full-Stack-Anbieter für Blockchain-Sicherheit und Krypto-Compliance. Wir entwickeln Produkte und Dienstleistungen, die Kunden dabei helfen, Code-Audits durchzuführen (einschließlich Smart Contracts, Blockchain und Wallets), Angriffe in Echtzeit abzufangen, Vorfälle zu analysieren, illegale Gelder zurückzuverfolgen und AML/CFT-Verpflichtungen zu erfüllen – über den gesamten Lebenszyklus von Protokollen und Plattformen hinweg.
BlockSec hat mehrere Blockchain-Sicherheitspapiere auf renommierten Konferenzen veröffentlicht, mehrere Zero-Day-Angriffe auf DeFi-Anwendungen gemeldet, mehrere Hacks blockiert, um mehr als 20 Millionen Dollar zu retten, und Kryptowährungen im Wert von mehreren Milliarden abgesichert.
-
Offizielle Website: https://blocksec.com/
-
Offizieller Twitter-Account: https://twitter.com/BlockSecTeam



