Wie wir bereits in unserem vorangegangenen Artikel hervorgehoben haben, weisen über 30 % der Projekte im Repository Awesome Uniswap v4 Hooks[1] Schwachstellen auf. Es ist erwähnenswert, dass sich die hier genannten Schwachstellen spezifisch auf die Interaktionen mit Uniswap v4 beziehen. Dementsprechend werden wir in diesem Artikel die sichere Hook-Interaktionslogik aus den folgenden zwei Perspektiven untersuchen:
- Fehlerhafte Zugriffskontrolle (Access Control)
- Fehlerhafte Eingabevalidierung (Input Validation)
Für jede Kategorie beginnen wir mit einer Analyse der Schwachstelle und demonstrieren deren potenzielle Ausnutzung anhand eines entsprechenden Proof-of-Concept (PoC). Im Anschluss werden mögliche Strategien zur Risikominderung diskutiert.
Fehlerhafte Zugriffskontrolle
Generell können Interaktionen im Zusammenhang mit Uniswap v4-Hooks danach klassifiziert werden, ob der Hook als „Locker“ agiert, also eine Sperre („Lock“) im PoolManager erwirbt, um Operationen in Pools durchzuführen. Zwei primäre Interaktionsszenarien erfordern eine ordnungsgemäße Zugriffskontrolle:
- Hook-PoolManager-Interaktion: Dies betrifft Interaktionen zwischen den offiziellen Callback-Funktionen und dem
PoolManager. Zu den Callback-Funktionen gehören acht Pool-Aktions-Callbacks (d. h.initialize,modifyPosition,swapunddonate) sowie der Lock-Callback (d. h.lockAcquired).
- Hook-intern-Interaktion: Dies betrifft Interaktionen, die innerhalb des Hook-Contracts (der als Locker fungiert) stattfinden.
Hook-PoolManager-Interaktionen sind relativ unkompliziert. Hier agiert der Hook rein als Hook und nimmt die acht Pool-Aktions-Callbacks entgegen. Die Logik im Hook beeinflusst nicht die zugehörigen Pools, was bedeutet, dass keine Geldflüsse zwischen dem Hook und den Pools stattfinden. Die von den Callback-Funktionen bereitgestellten Parameter werden verwendet, um notwendige Speicherbereiche zu ändern oder als wichtige Funktionsparameter dienen. Der entscheidende Punkt ist, ob die Callback-Parameter manipuliert werden können.
Hook-intern-Interaktionen sind etwas komplexer. In der Praxis leisten viele Hook-Prototypen mehr, als nur als reine Hooks zu agieren. Einige Entwickler erlauben es den Hooks, Fonds-Management-Funktionen für ihre Nutzer bereitzustellen. Diese Funktionen sind möglicherweise nicht in den Hook-Contracts implementiert, aber wir können sie in diesem Kontext dennoch kollektiv als Hooks betrachten. In diesen Fällen nimmt ein Hook Nutzergelder entgegen und führt Pool-Operationen wie Liquiditätsmanagement oder Swaps durch. Dies bedeutet, dass der Contract 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. Speziell stellt die BaseHook-Vorlage die Funktion lockAcquired als Lock-Callback bereit, 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 unter Verwendung dieser data durch. Die data hängen von der Geschäftslogik des Hooks ab und können von Nutzern manipuliert werden, was möglicherweise zu Sicherheitsproblemen durch Hook-intern-Interaktionen führen kann, die durch lockAcquired ausgelöst werden. Beachten Sie, dass das Hook-Design so flexibel ist, dass wir nicht alle möglichen Szenarien in dieser Situation abdecken können. Unser Hauptaugenmerk liegt hier auf dem Hook, der eine Sperre erwirbt und seine darauffolgenden internen Interaktionen. Eine Vertiefung in andere potenzielle Geschäftslogiken würde den Rahmen dieser Diskussion sprengen.
In beiden Szenarien liegt die Priorität darauf, jegliche fehlerhaften Zugriffskontrollen zu adressieren, die potenziell zu einer Ausnutzung führen könnten, da diese Funktionen klare Interaktionsinstanzen haben. In den folgenden Unterabschnitten werden wir jedes Szenario nacheinander untersuchen und die notwendigen Zugriffskontrollen erörtern, um eine sicherere Interaktionslogik zu gewährleisten.
Schwachstellenanalyse
Zugriffskontrollen dienen als hocheffiziente und unkomplizierte Sicherheitslösungen für viele Projekte. Wenn eine Funktion so konzipiert ist, dass sie von bestimmten Entitäten aufgerufen wird, sollte sie eine Zugriffskontrolle beinhalten. Das bekannteste Beispiel für eine Zugriffskontrolle ist der Ownable-Contract der OpenZeppelin-Bibliothek, der erfordert, dass privilegierte Funktionen nur vom Eigentümer des Contracts aufgerufen werden dürfen. Es ist klar, dass die beiden von uns oben diskutierten Szenarien geeignete Fälle für diese Art der Kontrolle sind.
Hook-PoolManager-Interaktion: Für sichere Interaktionen mit dem PoolManager sollten Hooks notwendige Zugriffskontrollen auf diese Callback-Funktionen erzwingen. Insbesondere sollten diese Callbacks ausschließlich vom PoolManager aufrufbar sein und nicht von anderen Konten. Das Versäumnis, solche Kontrollen einzurichten, könnte diese sensiblen Schnittstellen für eine potenzielle Ausnutzung durch böswillige Akteure offenlegen.
Über die acht Pool-Aktions-Callbacks hinaus muss auch der Lock-Callback (d. h. lockAcquired), der nach Erhalt der Sperre vom PoolManager benutzerdefinierte Logik ausführt, dieses Problem adressieren.
Hook-intern-Interaktion: Die an den Hook-internen Interaktionen beteiligten Funktionen sind ebenfalls darauf ausgelegt, von bestimmten Aufrufern aufgerufen zu werden. Wie wir bereits erwähnt haben, umfasst dieses Szenario zwei Phasen. Erstens wird die lockAcquired-Funktion des Lockers vom PoolManager aufgerufen, was bedeutet, dass die Funktion von msg.sender erfordern sollte, der PoolManager zu sein. Zweitens verteilt 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 müssen und der Aufrufer auf die Adresse des Hooks beschränkt sein muss.
Nehmen wir als Beispiel eines der Beispiele, die vom Awesome Uniswap v4 Hooks-Repository aufgelistet wurden, d. h. Stop Loss Order[2]:
Direkt in die Uniswap V4-Pools integriert, werden Stop-Loss-Orders on-chain platziert und über den
afterSwap()-Hook ausgeführt. Es sind keine externen Bots oder Akteure erforderlich, um die Ausführung zu garantieren.
Lassen Sie uns die afterSwap-Callback-Funktion untersuchen:
![Abbildung 1: Die afterSwap-Funktion von Stop Loss Order[2]](https://assets.blocksec.com/frontend/blocksec-strapi-online/1_09ba973d9e.webp)
Es ist klar, dass die obige Funktion dazu gedacht ist, sensible Operationen durchzuführen. Aufgrund einer fehlerhaften Zugriffskontrolle könnte sie jedoch von böswilligen Akteuren ausgenutzt werden, die die Argumente (z. B. den key und die params) manipulieren, was zu unerwartetem Verhalten führt. Beispielsweise könnte der afterSwap-Callback unter der Annahme arbeiten, dass der Swap bereits im PoolManager stattgefunden hat. Danach könnte er Aktionen einleiten, um wesentliche Zustandsinformationen aufzuzeichnen, wie z. B. den aktuellen Preis oder die gesammelten Swap-Gebühren. Wenn afterSwap jedoch seine Aufrufe 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
Der Einfachheit halber verwenden wir ein einfaches PoC, um dieses Zugriffskontrollproblem zu veranschaulichen. Im Allgemeinen akzeptiert der beforeInitialize-Callback des Hooks einen Parameter vom Typ PoolKey, der diese Hook-Adresse in seinem hooks-Feld enthalten muss (da der PoolManager dieses Feld verwendet, um die aufzurufende Hook-Adresse zu bestimmen).
Der Screenshot zeigt ein PoC, das die Ausnutzung eines Hooks mit fehlerhafter Zugriffskontrolle demonstriert, wie in DiamondHookPoC [3] zu sehen.
Mangels Zugriffsbeschränkungen auf die beforeInitialize-Callback-Funktion können böswillige Akteure dieser Funktion einen beliebigen poolKey übergeben. Der Hook überprüft nicht, ob der Hook dieses poolKey mit der aktuellen Hook-Adresse übereinstimmt.

Obwohl es wichtig ist anzumerken, dass der Exploit in diesem Szenario möglicherweise keine finanziellen Verluste für den Hook verursacht, verdeutlicht er dennoch drastisch, wie der Status des Hooks durch ungeschützte Callback-Funktionen manipuliert werden kann.
Wie man Risiken mindert
Um die Sicherheit der 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 durch den BaseHook in seinem v4-periphery-Repository[4] Best Practices.
Der BaseHook stellt den poolManagerOnly-Modifikator bereit, um Aufrufe strikt auf den PoolManager zu beschränken:
/// @dev Nur der PoolManager darf diese Funktion aufrufen
modifier poolManagerOnly() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
Dieser Modifikator kann effektiv eingesetzt werden, um eine ordnungsgemäße Zugriffskontrolle für die sensiblen Hook- und Lock-Callbacks zu erzwingen.
Andererseits erfordert das Vorhandensein der Hook-intern-Interaktionen, dass alle bedeutenden zustandsändernden Funktionen, die über den lockAcquired-Callback aufgerufen werden (wie vom BaseHook spezifiziert), nicht beliebig aufrufbar sein sollten.
Um diese Anforderung zu erfüllen, bietet der BaseHook einen selfOnly-Modifikator. Dieser Modifikator beschränkt die Zugänglichkeit der deklarierten Funktion auf den Hook selbst und verbietet externen Contracts, diese sensiblen Funktionen direkt für böswillige Zwecke aufzurufen.
/// @dev Nur diese Adresse darf diese Funktion aufrufen
modifier selfOnly() {
if (msg.sender != address(this)) revert NotSelf();
_;
}
Zusammenfassend lässt sich sagen, dass durch die Vererbung von BaseHook benutzerdefinierte Hooks diese eingebauten Zugriffskontrollmodifikatoren und Callbacks nutzen können, um eine ordnungsgemäße Zugriffskontrolle zu erzwingen.
Fehlerhafte Eingabevalidierung
Der BaseHook in v4-periphery[4] bietet eine Lösung für eine sicherere Interaktionslogik, die Hook-Entwickler nutzen können. Wir beobachten jedoch weiterhin Fälle von falscher Nutzung, die neue Möglichkeiten für Angriffsvektoren in bestehenden Hooks eröffnen.
Standardmäßig erlauben Hooks jedem Pool, sich über die initialize-Funktion im PoolManager zu registrieren. Wenn ein Hook jedoch versäumt, die zugrunde liegenden Vermögenswerte im registrierenden Pool zu validieren, könnten böswillige Nutzer einen Pool mit gefälschten Token registrieren, was es ihnen ermöglicht, über die transfer-Funktion der Token erneut in den Hook einzutreten (Reentrancy).
Diese Schwachstelle ist subtil, da der Hook selbst möglicherweise keine böswillige 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öswilligen Pools den Kontrollfluss über die take-Funktion im PoolManager potenziell 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 stammt die Schwachstelle aus einer fehlerhaften Validierung des registrierten Pools, mit dem Hook-Nutzer interagieren möchten. Wir werden diese Schwachstelle anhand eines konkreten Beispiels untersuchen und Strategien zur Risikominderung diskutieren.
Schwachstellenanalyse
Take Profits Hook[5] ist ein Hook, der von Awesome Uniswap v4 Hooks aufgelistet wird:
In diesem Beispiel erstellen wir einen Hook, der es Nutzern ermöglicht, Take-Profit-Positionen zu platzieren. Wenn beispielsweise in einem ETH/DAI-Pool aktuell 1 ETH = 1500 DAI ist, könnten Sie eine Take-Profit-Order wie „verkaufe mein gesamtes ETH, wenn 1 ETH = 2000 DAI“ platzieren, welche automatisch ausgeführt wird.
Schauen wir uns die _handleSwap-Funktion in diesem Hook an. Diese Funktion führt einen Swap aus, um Take-Profit-Orders nach Erhalt einer Sperre zu füllen.
![Abbildung 3: Die _handleSwap-Funktion von Take Profits Hook[5]](https://assets.blocksec.com/frontend/blocksec-strapi-online/3_53b046f4c7.webp)
Sie bemerken vielleicht, dass diese Funktion nicht durch einen Zugriffskontrollmodifikator geschützt ist. Zeile 250 schränkt den Zugriff jedoch effektiv so ein, dass diese Funktion nur aufgerufen werden kann, nachdem eine Sperre vom PoolManager erworben wurde. Andernfalls würde das poolManager.swap fehlschlagen, da der Akteur nicht der zuletzt aktive Locker wäre. Mit anderen Worten: _handleSwap muss in einer bestimmten Reihenfolge aufgerufen werden, sofern die registrierten Pools validiert sind. Leider implementiert der Hook eine solche Validierung nicht.
Aufgrund dieser fehlerhaften Implementierung ist der Hook anfällig für einen Reentrancy-Angriff. Diese Schwachstelle könnte es Angreifern ermöglichen, willkürliche Swaps unter Verwendung von durch Nutzer hinterlegten Geldern zu erzwingen.
Exploit & PoC
Speziell kann der Angriff in den folgenden Schritten gestartet 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, was den
fillOrder-Aufruf imafterSwap-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
_handleSwap-Funktion lösen die Token-Transfers bösartige Logik im gefälschten Token-Contract aus, welche erneut in die_handleSwap-Funktion eintritt. Dies ist möglich, da_handleSwapeine externe Funktion ohne Zugriffsbeschränkungen ist. Da die Sperre bereits erlangt wurde, kann der Angreifer den Hook zwingen, willkürliche Swaps auf jedem Pool auszuführen, sofern der Hook über ausreichende zugrunde liegende Vermögenswerte verfügt. Der Angreifer kann dann die Swaps „sandwich-en“, um Gewinne auf Kosten anderer Nutzer 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 besteht darin, dass der Hook nicht verhindert, dass nicht vertrauenswürdige Token-Pools im PoolManager-Contract registriert werden. Indirekt wird die bösartige Logik im gefälschten Token-Contract über Token-Transferoperationen aufgerufen, was ebenfalls eine Art nicht vertrauenswürdiger externer Aufruf ist.
Wie man Risiken mindert
Es gibt drei praktikable Ansätze, um potenzielle Angriffe aufgrund fehlerhafter Eingabevalidierung zu mindern:
-
Ordnungsgemäße Zugriffskontrolle. Durch die Nutzung von Bausteinen des
BaseHookkann ein Hook die Zugänglichkeit von Funktionen strikt verwalten. Dies verhindert, dass beliebige Konten sensible Funktionen aufrufen können. -
Reentrancy-Sperre. Im obigen Angriffsszenario kann dieser Ansatz zweifellos verhindern, dass die bösartige Token-Logik wieder in die sensiblen Funktionen eintritt. In einigen Fällen erfordert das Hook-Design jedoch, dass der Hook selbst „neueintretbar“ (re-enterable) sein muss. Wenn ein Hook bestimmte Pool-Aktionen ausführen muss, sollte er dem
PoolManagererlauben, seine Callbacks erneut aufzurufen, um diese Aktionen abzuschließen. Eine Reentrancy-Sperre könnte diese beabsichtigte Funktionalität unterbrechen. -
Whitelisting-Ansatz. Dies würde erfordern, dass ein privilegierter Admin zugelassene Pools in den Hooks auf eine Whitelist setzt. Der Admin stellt sicher, dass Whitelist-Pools keine potenziellen Risiken bergen. Die Einschränkung ist jedoch, dass Hook-Nutzer nur Operationen auf einer begrenzten Anzahl von Admin-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 eine Herausforderung, eine perfekte Lösung zu finden, die Sicherheit und Benutzerfreundlichkeit für Hooks in Einklang bringt. Während wir mehrere Minderungsansätze diskutieren, müssen Entwickler die Kompromisse in ihrem Hook-Design sorgfältig abwägen. Das Ziel sollte es sein, potenzielle Risiken so weit wie möglich zu mindern und gleichzeitig die beabsichtigte Funktionalität beizubehalten. Darüber hinaus deckt unsere Diskussion nur Schwachstellen ab, die in den spezifisch auf Uniswap v4-Funktionen bezogenen Interaktionen liegen können. Praktische Anwendungen werden zweifellos umfassender sein. Stellen Sie immer sicher, dass Sie jede Zeile Ihrer Contracts 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 fehlerhafte Eingabevalidierung. Wir präsentieren eine detaillierte Schwachstellenanalyse, veranschaulichen potenzielle Exploits zusammen mit ihrem PoC und diskutieren mögliche Strategien zur Risikominderung. Wir glauben, dass diese Erkenntnisse zu einer sicheren Entwicklung und Nutzung der Hooks beitragen und zukünftige Bemühungen bei der Schwachstellenerkennung leiten können.
Referenz
[2] Stop Loss Order
[3] DiamondHookPoC
[4] v4-periphery
[5] Take Profits



