In der vergangenen Woche (12.10.2026–18.10.2026) gab es die folgenden 3 bemerkenswerten Sicherheitsvorfälle mit einem geschätzten Gesamtschaden von ungefähr 3,87 Millionen USD.
| Datum | Vorfall | Art | Geschätzter Schaden |
|---|---|---|---|
| 12.10.2026 | Autheo Network | Kompromittierung eines privaten Schlüssels | ~1,76 Millionen USD |
| 14.10.2026 | UNCX Network | Buchführungsfehler | ~807 Tausend USD |
| 18.10.2026 | Qubic Wallet | Schwache Seed-Generierung | ~1,30 Millionen USD |
Die Schadenszahlen sind Schätzungen auf Grundlage des realisierten Werts.
- UNCX Network: Dieser Vorfall wird ausführlich analysiert, weil seine fehlerhafte Berechnung der aggregierten Anteile das anschaulichste Beispiel dieser Woche für einen Fehler in der Buchführung von Smart Contracts liefert.
Kostenloser Sicherheits-Scan
Eine schnelle Sicherheitsprüfung mit unserer intern entwickelten automatisierten Analyse-Engine.
Kostenlos scannenWochenschwerpunkt: UNCX Network
Am 14.10.2026 nutzten Angreifer Schwachstellen in den Smart Token Vesting V2-Verträgen von UNCX Network auf Ethereum, BNB Chain, Base und Robinhood Chain aus. Der Vorfall betraf 10 Vermögenswerte in 11 bestätigten Transaktionen, bei denen Mittel abgezogen wurden, und führte zu einem realisierten oder weitergeleiteten Wert von ungefähr 807,609 Tausend USD [1]. Die Ursache war eine fehlerhafte Berechnung der aggregierten Anteile, bei der Vesting-Tranchen vermischt wurden, die zu unterschiedlichen Token-Anteil-Verhältnissen angelegt worden waren. Dadurch wichen die Datensätze einzelner Positionen vom globalen Anteilsbestand ab, sodass von Angreifern kontrollierte Positionen mehr Token abziehen konnten, als die Angreifer eingebracht hatten.
Hintergrund
UNCX Network ist ein Multi-Chain-Anbieter von DeFi-Infrastruktur. Mit seinem Produkt Smart Token Vesting V2 können Projekte ERC-20-Token gemäß zeitbasierten Vesting-Plänen sperren. Jeder Plan wird durch eine Vesting-ID repräsentiert und erfasst den Token, den Ersteller, den Begünstigten, den Gesamtbetrag, den bereits freigegebenen Betrag und eine oder mehrere Freigabetranchen. Einer flexiblen Position, die Aufstockungen zulässt, können nach ihrer Erstellung weitere Tranchen hinzugefügt werden; ihr Ersteller kann den noch nicht unverfallbaren Teil stornieren.
Positionen für denselben Token teilen sich ein Guthaben im VestingManager, und das Protokoll bestimmt anhand von Anteilen, welcher Teil dieses Guthabens jeder Position gehört. Die Buchführung umfasst daher zwei miteinander verbundene Ebenen:
Global pool, per token
create / topUp: receive tokens and mint shares
release: convert releasable shares into tokens
_totalTokenShares[token]: active shares across all positions
Position record, per vesting ID
_depositedShares[id]: share basis assigned to the position
totalAmount: token amount represented by its tranches
released: shares already paid to the beneficiary
Bei einer Freigabe berechnet der Manager zunächst, welcher Teil des Plans unverfallbar geworden ist. Er wandelt diesen Anteil von totalAmount mithilfe von _depositedShares[vestingId] in unverfallbare Anteile um, zieht released ab und bezeichnet das Ergebnis als releasableShares. In der folgenden Formel ist tokenBalance das aktuelle ERC-20-Guthaben des Managers für den Token des Plans, das von allen Positionen für diesen Token gemeinsam genutzt wird. _totalTokenShares[token] ist der zugehörige globale Bestand aktiver Anteile. Die Token-Auszahlung hängt vom Verhältnis dieser beiden Größen ab:
amount = releasableShares * tokenBalance / _totalTokenShares[token]
Einzahlungen über die Vesting-Funktionen erhöhen sowohl das Token-Guthaben als auch den Anteilsbestand. Eine direkte Übertragung von ERC-20-Token an den Manager verändert nur das Token-Guthaben und damit die Anzahl der Token, die jeder Anteil repräsentiert. Dieses Anteilsmodell kann Positionen in einem gemeinsamen Pool nur unterstützen, wenn bei jedem Vorgang im Lebenszyklus die Positionsdatensätze mit dem globalen Bestand abgeglichen bleiben.
Schwachstellenanalyse
Der betroffene Ethereum-VestingManager unter 0xA98F...99F5 enthält eine fehlerhafte Berechnung der aggregierten Anteile, die durch das Zusammenspiel von release(), topUpVesting() und cancel() ausnutzbar wird. Entsprechende betroffene Deployments gibt es auf den anderen drei Netzwerken.
Um die Zustandsänderungen leichter vergleichen zu können, verwendet die folgende Analyse D, R und V als Kurzbezeichnungen. Diese Symbole kommen im Vertragscode nicht vor; die Tabelle ordnet jedes Symbol der entsprechenden Vertragsvariablen oder dem entsprechenden Ausdruck zu. Die zugeordneten Werte gehören zu zwei Arten von Größen:
| Symbol | Vertragsvariable / Ausdruck | Art der Größe | Bedeutung |
|---|---|---|---|
D |
_depositedShares[vestingId] |
Anzahl der Anteile | Kumulative Anteilsbasis, die allen für die Position erfassten Einzahlungen zugeordnet ist |
R |
schedule.released |
Anzahl der Anteile | Kumulative Anzahl der bereits an den Begünstigten ausgezahlten Anteile |
| - | schedule.totalAmount |
Token-Menge | Für die aktuellen Tranchen der Position insgesamt erfasste Token-Menge |
| - | vestedAmount |
Token-Menge | Anteil von totalAmount, der gemäß diesen Tranchen zum aktuellen Zeitpunkt unverfallbar geworden ist |
V |
vestedShare |
Anzahl der Anteile | Kumulative unverfallbare Anteile, berechnet durch Anwendung eines einzigen gemischten Verhältnisses unverfallbarer Token auf D |
In release() berechnet der Vertrag releasableShares, erhöht schedule.released und zieht dieselbe Anzahl von _totalTokenShares[schedule.token] ab. _depositedShares[vestingId] wird nicht verringert. Nach einer Freigabe trägt die Position daher nur noch D - R aktive Anteile zum globalen Bestand bei, obwohl ihre gespeicherte Basis weiterhin D beträgt.
function release(uint256 vestingId) external nonReentrant {
...
uint256 releasableShares = _getReleasableShares(vestingId);
uint256 balance = IERC20(schedule.token).balanceOf(address(this));
uint256 amount =
(releasableShares * balance) / _totalTokenShares[schedule.token];
schedule.released += releasableShares;
_totalTokenShares[schedule.token] -= releasableShares;
IERC20(schedule.token).safeTransfer(msg.sender, amount);
}
cancel() wendet ein einziges gemischtes Vesting-Verhältnis auf die aggregierte Anteilsbasis der Position an. Die Funktion berechnet zunächst vestedAmount über getVestedAmount(vestingId) und wandelt dann den in Token bemessenen unverfallbaren Anteil gemäß V = (vestedAmount * D) / totalAmount in Anteile um. Schließlich behandelt die Funktion D - V als die entfernbaren, noch nicht unverfallbaren Anteile der Position. V ist ein kumulativer Wert und nicht die Zahl der unverfallbaren Anteile, die nach früheren Auszahlungen verbleiben.
function cancel(uint256 vestingId) external nonReentrant {
...
uint256 vestedAmount = getVestedAmount(vestingId);
uint256 remainingAmount = schedule.totalAmount - vestedAmount;
uint256 vestedShare =
vestedAmount * _depositedShares[vestingId] / schedule.totalAmount;
uint256 sharesToRemove =
_depositedShares[vestingId] - vestedShare;
_totalTokenShares[schedule.token] -= sharesToRemove;
_depositedShares[vestingId] = vestedShare;
schedule.totalAmount = vestedAmount;
schedule.cancelled = true;
IERC20(schedule.token).safeTransfer(schedule.creator, remainingAmount);
}
Der Fehler liegt in der gemischten Berechnung selbst. Jede Tranche hat ihre eigene Token-Menge, ihren eigenen Vesting-Fortschritt und eine Anzahl von Anteilen, die zu dem beim Hinzufügen der Tranche geltenden Pool-Verhältnis geprägt wurden. Ihre unverfallbaren Anteile müssten daher separat berechnet und anschließend addiert werden. Stattdessen aggregiert der Vertrag zunächst die Token-Mengen und die Anteilsbasis und wendet dann ein einziges Vesting-Verhältnis auf die kombinierte Position an, weil er nur ein D und ein totalAmount speichert. Beide Ansätze sind nur dann gleichwertig, wenn die Tranchen dasselbe Verhältnis von Anteilen pro Token oder denselben Vesting-Fortschritt aufweisen. Eine direkte Token-Übertragung verändert vor einer Aufstockung das Pool-Verhältnis, sodass die neue Tranche pro Token deutlich weniger Anteile erhält als die ursprüngliche Tranche. Wird anschließend ein einziges gemischtes Vesting-Verhältnis auf beide Tranchen angewendet, kann sich ein V ergeben, das ihre kombinierten unverfallbaren Anteile nicht mehr repräsentiert.
Da R bereits aus den kumulativen unverfallbaren Anteilen der Position ausgezahlt wurde, muss ein konsistenter Plan R <= V erfüllen. Die Verletzung dieser Ungleichung ist das unmittelbare Symptom der gemischten Berechnung. Der Code führt für die Position keinen separaten Bestand aktiver Anteile; er zieht D - V direkt vom globalen _totalTokenShares ab. Angenommen, andere Positionen tragen 1000 Anteile bei, während für diese Position D = 100 und R = 80 gelten. Vor der Stornierung umfasst der globale Bestand 1,000 + (D - R) = 1,020 Anteile. Ergibt die gemischte Berechnung V = 60, zieht cancel() D - V = 40 ab. Damit verbleiben global nur 980 Anteile – 20 weniger als die 1000 Anteile, die von den anderen Positionen beigetragen wurden. Enthielte der globale Bestand nur die 20 aktiven Anteile dieser Position, würde das Abziehen von 40 einen Unterlauf auslösen und die Transaktion rückgängig machen. Der überhöhte Abzug gelingt nur, weil der gemeinsame Pool Anteile enthält, die von anderen Positionen beigetragen wurden. Anschließend speichert die Funktion _depositedShares[vestingId] = 60, während schedule.released weiterhin 80 beträgt. Dadurch befindet sich der Positionsdatensatz im unmöglichen Zustand D < R.
Der Code erzwingt weder R <= V noch begrenzt er sharesToRemove auf die aktiven Anteile der Position. Bei einer Stornierung können daher historische Anteile abgezogen werden, die nicht mehr zum globalen Bestand gehören. Gleichzeitig überträgt die Funktion totalAmount - vestedAmount direkt in Token-Einheiten, ohne die Erstattung anhand des Anteilsverhältnisses des Pools umzurechnen. Die Stornierung selbst führt nicht zu einer überhöhten Auszahlung; sie verringert lediglich den globalen Anteilsbestand unter die Zahl der aktiven Anteile, die über alle Positionen hinweg erfasst sind, und schafft damit die Voraussetzung für eine spätere überhöhte Einlösung.
Analyse des Angriffs
Die folgende Analyse betrachtet beispielhaft eine Ethereum-Transaktion, 0x352065...c23823. Der Angriffsvertrag führte innerhalb dieser Transaktion dieselbe Kernabfolge gegen fünf Token aus und nutzte für jeden Token Flash-Liquidität.
-
Schritt 1: Der Angreifer erstellte eine kleine, sofort unverfallbare Position,
#5058, sowie zwei wesentlich größere, sofort unverfallbare, flexible Positionen mit Aufstockungsmöglichkeit,#5059und#5060. Die kleine Position sollte für den abschließenden Abzug erhalten bleiben, während die großen Positionen die historischen Anteilsdatensätze lieferten, die zur Verfälschung der globalen Buchführung nötig waren. -
Schritt 2: Der Angreifer ließ die beiden großen Positionen freigeben. Beim anfänglichen Pool-Verhältnis von annähernd 1:1 brachte jede Freigabe ungefähr die jeweilige Nettoeinzahlung zurück. Die entsprechenden Anteile schieden aus
_totalTokenSharesaus, doch die ursprünglichen Werte blieben in den_depositedShares-Datensätzen für die Positionen#5059und#5060erhalten. Als freigegeben wurden sie nur im separaten Feldreleaseddes jeweiligen Plans erfasst. -
Schritt 3: Der Angreifer übertrug Token direkt an den
VestingManager. Dadurch wurde das Token-Guthaben wieder aufgefüllt, ohne die freigegebenen Anteile wiederherzustellen. Die Anzahl der Token, die jeder verbleibende Anteil repräsentierte, stieg daher stark an. -
Schritt 4: Der Angreifer stockte die bereits freigegebenen großen Positionen auf. Da die Aufstockungen zum nun erhöhten Verhältnis von Token pro Anteil bewertet wurden, prägten beträchtliche Token-Einzahlungen nur wenige neue Anteile. Die Positionen vereinten nun eine große, veraltete historische Basis mit einem kleinen Beitrag aktiver Anteile.
-
Schritt 5: Der Angreifer stornierte die beiden aufgestockten Positionen. Jede Stornierung wandte ein einziges gemischtes Vesting-Verhältnis auf die aggregierte Historie der eingezahlten Anteile an. Das neu berechnete
vestedSharelag unter den bereits freigegebenen Anteilen, sodasssharesToRemoveden Beitrag aktiver Anteile der Position überstieg. Der globale Bestand wurde um ungefähr5.0e25Phantomanteile pro Position verringert, obwohl jede Position bei der Aufstockung nur wenige aktive Anteile hinzugefügt hatte. -
Schritt 6: Bei dem nun erheblich zu niedrig angesetzten Nenner ließ der Angreifer die kleine, zurückbehaltene Position freigeben. Im durchgerechneten Token-Beispiel wurden mit einer Nettoeinzahlung von ungefähr
5.73e22Roheinheiten ungefähr1.07e26Roheinheiten abgezogen – etwa das 1,873-Fache der Einzahlung. Der Angreifer wiederholte Zyklen aus Erstellung und Freigabe unter Ausnutzung des verfälschten Verhältnisses und zahlte die Flash-Liquidität zurück.

Der offizielle Vorfallsbericht stellte dasselbe Muster bei 11 bestätigten Transaktionen fest, bei denen Mittel abgezogen wurden. Einige Varianten wurden atomar abgeschlossen, während bei anderen die abschließende Freigabe in einer verknüpften Transaktion erfolgte. UNCX berechnete den Wert von ungefähr 807,609 Tausend USD anhand realisierter oder weitergeleiteter Erlöse, nachdem das vom Angreifer eingesetzte Kapital, vorübergehendes Flash-Volumen und zurückgegebene Vermögenswerte ausgeschlossen worden waren [1].
Erste Schritte mit Phalcon Explorer
Transaktionen eingehend untersuchen, um fundiert zu handeln
Jetzt kostenlos ausprobierenFazit
Die Ursache war eine fehlerhafte Buchführung über die aggregierten Anteile. Das Protokoll kombinierte Tranchen, die zu unterschiedlichen Token-Anteil-Verhältnissen angelegt worden waren, und wandte anschließend ein einziges Vesting-Verhältnis auf ihre aggregierte Anteilsbasis an. Dadurch wurden die kumulativen unverfallbaren Anteile falsch berechnet. Eine Position konnte mehr Anteile entfernen, als sie noch beitrug, und die Datensätze einzelner Positionen wichen vom globalen Anteilsbestand ab. Die Angreifer nutzten den zu niedrig angesetzten globalen Bestand aus, um Token aus dem gemeinsamen Guthaben abzuziehen, die weiterhin benötigt wurden, um Ansprüche aus anderen Vesting-Positionen zu erfüllen.
Ein Buchführungsmodell, das spätere Einzahlungen bei veränderlichen Anteilsverhältnissen zulässt, sollte die Token-Menge, die Anteilsbasis und den Vesting-Fortschritt jeder Tranche separat erhalten, statt sie unter einem einzigen Verhältnis zusammenzufassen. Es sollte außerdem sicherstellen, dass kumulative Auszahlungen niemals die kumulativen unverfallbaren Anteile übersteigen und dass eine Position nicht mehr als ihren Beitrag an aktiven Anteilen entfernen kann.
Quellen
[1] Vorfallsbericht von UNCX Network zu Smart Token Vesting V2
Bester Sicherheitsauditor für Web3
Design, Code und Geschäftslogik vor dem Start prüfen



