Back to Blog

~10,26 Mio. $: Angriffe auf Term Finance & MAYAChain | BlockSec

August 26, 2026
12 min read
Key Insights
  • Diese Woche werden 2 bemerkenswerte Sicherheitsvorfälle vorgestellt, mit einem Gesamtschaden von rund 10,26 Mio. USD. Der vorgestellte Vorfall, Term Finance (~8,5 Mio. USD), war eine Governance-Übernahme auf Ethereum, während MAYAChain (~1,76 Mio. USD) ein verkettetes Buchhaltungs- und Zustandsvalidierungsversagen auf einem Cosmos-SDK-Cross-Chain-Netzwerk war.

  • Die Governance von Term Finance pro Vault beruhte auf relativen Zustimmungs- und Beteiligungsschwellen ohne absolute Kapital- oder Mindestbreitengrenze; da fast niemand seine Vault-Anteile in das Governance-Token umgewandelt hatte, war die Beteiligung zu gering, damit eine Gegenstimme einen Vorschlag hätte ablehnen können. Ein Angreifer erlangte eine Zweidrittelmehrheit der Stimmrechte eines Vaults für etwa 0,5 ETH, und die Ausführungsverzögerung verschob das Ergebnis nur, ohne dass ein Guardian eingreifen konnte. Sechs Vaults von Term wurden auf diese Weise übernommen, mit einem Gesamtschaden von etwa 8,5 Mio. USD.

  • Auf MAYAChain beschädigte eine einzelne manipulierte Einzahlung die interne Buchhaltung der Chain so, dass gültige Auszahlungen als fehlgeschlagen behandelt wurden, was einen Wiederherstellungspfad auslöste, der den verzeichneten Native-Token-Bestand eines Pools mit geringer Liquidität ohne echte Deckung aufblähte; der Angreifer entzog diesen aufgeblähten Wert anschließend, indem er Liquidität hinzufügte und wieder entzog.

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 ETH gegen 0,485 tmvETH, wobei der Forwarder den Tausch abwickelte und die Vault-Anteile an den Angreifer auslieferte.

  • Schritt 2: Der Angreifer wrappte die 0,485 tmvETH im Verhältnis eins zu eins in 0,485 gtmvETH und erhielt so die Stimmkraft, die in den folgenden Schritten verwendet wurde.

  • Schritt 3: Der Angreifer rief propose() auf, was seine eigene Stimmkraft auslas und TokenVoting.createProposal() aufrief. Der Vertrag erfasste den Snapshot und las das gesamte Governance-Angebot zu diesem Block als nur 0,535 gtmvETH aus, 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 Absicherung canExecute() prüfte isSupportThresholdReached(): Da der Angreifer der einzige Ja-Stimmer war, überschritt das Zustimmungsverhältnis bei weitem den Schwellenwert von 50 %. Anschließend prüfte sie isMinParticipationReached(): Die eigene Stimmkraft des Angreifers allein überschritt bereits minVotingPower. 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 liquides WETH, 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...E9B7 mit 23 Nachrichten ein: 20 trade-:ARB~ETH-Auszahlungen, 2 trade-:ARB~LINK-Auszahlungen und eine abschließende DONATE: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 die CACAO-Seite des Pools um etwa 49,45 Mio. CACAO auf. Die Übertragung von der Reserve zu Asgard, die den Zuschuss hätte finanzieren sollen, schlug fehl, da die Reserve nur über etwa 168.000 CACAO verfü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 CACAO und 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 etwa 1 Billion LP-Einheiten gegenüber rund 731 Mio. bestehenden Einheiten.

  • Schritt 6: Der Angreifer zog seine Position umgehend fast vollständig zurück und erhielt dabei etwa 48,87 Mio. CACAO und 98,82 LINK, ausgezahlt aus den gemeinsamen Asgard-Vaults gegen den aufgeblähten Poolstand, und tauschte das entzogene CACAO über MAYAChain-Pools in BTC und andere Vermögenswerte. Der CACAO-Preis fiel während des Ausverkaufs von etwa 0,115 USD auf einen Tiefstwert von nahezu 0,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.

Get Started with Phalcon Explorer

Dive into Transactions to Act Wisely

Try now for free

Get Started with Phalcon Security

Detect every threat, alert what matters, and block attacks.

Try now for free

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.

~10,26 Mio. $: Angriffe auf Term Finance & MAYAChain | BlockSec