この記事では、Uniswap v4 のフックインタラクション中に発生する可能性のある、主に2つの種類の問題に焦点を当てます。
前回の記事で指摘したように、「Awesome Uniswap v4 Hooks」リポジトリ[1]のプロジェクトの30%以上に脆弱性が存在します。ここで言及する脆弱性は、Uniswap v4 のインタラクションに特有のものであることに注意してください。したがって、この記事では、安全なフックインタラクションロジックを以下の2つの観点から詳細に検討します。
- 不適切なアクセス制御
- 不適切な入力検証
各カテゴリについて、脆弱性の分析から始め、対応する概念実証(PoC)を提供してその潜在的な悪用方法を実証します。その後、潜在的な緩和戦略についての議論を行います。
不適切なアクセス制御
一般に、Uniswap v4 のフックに関連するインタラクションは、フックがロッカーとして機能し、PoolManager でロックを取得してプールで操作を実行するかどうかに基づいて分類できます。適切なアクセス制御を必要とする2つの主要なインタラクションシナリオがあります。
- フック - PoolManager インタラクション:これは、公式のコールバック関数と
PoolManagerとの間のインタラクションを伴います。コールバック関数には、8つのプールアクションコールバック(initialize、modifyPosition、swap、donate)とロックコールバック(lockAcquired)が含まれます。
- フック - 内部 インタラクション:これは、フックコントラクト(ロッカーとして機能)内で発生するインタラクションに関係します。
フック - PoolManager インタラクションは比較的単純です。ここでは、フックは純粋にフックとして機能し、8つのプールアクションコールバックを受け入れます。フック内のロジックは関連するプールに影響を与えないため、フックとプール間の資金の流れはありません。コールバック関数によって提供されるパラメータは、必要なストレージを変更するため、または重要な関数パラメータとして使用されます。重要な考慮事項は、コールバックパラメータが操作される可能性があるかどうかです。
フック - 内部 インタラクションはやや複雑です。実際には、多くのフックプロトタイプは純粋なフックとして機能する以上のことを行います。一部の開発者は、フックがユーザーに資金管理機能を提供するようにしています。これらの機能はフックコントラクトに実装されていない場合がありますが、このコンテキストではまとめてフックと見なすことができます。この場合、フックはユーザー資金を受け取り、流動性管理やスワップなどのプール操作を実行します。これは、コントラクトが PoolManager からロックを取得する必要があることを意味し、フックをロッカーに変えます。
Uniswap Foundation はこの状況を考慮し、フックテンプレートに機能を統合しました。具体的には、BaseHook テンプレートはロックコールバックとして lockAcquired 関数を提供します。
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))
}
}
カスタムロジックを実行するために、lockAcquired は data バイトを受け取り、その data を使用して自身への低レベルコールを実行します。data はフックのビジネスロジックに依存し、ユーザーによって操作される可能性があり、フック - 内部 インタラクションが lockAcquired によってトリガーされることによるセキュリティ問題につながる可能性があります。フックの設計は非常に柔軟であるため、この状況で可能なすべてのシナリオを網羅することはできないことに注意してください。ここでは、フックがロックを取得し、その後の内部インタラクションに主に焦点を当てます。他の潜在的なビジネスロジックを掘り下げると、この議論には複雑すぎることになります。
どちらのシナリオでも、これらの関数には明確なインタラクションエンティティがあることを考慮すると、悪用につながる可能性のある不適切なアクセス制御に対処することが優先事項です。後続のサブセクションでは、各シナリオを順に検査し、より安全なインタラクションロジックを確保するために必要なアクセス制御について説明します。
脆弱性分析
アクセス制御は、多くのプロジェクトにとって非常に効率的で直接的なセキュリティソリューションとして機能します。関数が特定のエンティティによって呼び出されるように設計されている場合は、アクセス制御を組み込む必要があります。最もよく知られているアクセス制御の例は、OpenZeppelinライブラリの Ownable コントラクトであり、特権関数はコントラクトの所有者のみが呼び出すことができるように要求します。上記で議論した2つのシナリオは、この種の制御に適したケースであることは明らかです。
フック - PoolManager インタラクション:PoolManager との安全なインタラクションのために、フックはこれらのコールバック関数に必須のアクセス制御を適用する必要があります。具体的には、これらのコールバックは PoolManager のみが呼び出すことができ、他のアカウントは呼び出すことができません。このような制御を確立しないと、これらの機密性の高いインターフェースが、悪意のあるアクターによる潜在的な悪用の対象となる可能性があります。
8つのプールアクションコールバックに加えて、ロック(lockAcquired)コールバック(PoolManager からロックを取得した後にカスタムロジックを実行する)もこの問題に対処する必要があります。
フック - 内部 インタラクション:フック - 内部インタラクションに関与する関数も、特定の呼び出し元によって呼び出されるように設計されています。前述したように、このシナリオには2つのフェーズがあります。まず、ロッカーの lockAcquired 関数は PoolManager によって呼び出されます。これは、関数が msg.sender を PoolManager であることを要求する必要があることを示します。次に、フックは関数呼び出しをそれに応じてディスパッチします。BaseHook の設計に基づき、これはフック自体への低レベルコールによって実装されます。これは、これらの関数が external として定義され、呼び出し元をフックのアドレスに制限する必要があることを示します。
Awesome Uniswap v4 Hooks リポジトリにリストされている例の1つ、つまりStop Loss Orderを例に取ります[2]。
Uniswap V4プールに直接統合されたストップロスオーダーはオンチェーンに投稿され、afterSwap()フックを介して実行されます。実行を保証するために外部ボットやアクターは必要ありません。
その afterSwap コールバック関数を見てみましょう。
![Figure 1: The afterSwap function of Stop Loss Order[2]](https://assets.blocksec.com/frontend/blocksec-strapi-online/1_09ba973d9e.webp)
明らかに、上記の関数は機密性の高い操作を実行するように設計されています。しかし、不適切なアクセス制御のため、悪意のあるアクターが引数(例:key および params)を操作することによって悪用される可能性があり、予期しない動作につながる可能性があります。たとえば、afterSwap コールバックは、PoolManager でスワップがすでに完了したという前提で動作する可能性があります。これに続いて、現在の価格や収集されたスワップ手数料などの重要な状態情報を記録するためのアクションを開始する可能性があります。しかし、afterSwap がその呼び出しを PoolManager から厳密に制限しない場合、悪意のあるアクターは params パラメータを偽造し、歪んだ記録状態につながる可能性があります。
エクスプロイトとPoC
簡単にするために、このアクセス制御の問題を説明するために基本的なPoCを使用します。一般に、フックの beforeInitialize は PoolKey 型のパラメータを受け取ります。このパラメータは、PoolManager が呼び出すフックアドレスを決定するために使用するため、hooks フィールドにこのフックアドレスを含める必要があります。
スクリーンショットは、DiamondHookPoC [3]で見られるように、不適切なアクセス制御を持つフックの悪用を示すPoCを提供しています。
beforeInitialize コールバック関数にアクセス制限がない場合、悪意のあるアクターはこの関数に任意の poolKey を供給できます。フックは、この poolKey のフックが現在のフックアドレスと一致するかどうかを検証しません。

このシナリオでの悪用は金銭的損失を引き起こさない可能性があることに注意することが重要ですが、それでも保護されていないコールバック関数を通じてフックの状態が操作される可能性を劇的に示しています。
緩和方法
フック - PoolManager インタラクションのセキュリティを確保するために、フックコールバックとロックコールバックの両方で、アクセス可能性を PoolManager のみに制限する必要があります。
幸いなことに、Uniswap v4 はそのv4-peripheryリポジトリ[4]の BaseHook を通じてベストプラクティスを提供しています。
BaseHook は poolManagerOnly モディファイアを提供し、PoolManager からの呼び出しを厳密に制限します。
/// @dev Only the pool manager may call this function
modifier poolManagerOnly() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
このモディファイアは、機密性の高いフックおよびロックコールバックに適切なアクセス制御を適用するために効果的に使用できます。
一方、フック - 内部 インタラクションの存在により、BaseHook で指定された lockAcquired コールバックを介して呼び出される任意の重要な状態変更関数は、任意に呼び出し可能であってはなりません。
この要件を満たすために、BaseHook は selfOnly モディファイアを提供します。このモディファイアは、宣言された関数のアクセス可能性をフック自体に制限し、外部コントラクトが悪意のある目的でこれらの機密関数を直接呼び出すことを禁止します。
/// @dev Only this address may call this function
modifier selfOnly() {
if (msg.sender != address(this)) revert NotSelf();
_;
}
要約すると、BaseHook を継承することにより、カスタムフックはこれらの組み込みアクセス制御モディファイアとコールバックを活用して、適切なアクセス制御を適用できます。
不適切な入力検証
v4-periphery[4] の BaseHook は、より安全なインタラクションロジックのためのソリューションを提供しており、フックの開発者はこれを活用できます。しかし、既存のフックで攻撃ベクトルを開く不適切な使用例が引き続き見られます。
デフォルトでは、フックは PoolManager の initialize 関数を介して任意のプールを登録できます。しかし、フックが登録されているプール内の基盤となる資産を検証しない場合、悪意のあるユーザーは偽造トークンを含むプールを登録できるため、トークンの transfer 関数を介してフックに再参入できます。
この脆弱性は、フック自体が悪意のあるロジックを実行しないため、微妙です。しかし、フックが PoolManager を呼び出すと、悪意のあるプールの基盤となる資産と PoolManager との間のインタラクションが、PoolManager の take 関数を介して攻撃者に制御フローを渡す可能性があります。
/// @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);
}
本質的に、脆弱性は、フックユーザーがインタラクションを計画している登録済みプールに対する不適切な検証に起因します。具体的な例を使用してこの脆弱性を掘り下げ、潜在的な緩和戦略について説明します。
脆弱性分析
Take Profits Hook[5] は、Awesome Uniswap v4 Hooks によってリストされているフックです。
この例では、「テイクプロフィット」ポジションを配置できるフックを作成します。たとえば、ETH/DAIプールで、現在1 ETH = 1500 DAIの場合、「1 ETH = 2000 DAIになったら私のETHをすべて売る」というテイクプロフィットオーダーを配置できます。これは自動的に実行されます。
このフックの _handleSwap 関数を見てみましょう。この関数は、ロックを取得した後にテイクプロフィットオーダーを埋めるためにスワップを実行します。
![Figure 3: The _handleSwap function of Take Profits Hook[5]](https://assets.blocksec.com/frontend/blocksec-strapi-online/3_53b046f4c7.webp)
この関数はアクセス制御モディファイアで保護されていないことに気付くかもしれません。しかし、250行目は、ロックが PoolManager から取得された後にのみこの関数を呼び出すことができるように、効果的にアクセスを制限しています。それ以外の場合、オペレーターが最新のロッカーではないため、poolManager.swap は失敗します。つまり、_handleSwap は、登録されたプールが検証されていることを前提として、特定の順序で呼び出す必要があります。残念ながら、フックはこのような検証を実装していません。
この不適切な実装のため、フックは再入金攻撃の影響を受けやすくなっています。この脆弱性により、攻撃者はユーザーが預けた資金を使用して任意のスワップを強制できます。
エクスプロイトとPoC
具体的には、攻撃は次の手順で開始できます。
- 攻撃者は、偽のトークンを持つ悪意のあるプールを登録し、そのプールのフックとして Take Profits Hook を指定します。
- 攻撃者は、フックを介して悪意のあるプールに ストッププロフィット オーダーを配置します。
- 攻撃者は悪意のあるプールでスワップを実行し、
afterSwapコールバックのfillOrderをトリガーして攻撃者のストッププロフィットオーダーを埋めます。 - フックは
PoolManagerのlock関数を呼び出してロックを要求し、lockAcquiredコールバックで_handleSwap関数を呼び出します。 _handleSwap関数で、トークンの転送は偽のトークンコントラクトの悪意のあるロジックをトリガーし、_handleSwap関数に再入金します。これは、_handleSwapがアクセス制限のない外部関数であるため可能です。ロックはすでに取得されているため、攻撃者は、フックが十分な基盤となる資産を保持している限り、任意のプールで任意のスワップを実行するようにフックを強制できます。攻撃者はその後、他のユーザーを犠牲にして利益を上げるためにスワップをサンドイッチすることができます。
以下の詳細な図は、攻撃フローを示しています。

前述したように、フック自体は悪意のあるロジックを呼び出しません。唯一の間違いは、フックが信頼されていないトークンプールが PoolManager コントラクトに登録されるのを防がないことです。間接的に、偽のトークンコントラクトの悪意のあるロジックは、トークン転送操作を介して呼び出されます。これは、信頼されていない外部呼び出しの一種でもあります。
緩和方法
入力検証の不備による潜在的な攻撃を緩和するための3つの実行可能なアプローチがあります。
-
適切なアクセス制御。
BaseHookのビルディングブロックを活用することにより、フックは関数アクセス可能性を厳密に管理できます。これにより、任意の(arbitrary)アカウントが機密関数を呼び出すことが防止されます。 -
再入金ロック。上記の攻撃シナリオでは、このアプローチは悪意のあるトークンロジックが機密関数に再入金するのを確実に防ぐことができます。ただし、場合によっては、フックの設計により、フック自体が再入可能である必要があります。 具体的には、フックがプールアクションを実行する必要がある場合、
PoolManagerがコールバックに再入金してこれらのアクションを完了できるようにする必要があります。再入金ロックは、この意図された機能を壊す可能性があります。 -
ホワイトリストアプローチ。これには、特権管理者が承認されたプールをフックにホワイトリストに登録する必要があります。管理者は、ホワイトリストに登録されたプールが潜在的なリスクをもたらさないことを保証します。ただし、制限は、フックユーザーが管理者によって承認された限られた数のプールのみを介して操作を実行できることです。 ホワイトリストアプローチはセキュリティを向上させますが、フックの機能を大幅に制限します。
セキュリティと使いやすさのバランスをとる完璧なソリューションを見つけるのは困難です。いくつかの緩和アプローチを議論しますが、開発者はフックの設計でトレードオフを慎重に検討する必要があります。目標は、意図された機能を維持しながら、潜在的なリスクを可能な限り緩和することです。さらに、私たちの議論は、Uniswap v4 の機能に特に関連するインタラクションにある可能性のある脆弱性のみをカバーしています。実際の実用的なアプリケーションは、間違いなくより包括的になるでしょう。常に、コントラクトのすべての行を理解していることを確認し、SAFE でいてください!
結論
この記事では、フックインタラクションロジック中に発生する脆弱性について探求し、特に不適切なアクセス制御と不適切な入力検証の2つのシナリオに焦点を当てました。詳細な脆弱性分析を提示し、概念実証(PoC)とともに潜在的な悪用を実証し、潜在的な緩和戦略について議論します。これらの洞察は、フックの安全な開発と使用に貢献し、将来の脆弱性検出の取り組みを導くことができると信じています。
参照
[2] Stop Loss Order
[3] DiamondHookPoC
[4] v4-periphery
[5] Take Profits



