Wie in unserem vorherigen Artikel hervorgehoben, weisen über 30 % der Projekte im Repository Awesome Uniswap v4 Hooks[1] Schwachstellen auf. Es ist erwähnenswert, dass sich die hier genannten Schwachstellen speziell auf Uniswap v4-Interaktionen beziehen. Entsprechend werden wir in diesem Artikel die sichere Hook-Interaktionslogik aus den folgenden zwei Perspektiven untersuchen:
- Fehlerhafte Zugriffskontrolle
- Unzureichende Eingabevalidierung
Für jede Kategorie beginnen wir mit einer Analyse der Schwachstelle und demonstrieren deren potenzielle Ausnutzung, indem wir den entsprechenden Proof-of-Concept (PoC) bereitstellen. Darauf folgt eine Diskussion über mögliche Abhilfemaßnahmen.
Fehlerhafte Zugriffskontrolle
Im Allgemeinen können Interaktionen im Zusammenhang mit Uniswap v4-Hooks danach klassifiziert werden, ob der Hook als Locker fungiert und eine Sperre im PoolManager erwirbt, um Operationen in Pools durchzuführen. Zwei primäre Interaktionsszenarien erfordern ordnungsgemäße Zugriffskontrollen:
- Hook-PoolManager Interaktion: Dies beinhaltet Interaktionen zwischen den offiziellen Callback-Funktionen und dem
PoolManager. Die Callback-Funktionen umfassen acht Pool-Action-Callbacks (d. h.initialize,modifyPosition,swapunddonate) und den Lock-Callback (d. h.lockAcquired).
- Hook-Intern Interaktion: Dies bezieht sich auf Interaktionen, die innerhalb des Hook-Vertrags (der als Locker fungiert) auftreten.
Hook-PoolManager-Interaktionen sind relativ unkompliziert. Hier fungiert der Hook rein als Hook und akzeptiert die acht Pool-Action-Callbacks. Die Logik im Hook wirkt sich nicht auf verwandte Pools aus, was bedeutet, dass keine Gelder zwischen dem Hook und den Pools fließen. Die von den Callback-Funktionen bereitgestellten Parameter werden verwendet, um notwendige Speicher zu ändern oder als wichtige Funktionsparameter zu dienen. Die wichtigste Überlegung ist, ob die Callback-Parameter manipuliert werden können.
Hook-Intern-Interaktionen sind etwas komplexer. In der Praxis tun viele Hook-Prototypen mehr, als nur als reine Hooks zu fungieren. Einige Entwickler erlauben den Hooks, Fondsverwaltungsfunktionen für ihre Benutzer bereitzustellen. Diese Funktionen sind möglicherweise nicht in den Hook-Verträgen implementiert, aber wir können sie in diesem Kontext kollektiv als Hooks betrachten. In diesen Fällen akzeptiert ein Hook Benutzergelder und führt Pooloperationen wie Liquiditätsmanagement oder Swaps durch. Dies bedeutet, dass der Vertrag eine Sperre vom PoolManager erwerben muss, wodurch der Hook zu einem Locker wird.
Die Uniswap Foundation hat diese Situation berücksichtigt und eine Funktion in ihre Hook-Vorlage integriert. Insbesondere bietet die BaseHook-Vorlage die lockAcquired-Funktion als Lock-Callback an, wie folgt:
function lockAcquired(bytes calldata data) external virtual poolManagerOnly
returns (bytes memory) {
(bool success, bytes memory returnData) = address(this).call(data);
if (success) return returnData;
if (returnData.length == 0) revert LockFailure();
// if the call failed, bubble up the reason
/// @solidity memory-safe-assembly
assembly {
revert(add(returnData, 32), mload(returnData))
}
}
Um benutzerdefinierte Logik auszuführen, akzeptiert lockAcquired data-Bytes und führt einen Low-Level-Aufruf an sich selbst mit diesen data durch. Die data hängt von der Geschäftslogik des Hooks ab und kann von Benutzern manipuliert werden, was aufgrund von Hook-Intern-Interaktionen, die durch lockAcquired ausgelöst werden, potenziell zu Sicherheitsproblemen führen kann. Beachten Sie, dass das Hook-Design so flexibel ist, dass wir in dieser Situation nicht alle möglichen Szenarien abdecken können. Unser Hauptaugenmerk liegt hier auf dem Erwerb einer Sperre durch den Hook und seinen nachfolgenden internen Interaktionen. Das Eingehen auf andere potenzielle Geschäftslogik würde die Situation für diese Diskussion zu komplex machen.
In beiden Szenarien ist es vorrangig, alle fehlerhaften Zugriffskontrollen anzugehen, die potenziell zu Ausnutzungen führen könnten, da diese Funktionen klare Interaktionsentitäten haben. In den folgenden Unterabschnitten werden wir jedes Szenario sequenziell untersuchen und die notwendigen Zugriffskontrollen diskutieren, um eine sicherere Interaktionslogik zu gewährleisten.
Schwachstellenanalyse
Zugriffskontrollen dienen als hocheffiziente und unkomplizierte Sicherheitslösungen für viele Projekte. Wenn eine Funktion für bestimmte Entitäten aufrufbar sein soll, sollte sie eine Zugriffskontrolle beinhalten. Das bekannteste Beispiel für Zugriffskontrolle ist der Ownable-Vertrag der OpenZeppelin-Bibliothek, der erfordert, dass privilegierte Funktionen nur vom Vertragsbesitzer aufgerufen werden. Es ist klar, dass die beiden oben diskutierten Szenarien geeignete Fälle für diese Art von Kontrolle sind.
Hook-PoolManager-Interaktion: Für sichere Interaktionen mit dem PoolManager sollten Hooks notwendige Zugriffskontrollen für diese Callback-Funktionen durchsetzen. Insbesondere sollten diese Callbacks ausschließlich vom PoolManager und keinen anderen Konten aufrufbar sein. Das Versäumnis, solche Kontrollen einzurichten, kann dazu führen, dass diese sensiblen Schnittstellen potenzieller Ausnutzung durch böswillige Akteure ausgesetzt sind.
Über die acht Pool-Action-Callbacks hinaus muss auch der Lock-Callback (d. h. lockAcquired), der benutzerdefinierte Logik nach dem Erwerb des Locks vom PoolManager ausführt, dieses Problem angehen.
Hook-Intern-Interaktion: Die an den Hook-internen Interaktionen beteiligten Funktionen sind ebenfalls so konzipiert, dass sie von bestimmten Aufrufern aufgerufen werden. Wie wir bereits erwähnt haben, enthält dieses Szenario zwei Phasen. Erstens wird die lockAcquired-Funktion des Lockers vom PoolManager aufgerufen, was bedeutet, dass die Funktion den msg.sender als PoolManager erfordern sollte. Zweitens dispatcht der Hook den Funktionsaufruf entsprechend. Basierend auf dem Design von BaseHook wird dies durch Low-Level-Aufrufe an den Hook selbst implementiert. Dies bedeutet, dass diese Funktionen als external definiert sein und den Aufrufer auf die Adresse des Hooks beschränken müssen.
Nehmen wir eines der im Repository Awesome Uniswap v4 Hooks aufgeführten Beispiele, z. B. die Stop Loss Order als Beispiel[2]:
Direkt in die Uniswap V4-Pools integriert, werden Stop-Loss-Orders On-Chain platziert und über den afterSwap()-Hook ausgeführt. Keine externen Bots oder Akteure sind erforderlich, um die Ausführung zu garantieren.
Betrachten wir seine afterSwap-Callback-Funktion:
![Abbildung 1: Die afterSwap-Funktion der Stop-Loss-Order[2]](https://assets.blocksec.com/frontend/blocksec-strapi-online/1_09ba973d9e.webp)
Es ist klar, dass die obige Funktion für die Durchführung sensibler Operationen konzipiert ist. Aufgrund fehlerhafter Zugriffskontrollen könnte sie jedoch von böswilligen Akteuren ausgenutzt werden, die die Argumente (z. B. key und params) manipulieren, was zu unerwarteten Verhaltensweisen führt. Zum Beispiel könnte der afterSwap-Callback davon ausgehen, dass der Swap bereits im PoolManager stattgefunden hat. Danach könnte er Aktionen initiieren, um wichtige Zustandsinformationen aufzuzeichnen, wie z. B. den aktuellen Preis oder gesammelte Swap-Gebühren. Wenn afterSwap seine Aufrufe jedoch nicht strikt auf den PoolManager beschränkt, könnten böswillige Akteure den params-Parameter fälschen, was zu verzerrten aufgezeichneten Zuständen führt.
Exploit & PoC
Zur Vereinfachung verwenden wir einen einfachen PoC, um dieses Zugriffskontrollproblem zu veranschaulichen. Im Allgemeinen akzeptiert der beforeInitialize-Hook des Hooks einen Parameter vom Typ PoolKey, der die Hook-Adresse in seinem hooks-Feld enthalten muss (da der PoolManager dieses Feld verwendet, um die aufzurufende Hook-Adresse zu bestimmen).
Der Screenshot zeigt einen PoC, der die Ausnutzung eines Hooks mit fehlerhafter Zugriffskontrolle demonstriert, wie im DiamondHookPoC [3] zu sehen ist.
In Ermangelung von Zugriffsbeschränkungen für die beforeInitialize-Callback-Funktion können böswillige Akteure dieser Funktion einen beliebigen poolKey übergeben. Der Hook prüft nicht, ob der Hook dieses poolKey mit der aktuellen Hook-Adresse übereinstimmt.

Obwohl es wichtig ist zu beachten, dass der Exploit in diesem Szenario möglicherweise keine finanziellen Verluste für den Hook verursacht, verdeutlicht er dennoch dramatisch, wie der Zustand des Hooks durch ungeschützte Callback-Funktionen manipuliert werden kann.
Wie man sich absichert
Um die Sicherheit von Hook-PoolManager-Interaktionen zu gewährleisten, sollten sowohl die Hook-Callbacks als auch der Lock-Callback ihre Zugänglichkeit ausschließlich auf den PoolManager beschränken.
Glücklicherweise bietet Uniswap v4 Best Practices über die BaseHook in seinem v4-periphery Repository[4].
Die BaseHook bietet den poolManagerOnly-Modifier, um Aufrufe strikt vom PoolManager einzuschränken:
/// @dev Nur der Poolmanager darf diese Funktion aufrufen
modifier poolManagerOnly() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
Dieser Modifier kann effektiv eingesetzt werden, um die ordnungsgemäße Zugriffskontrolle für die sensiblen Hook- und Lock-Callbacks durchzusetzen.
Andererseits erfordert das Vorhandensein von Hook-Intern-Interaktionen, dass alle bedeutenden zustandsverändernden Funktionen, die über den lockAcquired-Callback aufgerufen werden, wie in BaseHook spezifiziert, nicht willkürlich aufrufbar sein sollten.
Um dieser Anforderung gerecht zu werden, bietet die BaseHook einen selfOnly-Modifier. Dieser Modifier beschränkt die Zugänglichkeit der deklarierten Funktion auf den Hook selbst und verbietet externen Verträgen, diese sensiblen Funktionen zu bösartigen Zwecken direkt aufzurufen.
/// @dev Nur diese Adresse darf diese Funktion aufrufen
modifier selfOnly() {
if (msg.sender != address(this)) revert NotSelf();
_;
}
Zusammenfassend lässt sich sagen, dass benutzerdefinierte Hooks durch die Vererbung von BaseHook diese integrierten Zugriffskontroll-Modifier und Callbacks nutzen können, um eine ordnungsgemäße Zugriffskontrolle durchzusetzen.
Unzureichende Eingabevalidierung
Die BaseHook in v4-periphery[4] bietet eine Lösung für sicherere Interaktionslogik, die Entwickler von Hooks nutzen können. Wir beobachten jedoch weiterhin Fälle von unsachgemäßer Verwendung, die neue Angriffsmöglichkeiten in bestehenden Hooks eröffnen.
Standardmäßig erlauben Hooks die Registrierung beliebiger Pools über die initialize-Funktion im PoolManager. Wenn ein Hook jedoch die zugrunde liegenden Vermögenswerte im registrierten Pool nicht validiert, könnten böswillige Benutzer einen Pool mit gefälschten Token registrieren, was es ihnen ermöglicht, über die transfer-Funktion der Token erneut in den Hook einzudringen.
Diese Schwachstelle ist subtil, da der Hook selbst möglicherweise keine bösartige Logik ausführt. Wenn der Hook jedoch den PoolManager aufruft, könnten die Interaktionen zwischen dem PoolManager und den zugrunde liegenden Vermögenswerten eines bösartigen Pools potenziell den Kontrollfluss über die take-Funktion im PoolManager an einen Angreifer übergeben.
/// @inheritdoc IPoolManager
function take(Currency currency, address to, uint256 amount) external override
noDelegateCall onlyByLocker {
_accountDelta(currency, amount.toInt128());
reservesOf[currency] -= amount;
currency.transfer(to, amount);
}
Im Wesentlichen rührt die Schwachstelle von unzureichenden Validierungen des registrierten Pools her, mit dem Hook-Benutzer interagieren möchten. Wir werden diese Schwachstelle anhand eines konkreten Beispiels untersuchen und mögliche Abhilfemaßnahmen diskutieren.
Schwachstellenanalyse
Der Take Profits Hook[5] ist ein Hook, der von Awesome Uniswap v4 Hooks aufgeführt wird:
In diesem Beispiel erstellen wir einen Hook, der es Benutzern ermöglicht, "Take-Profit"-Positionen zu platzieren. Zum Beispiel in einem ETH/DAI-Pool, wenn derzeit 1 ETH = 1500 DAI ist, könnten Sie eine Take-Profit-Order als "verkaufe mein gesamtes ETH, wenn 1 ETH = 2000 DAI" platzieren, die automatisch ausgeführt wird.
Werfen wir einen Blick auf die Funktion _handleSwap in diesem Hook. Diese Funktion führt einen Swap aus, um Take-Profit-Orders nach Erhalt einer Sperre zu erfüllen.
![Abbildung 3: Die _handleSwap-Funktion des Take Profits Hooks[5]](https://assets.blocksec.com/frontend/blocksec-strapi-online/3_53b046f4c7.webp)
Sie werden vielleicht feststellen, dass diese Funktion nicht durch einen Zugriffskontroll-Modifier geschützt ist. Zeile 250 beschränkt jedoch effektiv den Zugriff, sodass diese Funktion nur nach Erhalt einer Sperre vom PoolManager aufgerufen werden kann. Andernfalls würde der poolManager.swap fehlschlagen, da der Betreiber nicht der letzte Locker wäre. Mit anderen Worten, _handleSwap muss in einer bestimmten Reihenfolge aufgerufen werden, vorausgesetzt, die registrierten Pools werden validiert. Leider implementiert der Hook keine solche Validierung.
Aufgrund dieser fehlerhaften Implementierung ist der Hook anfällig für einen Reentrancy-Angriff. Diese Schwachstelle könnte es Angreifern ermöglichen, beliebige Swaps mit den von Benutzern eingezahlten Geldern zu erzwingen.
Exploit & PoC
Konkret kann der Angriff in folgenden Schritten durchgeführt werden:
- Der Angreifer registriert einen bösartigen Pool mit gefälschten Token und gibt den Take Profits Hook als Hook des Pools an.
- Der Angreifer platziert eine Stop-Profit-Order im bösartigen Pool über den Hook.
- Der Angreifer führt einen Swap im bösartigen Pool durch, der die
fillOrderimafterSwap-Callback auslöst, um die Stop-Profit-Order des Angreifers zu füllen. - Der Hook ruft die
lock-Funktion desPoolManagerauf, um eine Sperre anzufordern, und ruft die_handleSwap-Funktion imlockAcquired-Callback auf. - In der Funktion
_handleSwaplösen die Übertragungen von Token bösartige Logik im gefälschten Token-Vertrag aus, die erneut in die Funktion_handleSwapeindringt. Dies ist möglich, da_handleSwapeine externe Funktion ohne Zugriffsbeschränkungen ist. Da die Sperre bereits erworben wurde, kann der Angreifer den Hook zwingen, beliebige Swaps auf jedem Pool auszuführen, solange der Hook über ausreichende zugrunde liegende Vermögenswerte verfügt. Der Angreifer kann dann die Swaps sandwichen, um auf Kosten anderer Benutzer Gewinne zu erzielen.
Das folgende detaillierte Diagramm veranschaulicht den Ablauf des Angriffs.

Wie bereits erwähnt, ruft der Hook selbst keine bösartige Logik auf. Der einzige Fehler ist, dass der Hook es nicht verhindert, dass nicht vertrauenswürdige Token-Pools im PoolManager-Vertrag registriert werden. Indirekt wird die bösartige Logik im gefälschten Token-Vertrag über Token-Transfer-Operationen aufgerufen, was ebenfalls eine Art nicht vertrauenswürdiger externer Aufruf ist.
Wie man sich absichert
Es gibt drei praktikable Ansätze, um potenzielle Angriffe aufgrund unzureichender Eingabevalidierung zu mindern:
-
Ordnungsgemäße Zugriffskontrolle. Durch die Nutzung von Bausteinen aus der
BaseHookkann ein Hook die Funktionszugänglichkeit strikt verwalten. Dies verhindert, dass beliebige Konten sensible Funktionen aufrufen. -
Reentrancy Lock. Im oben genannten Angriffsszenario kann dieser Ansatz zweifellos verhindern, dass die bösartige Token-Logik erneut in die sensiblen Funktionen eindringt. In einigen Fällen erfordert das Hook-Design jedoch, dass der Hook selbst re-enterable ist. Insbesondere wenn ein Hook Pool-Aktionen ausführen muss, sollte er dem
PoolManagererlauben, seine Callbacks erneut aufzurufen, um diese Aktionen abzuschließen. Eine Reentrancy-Sperre kann diese beabsichtigte Funktionalität unterbrechen. -
Whitelisting-Ansatz. Dies würde einen privilegierten Administrator erfordern, um genehmigte Pools in den Hooks zu whitelisten. Der Administrator stellt sicher, dass whitelisted Pools keine potenziellen Risiken einführen. Die Einschränkung besteht jedoch darin, dass Hook-Benutzer Operationen nur auf einer begrenzten Anzahl von vom Administrator genehmigten Pools über den Hook ausführen könnten. Während der Whitelisting-Ansatz die Sicherheit verbessert, schränkt er die Funktionalität des Hooks stark ein.
Es ist schwierig, eine perfekte Lösung zu finden, die Sicherheit und Benutzerfreundlichkeit für Hooks in Einklang bringt. Obwohl wir mehrere Abhilfemaßnahmen diskutieren, müssen Entwickler die Kompromisse bei ihrem Hook-Design sorgfältig abwägen. Das Ziel sollte sein, potenzielle Risiken so weit wie möglich zu mindern und gleichzeitig die beabsichtigte Funktionalität beizubehalten. Darüber hinaus behandelt unsere Diskussion nur Schwachstellen, die in den Interaktionen speziell im Zusammenhang mit den Funktionen von Uniswap v4 liegen könnten. Praktische Anwendungen werden zweifellos umfassender sein. Stellen Sie immer sicher, dass Sie jede Zeile Ihrer Verträge verstehen, und bleiben Sie SAFU!
Fazit
In diesem Artikel untersuchen wir die Schwachstellen, die während der Hook-Interaktionslogik auftreten, wobei wir uns speziell auf zwei Szenarien konzentrieren: fehlerhafte Zugriffskontrolle und unzureichende Eingabevalidierung. Wir präsentieren eine detaillierte Schwachstellenanalyse, veranschaulichen potenzielle Ausnutzungen zusammen mit ihren PoCs und diskutieren mögliche Abhilfemaßnahmen. Wir glauben, dass diese Erkenntnisse zur sicheren Entwicklung und Nutzung von Hooks beitragen und zukünftige Anstrengungen zur Schwachstellenerkennung leiten können.
Referenz
[2] Stop Loss Order
[3] DiamondHookPoC
[4] v4-periphery
[5] Take Profits



