過去1週間(2026年4月19日~2026年4月26日)、BlockSecは8件の攻撃インシデントを検出し、分析しました。推定損失総額は約704万ドルです。下の表はこれらのインシデントをまとめたもので、各ケースの詳細な分析は以下のサブセクションで提供されています。
| 日付 | インシデント | タイプ | 推定損失 |
|---|---|---|---|
| 2026/04/19 | カスタムリバランサーコントラクト | 任意のコール | ~$64K |
| 2026/04/20 | REVLoans (Juicebox) | 不適切な検証 | ~$50.7K |
| 2026/04/22 | Volo Vault / Navi | キーの侵害 | ~$3.5M |
| 2026/04/22 | Kipseli Router | 不適切な検証 | ~$72.35K |
| 2026/04/23 | GiddyDefi | 不完全な署名検証 | ~$1.3M |
| 2026/04/25 | Purrlend | キーの侵害 | ~$1.5M |
| 2026/04/26 | SingularityFinance | オラクルの設定ミス | ~$413K |
| 2026/04/26 | Scallop | 会計上の欠陥 | ~$142.7K |
今週のハイライト:GiddyDefi
攻撃者は署名を破ることなく、フラッシュローンを使用することなく、価格を操作することもなく、署名されていないフィールドを自身のコントラクトのものと入れ替えて、正当な署名をリプレイしました。「自身の署名を自身に対して使用する」ことは、部分的なEIP-712カバレッジが有効な署名を汎用的な許可証に変える方法を最もクリーンに実証しています。
2026年4月23日、Ethereum上のGiddyVaultV3が約130万ドルの被害に遭いました。署名スキームはSwapInfo.dataのみをハッシュしており、aggregator、fromToken、toToken、amountはEIP-712ハッシュから除外されていたため、有効な署名をこれらのフィールドを改ざんしてリプレイすることが可能でした。攻撃者は、aggregatorを悪意のあるコントラクトに、fromTokenを戦略のLPトークンにポインティングさせ、約130万ドルを不正に引き出しました。
背景
GiddyVaultV3(0x5f0a...4318)は、ユーザーがdeposit()およびwithdraw()を通じて資金を預け入れたり引き出したりするイールドファーミングボルトコントラクトです。すべての操作は、バックエンドによって署名されたVaultAuth承認構造体を持つ必要があり、これにはEIP-712署名と、トークンスワップルートを記述するSwapInfo[ ]配列が含まれます。スワップを実行する際、コントラクトはGiddyLibraryV3.executeSwap()を呼び出し、これはswap.fromTokenに対してforceApproveを実行してswap.aggregatorへの承認を与え、その後aggregator.call(swap.data)を介してスワップを実行します。戦略コントラクトはその後、設定された戦略に従って資金を管理します。
EIP-712は、構造化されたオフチェーンデータを署名するための標準です。署名を消費するプロトコルは、オンチェーンで同じ構造体を再構築し、合意されたドメインセパレーターの下でハッシュし、署名者のアドレスを復元します。したがって、EIP-712フローのセキュリティは、オンチェーンハッシュが実行に影響を与えるすべてのフィールドをカバーしているかどうかに依存します。Giddyのデザインでは、バックエンドはユーザーの意図と必要なスワップのルーティング指示の両方を含むVaultAuthに署名し、_validateAuthorization()はその構造体を再構築して、戦略が資金を動かすことが許可される前に署名を検証します。
脆弱性分析
脆弱性はGiddyVaultV3の_validateAuthorization()関数にあります。署名対象のペイロードを構築する際、各SwapInfoのdataフィールドのみがハッシュされ、aggregator、fromToken、toToken、amountはすべて署名から除外されています。これは、有効な署名を持っている人なら誰でも、署名検証を通過させながら、SwapInfoの残りのフィールドを自由に置き換えることができることを意味します。
除外された各フィールドは、署名が自由にできる個別のレバーです。aggregatorはforceApproveとaggregator.call(swap.data)の両方を通じて、支出者および呼び出しターゲットになります。fromTokenは、どの戦略資産が承認されるかを選択します。amountは承認の上限を設定します。toTokenはreturnAmount > 0チェックにのみ使用されます。署名されたdataはこれらのいずれにも制約を与えません。なぜなら、これらのターゲットはどれもdata内で参照されていないからです。

攻撃分析
以下の分析は、トランザクション0x5edb66...5482e5に基づいています。
-
ステップ1:攻撃者はオンチェーンから正当なバックエンド承認済み
VaultAuth署名を取得し、dataフィールドをそのまま保持しました。以前のすべてのdeposit()またはwithdraw()呼び出しは、完全なVaultAuthペイロードをオンチェーンにブロードキャストしていたため、過去のトランザクションは再利用可能な署名の無料ソースでした。攻撃者は、意図したスワップ呼び出しに適したdataフィールドを持つ署名が1つあれば十分でした。 -
ステップ2:取得した署名を使用して、攻撃者は元の
signature、nonce、およびdataを変更せずに、残りのフィールドを改ざんしました。fromTokenは戦略コントラクトが保有するLPトークン(実際の資産)に設定されたため、forceApproveはプロトコルが実際に保有するトークンに対して承認を与えました。aggregatorは攻撃者の悪意のあるコントラクトに置き換えられたため、承認とそれに続くaggregator.call()は両方とも攻撃者が所有するコードに向けられました。これらのフィールドは署名検証スコープの外にあったため、_validateAuthorization()は改ざんされた構造体を変更なしで受け入れました。最終的なrequire(returnAmount > 0, "SWAP_NO_TOKENS_RECEIVED")チェックを回避するために、悪意のあるアグリゲーターは偽のトークンをプロトコルにミントする関数を実装し、実際のスワップを実行せずにチェックを満足させました。


- ステップ3:ステップ2で悪意のあるアグリゲーターに承認が与えられていたため、攻撃者は
transferFromを呼び出してボルトのLPトークンを直接悪意のあるアグリゲーターに移動させ、窃盗を完了しました。このステップはプロトコルの保護された実行パスから完全に外れていました。executeSwap()が戻る頃には、承認はすでに書き込まれており、呼び出し後の残高チェックはすでに合格していたため、プロトコルが介入する機会はもうありませんでした。

結論
この攻撃の根本原因は、EIP-712署名のカバレッジの不備でした。SwapInfoの、資金の流れを直接制御するコアフィールドが保護されずに残されていたため、攻撃者は有効な署名を提示しながら、スワップルートとアグリゲーターアドレスを差し替えることができました。外部アグリゲーターを統合する開発者は、以下を行うべきです。
-
aggregator、fromToken、toToken、amountを含む、実行結果に影響を与えるすべてのフィールドがEIP-712署名によってカバーされていることを確認してください。 -
監査されていない外部コントラクトへの呼び出しを防ぐために、アグリゲーターのホワイトリストを強制してください。
-
toTokenを期待される基軸通貨に制限し、偽のトークンが残高チェックを回避するのを防いでください。
より広範には、承認後呼び出しアーキテクチャにおけるEIP-712は、ユーザーが提示したパラメータと特権コントラクトアクションの間の唯一のゲートキーパーであるバックエンド署名 whenever、そのアクション(呼び出しターゲット、資産、金額、受領者)に流れるすべてのパラメータは署名された構造体の中に置かれなければなりません。dataを呼び出しのIDのプロキシとして扱うのはカテゴリーエラーです。呼び出しのIDはすべてのパラメータのタプルであり、署名の外に残されたパラメータは、定義上、トランザクションを送信する誰によっても制御されます。
Web3に最適なセキュリティ監査人
ローンチ前に設計、コード、ビジネスロジックを検証する
今週のその他のインシデント
カスタムリバランサーコントラクト
2026年4月19日、Avalanche上のsAVAXリバランサーコントラクトが、ユーザーのAave V3クレジット委任から約64,000ドル(約7,000 WAVAX)を引き出すために悪用されました。公開関数が、ユーザーの委任を保持したまま任意のtarget.call(data)を実行したため、攻撃者は被害者をonBehalfOfとしてAaveのborrow()を呼び出すことができました。ホワイトハットボットがエクスプロイトをフロントランし、引き出し前に資金を回収しました。
背景
リバランサーコントラクト(0x7a7b...a8c9)は、Aaveでのユーザーのレバレッジポジションをリバランスするように設計された関数b2a13230()を公開しています。この関数はAave V3クレジット委任を通じてユーザーの代わりに動作します。ユーザーはリバランサーに自身の代わりに借入を行う権限を与え、リバランサーはこれらの借入とユーザー提供の資金を組み合わせてポジションを調整します(例:借入+供給ワークフロー)。
脆弱性分析
根本原因は、b2a13230()に、ターゲットとカルldataの両方が呼び出し元によって制御されるtarget.call(data)ステップが含まれていることです。この呼び出しは、コントラクトがまだユーザーのAave V3クレジット委任の下で動作している間に行われるため、そのステップ中に呼び出されるロジックはユーザーの借入能力を継承します。許可されるターゲットのホワイトリストもなく、カルldataの形状制約もないため、呼び出しはAaveのborrow()をユーザーをonBehalfOfとして呼び出すことなど、任意のコントラクトメソッドを呼び出すことができます。

攻撃分析
以下の分析は、トランザクション0xaaa1b2...35001bに基づいています。
-
ステップ1:攻撃者はsAVAXとUSDCをフラッシュローンし、リバランサーコントラクトを通じてAave V3に借入したUSDCを供給して、借入のための十分な担保を確立しました。一方、借入したsAVAXは、借入後の供給ステップの準備のためにリバランサーコントラクトに直接転送されました。
-
ステップ2:攻撃者は関数
b2a13230()を呼び出しました。この関数はまず通常の借入操作を実行し、その後任意の呼び出しセクションに達しました。この時点で、攻撃者はAave V3のborrow()関数を被害者アドレスをonBehalfOfとして直接呼び出すように呼び出しを構成しました。被害者がリバランサーコントラクトにクレジット委任を付与していたため、借入は成功しました。借入したWAVAXはリバランサーコントラクトに転送されました。

- ステップ3:攻撃者は再度関数
b2a13230()を呼び出し、今回はリバランサーを使用して自身のためにWAVAXを借入しました。その後、コントラクトは以前借入したWAVAX(被害者のポジションから発生したもの)を使用して攻撃者のポジションに供給し、返済することで、攻撃者が利益を抽出できるようになりました。
結論
欠陥は、委任されたクレジットを保持する特権コンテキスト内での任意の外部呼び出しの組み合わせです。どちらかのレイヤーだけなら安全です。制約された外部呼び出しは委任を誤用できません。委任なしの任意の呼び出しはユーザーの資金を動かすことができません。クレジット委任を保持するコントラクトは、任意の外部呼び出しを公開してはなりません。そのような呼び出しが必要な場合は、ターゲットをホワイトリストに固定し、カルldataの形状をチェックする必要があります。
REVLoans (Juicebox)
2026年4月20日、Juicebox上の借入拡張機能であるREVLoansがEthereum上で約50,700ドルの被害に遭いました。borrowFrom()は、プロトコルに登録されていることを確認せずに、呼び出し元が提供した会計ソースを受け入れていました。36桁のコンテキストを偽装したことで、同通貨のショートカットがトリガーされ、残高が1e18倍誤ってスケーリングされました。1つのトランザクションでインフレした会計エントリをシードし、もう1つのトランザクションで正当なプールに対してインフレした株価で借入を行うことで、21.77 ETHが流出しました。
背景
Juiceboxは、Ethereum上のハイブリッド資金調達および融資プロトコルです。各プロジェクトは独自のERC20シェアトークン(ここではREVとして参照)と、1つ以上のターミナルに分割された財務省を持っています。ターミナルは、プロジェクトの資産の一部を物理的に管理し、ユーザーインターフェースとしてのエントリー/エグジットポイントとして機能するコントラクトです。1つのプロジェクトはJBDirectoryに複数のターミナルを登録でき、各(terminal, project, token)のトリプルは、そのターミナル内のそのトークンの記帳に使用される(decimals, currency)を宣言するJBAccountingContextを持ちます。したがって、REVは、単一のターミナルに対する請求ではなく、プロジェクトのすべてのターミナルの超過残高の和に対する請求です。
ユーザーは、ターミナルに資産を預け入れて新たに鋳造されたREVと交換するか、ターミナルでREVを redemption してその財務省の比例配分を受けることができます(設定可能なキャッシュアウト税は、残りの保有者のためにいくらかの価値を残します)。REVLoans(0x2db6...1846)は、その上にレイヤーされた別のコントラクトで、借入機能を追加しています。ユーザーはREVを担保としてバーンし、プロジェクトのターミナルからローンを引き出します。ローンは後で返済され、担保が再鋳造されます。借入額は redemption とまったく同じ数学で価格設定されているため、借入は同額の担保をキャッシュアウトすることと経済的に同等です。
REVの株価は、(totalSurplus + totalBorrowed) / (REV.totalSupply + totalCollateral)です。totalBorrowedを分子に含めることで、借入/返済価格を中立に保ちます。また、インフレしたtotalBorrowedは株価を直接上昇させ、少額の担保が不釣り合いにキャッシュアウトすることを可能にします。
脆弱性分析
根本原因は、sourceパラメータの検証されていない入力です。borrowFrom()は、呼び出し元が提供するREVLoanSource source(.terminalと.tokenというフィールドを持つ構造体)を受け入れますが、このペアが指定されたrevnetIdに登録されているかを確認しません。両方のフィールドはキャッシュアウト数学に直接流れ込むため、source.terminalから返される会計コンテキストは呼び出し元によって完全に制御されます。そのコンテキストのcurrencyフィールドが宛先ターミナルのものと一致すると、プロトコルは同通貨のショートカットを取り、価格オラクルをスキップし、供給された小数点と残高の数値を権威あるものとして扱います。

検証されていないsourceは、その後_loanSourcesOf[revnetId]とtotalBorrowedFrom[revnetId][source.terminal][source.token]に_addTo()によって書き込まれます。これも登録チェックを実行しません。

(source, revnetId)が記帳されたら、_borrowableAmountFrom()が借入リクエストを支払可能額に変換する関数です。これはsurplus = totalSurplus + totalBorrowedを_totalBorrowedFrom()から構築し、その超過分をJBCashOuts.cashOutFrom()に、呼び出し元の担保数とシェア供給とともに渡します。

小数点バグはさらに一段下、_totalBorrowedFrom()にあります。これは_loanSourcesOfを反復処理し、各エントリをmulDiv(tokensLoaned, 10**decimals, pricePerUnit)で折りたたみます。同通貨パスでは、pricePerUnit = 10**decimals(宛先の18桁精度)であるため、式はtokensLoanedをそのまま減らし、36桁会計で格納された残高が18桁のETH合計に1e18倍大きく着地します。

増幅はcashOutFrom()で発生します。base = mulDiv(surplus, cashOutCount, totalSupply):surplusはインフレしたtotalBorrowedで支配されており、わずかなcashOutCount(担保)でも不釣り合いに大きな支払いにつながります。

攻撃分析
攻撃は2つのトランザクションを使用します。最初のトランザクションはREVLoansの記帳を汚染します:0xc46cb7...dead1f。2番目のトランザクションは正当なターミナルに対してプールを排出します:0x9adbd6...a8f938。
- ステップ1:攻撃者は、
terminalとtokenの両方が偽のコントラクトを指すようにborrowFrom()を呼び出し、少量のREVを担保として投稿しました。REVLoansは、供給されたターミナルがrevnetに登録されているか、またはトークンが認識されているかを確認しませんでした。

- ステップ2:REVLoansは偽のターミナルに会計コンテキストを照会し、偽の
(decimals=36, currency=ETH-code(61166))を返しました。ソースと宛先の通貨が一致したため、REVLoansは同通貨のショートカットを取り、価格オラクルをスキップし、攻撃者の36桁ターゲット単位で再表現された正当なターミナルの実際のETH超過残高に対してキャッシュアウト数学を実行しました。これにより、数値が1e18倍にインフレしました。

- ステップ3:REVLoansは
(fake terminal, fake token)を_loanSourcesOfに登録し、インフレした数値をtotalBorrowedFromに書き込みました。偽のターミナルは、単に受領を確認するだけで「支払い」を行いました。実際のETHは移動しませんでした。最初のトランザクションはtotalBorrowedが操作された状態で終了し、少額のREV担保のみがバーンされました。

- ステップ4:攻撃者は再度
borrowFrom()を呼び出し、今回は正当なETHターミナルをローンソースとして、そしてわずかなREVを担保として渡しました。キャッシュアウト数学は実際の18桁ETH単位で実行されました。

- ステップ5:
totalBorrowedを計算している間、REVLoansは_loanSourcesOfを反復処理し、ステップ3のエントリにヒットしました。そのエントリのcurrencyはまだETHと一致していたため、同通貨のショートカットが再度発生し、36桁の格納された残高が18桁のETH合計に1e18倍大きく折りたたまれました。totalBorrowedは偽の負債で支配され、株価の分子は大幅にインフレしました。

- ステップ6:キャッシュアウト数学は、攻撃者が正当なターミナルの実際の超過残高をわずかに下回るように事前に調整していた、インフレした分子のサイズに見合った借入額を返しました。正当なターミナルはそれを支払い、ほぼすべてのプールを攻撃者に流出させました。

結論
根本原因は2つの複合的なギャップです。(terminal, token)ペアはrevnet登録を確認せずに受け入れられ、同通貨ショートカットは宛先合計にソース残高を小数点差を正規化せずに折りたたみます。どちらかのギャップだけでも危険度は低くなります。両方 together で、呼び出し元が任意のtotalBorrowedFromエントリを挿入し、それを額面価格でキャッシュアウトできるようになります。緩和策:(terminal, token)をrevnetの登録済みターミナルに対して検証し、ソースの格納された小数点スケールで残高を正規化してから折りたたんでください。
Volo Vault
2026年4月22日、Naviレンディングプロトコルに融資利回りをルーティングするSui上のイールドボルトであるVoloが、オペレーターの秘密鍵が漏洩した後、約350万ドルを失いました。ボルトコントラクトにはコードレベルのバグはなく、攻撃者は単に盗まれた認証情報で正規のオペレーターパスを実行し、VoloのNavi預金を流出させました。
背景
Voloはユーザーインターフェースのボルト(0xcd86...27fefa)であり、Naviは基盤となるレンディングプロトコルです。ボルトはNavi AccountCap(VoloのNaviアカウントからの引き出しを承認するSuiのケーパビリティオブジェクト)を保持し、戦略の移動をオペレーターロールに委任します。Naviに預け入れまたは引き出しを行うには、オペレーターはstart_op_with_bag_v2()を呼び出してAccountCapをボルトから一時的なバッグにリフトし、その後deposit_with_account_cap() / withdraw_with_account_cap_v2()はそのキャップを使用して資金を移動します。
脆弱性分析
根本原因は、コントラクトレベルの脆弱性というよりも、運用/鍵管理の失敗です。Volo戦略パスは、オペレーター秘密鍵を保持する者に引き出し権限を委任します。start_op_with_bag_v2()は2つのチェック(assert_operator_not_freezed(operation, cap)とassert_single_vault_operator_paired(operation, vault.vault_id(), cap))のみを実行しますが、これらは両方とも供給されたケーパビリティが登録されたオペレーターであることを確認するだけです。次に、withdraw_with_account_cap_v2()は、リフトされたAccountCapを提示できる任意の呼び出し元を受け入れます。したがって、オペレーター秘密鍵を保持する人なら誰でも、正規の操作と同じパスを実行できます。

攻撃分析
以下の分析は、トランザクションAQw9wM...3RUSに基づいています。
- ステップ1:攻撃者は、漏洩したオペレーターキーを使用して
@volosui/volo-vault::operationのstart_op_with_bag_v2を呼び出し、NaviAccountCapを一時的なバッグにリフトしました。

-
ステップ2:攻撃者は
bag::removeを使用して、一時的なバッグからAccountCapを抽出しました。 -
ステップ3:攻撃者は、抽出された
AccountCapを使用して@navi-protocol/lending::incentive_v3のwithdraw_with_account_cap_v2を呼び出し、Voloの預金をNaviから引き出しました。

- ステップ4:攻撃者は
bag::addを使用してAccountCapを戻し、操作を閉じ、資金を転送しました。
結論
欠陥は構造的です。1つのオペレーターキー、完全な引き出し権限、2番目のチェックなし。3つの変更により、キー侵害による損害が軽減されます。オペレーターロールをマルチシグまたはしきい値スキームに分割すると、漏洩したキーは単独で引き出しを承認できなくなります。送信引き出しにタイムロックを追加すると、異常な呼び出しに決済前に異議を申し立てられるウィンドウが与えられます。オペレーターの権限を預け入れと再バランスのみにスコープし、ユーザーインターフェースの引き出しを別のパスでルーティングすることで、オペレーターロールがユーザー資金にまったく到達できないようにします。
Kipseli Router
2026年4月22日、Base上のKipseli Routerが約72,350ドルの被害に遭いました。ルーターは、外部のUSDC専用クォーターから返された引用額を、引用トークンが出力トークンと一致することを確認せずに、生の出力トークン転送額として使用していました。攻撃者は、クォーターが実際にはサポートしていないパスで0.04 WETHをcbBTCにスワップし、クォーターのUSDCスケールリターン値(92,610,395)を生のcbBTC単位(≈0.926 cbBTC)として受け取りました。
背景
Kipseli Router(0x579f...9a07)は、外部の引用システムによってバックアップされたスワップ実行コントラクトです。コントラクトはオープンソースではありません。以下は、その逆コンパイルされたバイトコードに基づいた分析であり、関数名が4バイトセレクター(0xcce096f3()、0x592()、0xd88())として表示される理由です。オンチェーンAMMプールから直接スワップ価格を計算するのではなく、クォーターにアウトプット量(amountOut)を問い合わせ、その値に基づいてトークン転送を実行します。通常、ユーザーはtokenInをプロトコルウォレットに送信し、ルーターはそのウォレットからtokenOutを引き出して受信者に転送します。プロトコルは単一のQUOTE_TOKENで構成されており、引用ロジックは6桁の記帳を使用してUSDCで計上されます。システムはUSDCで評価された引用のみをサポートするように設計されています。
脆弱性分析
欠陥は2つのレイヤーにまたがって累積しています。ルーター側では、関数0xcce096f3()はクォーター関数0x592()を介して引用v0を取得し、それを変更せずに0xd88()のtokenOut.transferFrom(_wallet, receiver, v0)に渡します。ルーターはtokenOutがプロトコルのQUOTE_TOKENと等しいかを確認しないため、USDCスケールの値(6桁精度)がcbBTC数量(8桁精度)であるかのように転送されます。クォーター側では、基盤となるPropAMM AMMはトークン対USDCペア専用に設計されていますが、サポートされていないルーティングパス(WETH→cbBTC)を受け入れ、スワップが有効であるかのようにUSDCスケールの値を返します。

攻撃分析
以下の分析は、トランザクション0x96edee...3db3bbに基づいています。
- ステップ1:攻撃者は
tokenIn=WETHとtokenOut=cbBTCでルーターを呼び出しました。基盤となるAMMはこのパスをサポートしていませんでしたが、リバートせず、クォーター0x592()は92,610,395(≈92.61 USDC)のUSDCスケール値を返しました。

- ステップ2:ルーターはその値を直接
cbBTC転送額として使用しました。0.04 WETH(≈$95)がtransferFromを通じて流入しました。92,610,395の生のcbBTC単位(≈0.926 cbBTC、≈$72,350)がプロトコルウォレットから攻撃者に流出しました。

結論
クォーター呼び出しの両側で2つの前提条件がチェックされないため、エクスプロイトが発生します。クォーターは、その出力が独自のUSDC6桁フレームで消費されると想定しています。ルーターは、クォーターが返すものが要求されたtokenOutで計上されていると想定しています。どちらかの修正でバグが解消されます。
-
ルーター側:
tokenOut == QUOTE_TOKENをアサートするか、転送前にUSDCスケールの引用をオラクル経由でtokenOut単位に変換します。 -
クォーター側:サポートされるペアセットに登録されていないトークンを含むルーティングパスで、単に
USDCスケールのフォールバックを返すのではなく、リバートします。
Purrlend
2026年4月25日、HyperLiquidおよびMegaETH上の融資プロトコルであるPurrlendが、秘密鍵の侵害後、約150万ドルを失いました。攻撃者はブリッジロールを引き継ぎ、バックアップされていないpTokens(PurrlendのAaveライクレシートトークン)をミントし、それらのpTokensを担保として使用してプールから実際の資産を借入しました。
背景
Purrlend(0x81d5...a702)は、Aaveライクな会計モデルを持つ融資プロトコルです。ユーザーがプロトコルに資産を供給すると、対応するpTokens(PurrlendのaTokensに似ています)を受け取ります。これは、供給されたポジションを表し、他の資産を借りるための担保として使用できます。
プロトコルには、pool admin、risk admin、bridgeなどの特権ロールも含まれています。ブリッジロールは、クロスチェーン会計を目的としています。これは、反対側のチェーンで発生した預金をミラーリングするpTokensをミントできます。他の管理者ロールは、リスクパラメータを変更し、借入可能な資産を設定します。
脆弱性分析
直接的なトリガーは、特権キーの侵害でした。攻撃者はPurrlendの管理者およびブリッジロールを制御するキーを取得しました。コントラクトレベルの設計上の欠陥は、漏洩を増幅させました。bridgeロールのpTokenミントパスは、検証可能なクロスチェーンエスクローの証拠にアンカーされていません。この関数は、ブリッジロールを持つ呼び出し元が、対応する預金が発生したことを確認せずに、任意の宛先に任意の金額でpTokensを発行できます。プロトコルの他の場所では、pTokensは有効な担保として扱われ、借入パスは借入時にバックアップを再確認しません。したがって、不正なブリッジロールミントは、2番目のゲートなしで、借入能力に直接変換されます。
攻撃分析
以下の分析は、MegaETH上のトランザクション0xb96cff...dbbf24に基づいています。
- ステップ1:攻撃者は、漏洩した特権キーを保持していたため、
GnosisSafeProxyを介したMultiSendCallOnlyバッチを使用して、ACLManagerを通じて自身をpool admin、risk admin、bridge、emergency adminに設定し、その後WETHを借入可能な資産として有効にし、そのBorrowCapを200に設定しました。

-
ステップ2:ブリッジとして、攻撃者は大量の
pTokensを自身のアドレスにミントしました。ブリッジミントパスはクロスチェーンエスクローの検証を行わなかったため、新しいpTokensには裏付けとなる資産がありませんでした。 -
ステップ3:攻撃者はバックアップされていない
pTokensを担保として使用しました。借入パスは任意のpToken残高を有効な供給ポジションとして扱うため、バックアップを再確認せずに、担保チェックが合格し、WETHがプールから借入されました。
結論
これは、コントラクトレベルの設計上の欠陥によって増幅された秘密鍵の侵害でした。漏洩したキーは、攻撃者にブリッジロールの意図された権限のみを与えましたが、その権限には無制限のpTokenミントが含まれており、これは借入可能な担保に直接変換されます。各レイヤーは個別に強化できます。運用レイヤーでは、ブリッジロールをマルチシグまたはしきい値スキームに分割して、単一のキー漏洩がそれを行使できないようにします。コントラクトレイヤーでは、ブリッジミントに検証可能なエスクローの証拠(例:信頼できるクロスチェーン検証者からのメッセージコミットメント)を要求し、証拠が供給されない場合はリバートします。ミント時に証拠を検証することが、鍵管理への依存を完全に排除するため、より耐久性のある修正です。
SingularityFinance
2026年4月26日、Base上のSingularityFinanceのdynBaseUSDCv3ボルトが約413,000ドルの被害に遭いました。ボルトは無効なUniswap V3手数料ティア(42、V3に存在しない)で設定されていたため、非USDC資産のすべての価格オラクルが非存在プールに解決されました。価格設定関数はリバートせずに0をサイレントに返したため、ボルトは非USDC準備金をゼロと評価し、攻撃者は少量のUSDCを預け入れることでほぼすべてのシェア供給をミントし、その後実際の裏付け資産に対して redemption しました。
背景
dynBaseUSDCv3ボルト(0x67b9...4dcd)は複数のイールドベアリングトークンを保有し、Uniswap V3を通じて非USDC準備金を価格設定しています。価格設定ヘルパーgetPrice(base, fee, quote, amount)は、(base, quote, fee)タプルをUniswap V3プールに解決し、そのプールからTWAPを読み取ります。ボルトのtotalAssets()は、価格設定された準備金を集計します。シェアのミントと redemption の比率は、この合計から導出されます。
脆弱性分析
欠陥はgetPrice()の早期リターンブランチにあります。IUniswapV3Factory.getPool(base, quote, fee)がaddress(0)(指定された手数料ティアのプールが存在しない)を返すと、関数はフォールスルーし、リバートせずにゼロ初期化されたprice変数を返します。ボルトはfee=42でデプロイされました。これはUniswap V3のサポートされているティア(500/3000/10000)のいずれでもないため、非USDCトークンのすべてのルックアップがこのブランチにヒットします。したがって、totalAssets()はボルトのUSDC残高にほぼ等しい合計になりますが、実際のイールドベアリングトークンはゼロに貢献します。totalAssets()に依存するミントと redemption の比率は、このほぼゼロの分母に対して計算されます。

攻撃分析
以下の分析は、トランザクション0x00b949...8d3732に基づいています。
-
ステップ1:攻撃者は約100,000 USDCをフラッシュローンしました。
-
ステップ2:攻撃者はUSDCをボルトに預け入れました。
totalAssets()はUSDC残高のみをカウントしていたため、ボルトは預入額とほぼ同額で自身を評価し、攻撃者はシェア供給のほぼ100%を受け取りました。 -
ステップ3:攻撃者はシェアを redemption しました。これは、裏付け資産をシェア所有権に比例して分配します。攻撃者は、ボルトが保有するすべてのイールドベアリングトークンの大部分を受け取りました。
-
ステップ4:攻撃者はフラッシュローンを返済し、流出したイールドベアリングトークンを利益として保持しました。
結論
2つのチェックが不足していました。デプロイメントは、Uniswap V3のサポートされているティア(500/3000/10000)に対してfee=42を検証しませんでした。getPrice()は、リバートする代わりに、プールが存在しない場合に0を返しました。どちらの修正でも十分です。設定時にオラクルパラメータを検証するか、getPool() == address(0)でリバートします。深層防御として、シェアミントロジックは、預金を受け入れる前に外部参照に対してtotalAssets()を健全性チェックすべきです。
Scallop
2026年4月26日、Sui上のScallopのステーキング報酬プログラムが約142,700ドルの被害に遭いました。ユーザーの累積報酬を更新する関数は、提供された報酬追跡オブジェクトがユーザーのアカウントと一致することを確認しなかったため、攻撃者は放棄された、長期間放置されていた報酬追跡オブジェクトから架空のポイント残高を引き出し、それが枯渇するまで正規の報酬プールに対して redemption しました。
背景
ScallopはSui上の融資プロトコルです。融資商品の上に、Scallopはスプールプログラムを実行しています。ユーザーは単一の資産をScallopの市場に預け入れてMarketCoin<T>(融資レシート。SUI預金の場合はMarketCoin<SUI>、「sSUI」のオンチェーン表現)を受け取り、それをSpoolにステーキングして時間とともにプロトコルポイントを獲得し、後でペアリングされたRewardsPoolに対して redemption して実際の報酬トークンと交換します。各Spoolは、グローバルな1株あたりindexを追跡するSui共有オブジェクトです。各ユーザーは、ステーキングされた残高と累積されたpointsを記録する個別のSpoolAccountを保持します。
脆弱性分析
欠陥はspool::user::update_pointsにあります。この関数は、account.spool_id == object::id(spool)(またはaccount.stake_type == spool.stake_type)をアサートしません。兄弟エントリstake、unstake、redeem_rewardsはすべてエントリでそのバインディングチェックを実行しますが、update_pointsのみがそれをスキップします。チェックなしでは、spool_account::accrue_pointsは、渡された任意のSpoolに対してaccount.points += stake * (spool.index - account.index) / 1e9を計算し、そのindexをこのアカウント自身の報酬ストリームであるかのように扱います。

Suiは共有オブジェクトをガベージコレクションしないため、このパスは悪用可能になります。放棄されたScallop Spoolで、stakesがダストに枯渇した場合、報酬シェア(期間あたりの増分1e9 * reward / stakes)を累積し続け、そのindexは時間とともに累積的に増加し、任意の大きな値に達する可能性があります。バインディングチェックがない場合、update_pointsはこのインフレしたindexを使用して、任意の Счете を大きなポイントデルタで汚染できます。汚染されたpointsは、アカウントがそのターゲットスプールに正当にバインドされており、redeem_rewards自体のバインディングチェックが合格するため、ターゲットスプールのRewardsPoolに対して1対1で redemption されます。
攻撃分析
以下の分析は、トランザクション6WNDjC...NfVLに基づいています。
-
ステップ1:攻撃者は0.2 SUIを餌に
MarketCoin<SUI>をミントし、その後ターゲットスプールに対してnew_spool_account+stakeを呼び出し、account.spool_id = target_spoolを持つ正当にバインドされたSpoolAccountを作成しました。 -
ステップ2:攻撃者は
donor_spoolを放棄されたSpoolに設定して、update_points<MarketCoin<SUI>>(donor_spool, account, clock)を呼び出しました。ドナーのindex(≈8.91e14)はアカウントにpointsとして書き込まれました:points = stake * (8.91e14 - 1.19e9) / 1e9 ≈ 1.62e14。 -
ステップ3:攻撃者は
redeem_rewards<MarketCoin<SUI>, SUI>(target_spool, target_rp, account)を呼び出しました。バインディングアサーションはターゲットバインドアカウントを承認し、内部の再蓄積は早期リターンし、汚染されたpointsは報酬プールの1:1レートで、その残高まで変換されました:rewards = 150,098,061,595,978生SUI。 -
ステップ4:攻撃者は
unstakeとredeemを呼び出して0.2-SUIの餌を回収し、その後TransferObjectsで全てを移動させました。
結論
修正は、stake、unstake、redeem_rewardsがすでに実行しているのと同じassert!(account.spool_id == object::id(spool))チェックをupdate_pointsのエントリに追加することです。深層防御として、プロトコルは単一のaccrue_points呼び出しが受け入れるindexデルタをキャップすることもできます(設定された天井よりも大きいデルタを拒否する)。これにより、将来的にバインディングチェックがバイパスされた場合でも、単一の呼び出しで実際のステーキング期間に見合わない量のpointsをアカウントにクレジットすることはできません。
BlockSecについて
BlockSecは、フルスタックのブロックチェーンセキュリティおよびクリプトコンプライアンスプロバイダーです。私たちは、顧客がコード監査(スマートコントラクト、ブロックチェーン、ウォレットを含む)、リアルタイムでの攻撃傍受、インシデント分析、不正資金追跡、およびAML/CFT義務の遵守を、プロトコルおよびプラットフォームのライフサイクル全体で実行できるようにする製品とサービスを構築しています。
BlockSecは、著名なカンファレンスで複数のブロックチェーンセキュリティ論文を発表し、DeFiアプリケーションのゼロデイ攻撃を複数報告し、ハッキングをブロックして2000万ドル以上を救済し、数十億ドルの暗号通貨を保護してきました。
-
公式ウェブサイト:https://blocksec.com/
-
公式Twitterアカウント:https://twitter.com/BlockSecTeam



