わずかな切り捨て、巨額のファンド損失:最近のBalancerインシデントの詳細分析

2023年8月27日に発生したBalancer攻撃の包括的な分析

わずかな切り捨て、巨額のファンド損失:最近のBalancerインシデントの詳細分析

2023年9月15日更新: Balancerは、このインシデントの全体像、経験、教訓を含む詳細な説明を提供している公式の事後分析を公開しました。この事後分析は、その複雑で優れた説明により、説得力があり、時間をかける価値は間違いなくあります。

セキュリティの観点から、この事後分析は2つのバグが存在することを示しています。1つ目は、当社のレポートで議論した切り捨てエラーであり、2つ目は、当社のレポートで説明されているように、攻撃ステップ3.6および3.7で発生した「0供給でのレートリセット」です。Balancerのレポートでは、2番目を最も重大な問題と見なし、1番目は寄与的なものとしています。しかし、私たちは、利益を得るためには両方のバグが等しく重要であると考えています。

  1. 最初のバグは、トークンレートを急騰させるために使用され、利益の根本原因となります。これなしでは、利益を生み出すことは不可能でしょう。

  2. 2番目のバグは、bb-a-トークンの負債をバランスさせることにより、エクスプロイトを可能にします。これがないと、攻撃者はbb-a-トークンの流動性が低いため、攻撃は失敗します。なぜなら、これらのトークンを入手する他のソースはないからです(攻撃者が何らかの方法でそれらを入手することに成功しない限り)。

2023年8月22日、Balancerは複数のブーストプールに影響を与える重大な脆弱性の存在を公に発表し、ユーザーに影響を受けたプールからのLPの即時引き出しを促しました。Balancerは、TVLの大部分を保護するために緊急緩和措置を開始しましたが、一部の資金はリスクにさらされたままでした。残念ながら、8月27日、5日後に、私たちは複数の攻撃が実際に行われていることに気づきました。それ以来、212万ドルを超える資産が盗まれています。

本レポート執筆時点(発表から3週間以上経過し、安全であると判断できる時点)で、Balancerはこの脆弱性に関する詳細な分析を公開していません。本レポートでは、主に攻撃トランザクションの1つに基づいて、包括的な分析を提供することを目的とします。

主要なポイント (TL;DR)

  • 私たちの調査では、根本原因は**linearプールにおける切り捨てロジックに起因する価格操作**にあり、それが結果として対応するboostedプールで使用されるキャッシュされたトークンレートに不適切に影響を与えていることが示唆されています。
  • このインシデントは、脆弱なソースからフォークしたプロジェクトへの迅速な通知の極めて重要な必要性を強調しており、これはコミュニティ全体にとって大きな課題となっています。
  • 多数の進行中の攻撃は、プロアクティブな脅威防止の必要性を浮き彫りにしており、これは将来的な損失の軽減に不可欠な助けとなる可能性があります。

次のセクションでは、まずBalancerに関するいくつかの基本的な背景情報を提供します。その後、脆弱性と関連する攻撃について包括的な分析を行います。最後に、これまで観察された攻撃の簡単な要約と、それに対応する利益を提供します。

0x1 Balancerの背景

Balancer V2 [1]は、プログラム可能な流動性のための柔軟なビルディングブロックを表す、分散型自動マーケットメーカー(AMM)プロトコルです。トークン会計がプールロジックとペアになっている他のAMMとは異なり、Balancerはトークン会計と管理をプールロジックから分離しており、多くのトークン転送を削減することでスワップ効率を向上させることができます。

Balancerはさまざまな種類のプールをサポートしています。各プールはBPT(Balancer Pool Token)というLPトークンに関連付けられています。基本的に、BPTの価値は、すべての基盤トークンの総価値に基づいて計算されます。

Balancerは、Vaultに登録されているすべてのプールから最良の価格を利用するマルチホップスワップ、またはバッチスワップをサポートしています。具体的には、Vaultはマルチホップスワップを促進するためにbatchSwap関数を提供します。

Balancerのプールにおけるフラッシュスワップは、スワップを実行するために伝統的に必要とされる入力トークンを保持する必要性を排除します。代わりに、不均衡を特定すると、Vaultにスワップを実行するように指示し、その後報酬を受け取ることができます。

0x1.1 Balancerにおける様々なプール

以下に、この脆弱性に関連するプールの概念を簡単に紹介します。

  • Linear Pools: Linearプール [2]は、既知の交換レートで資産とそのラップされた利回りベアリングのカウンターパートとの交換を促進するBalancerプールです。名前が示すように、LinearプールはLinear Mathを使用します。linearプールには3つのトークンが含まれます。

    • 同じ基盤トークンを持つ2つの資産、すなわちmainトークンとwrappedトークン
    • 対応するBPT(Balancer Pool Token)。BPTはERC-20トークンです。
  • Nesting Linear Pools: Linear Pool BPTは、別のプール内にネストできます。これにより、スワッパーがベース資産から外部プールのトークンにスワップできるため、ベース資産と外部プール内のトークンとの間に簡単なbatchSwapパスが作成されます。

  • Composable Stable Pools: Composable Stable Pools [3]は、一貫してほぼ同等または既知の交換レートでスワップされると予想される資産のために設計されています。Composable Stable PoolsはStable Mathを使用しており、実質的な価格変動に遭遇する前にかなりの量のスワップを可能にし、同種および相関種のスワップにおける資本効率を大幅に向上させます。

    プールがcomposableであるとは、そのLPトークンとの間でスワップを可能にすることを意味します。そのLPトークンを他のプール(または「ネスト」)に入れると、ネストされたプールトークンから外部プールのトークンへの簡単なbatchSwapが可能になります。

  • Boosted Pools: Boostedプール [4]は、大規模プールにおけるアイドル流動性の資本効率を向上させるために設計されています。Boostedプールは、実際には他のプールのサブクラスです。たとえば、boostedプールはlinearプールの上に構築される可能性があります。

    Boosted Poolsは、ユーザーが一般的なトークンのスワップ流動性を提供できるようにする一方で、アイドルトークンを外部プロトコルに転送することにより、高い資本効率を提供するように設計されています。これにより、流動性プロバイダーは、スワップから収集したスワップ手数料に加えて、Aaveなどのプロトコルのメリットを享受できます。

0x1.2 脆弱なブーストプールの具体的な例: Balancer Boosted Aave USD

Balancer Boosted Aave USD(シンボル:bb-a-USD)は、3つのステーブルコイン(USDC、USDT、DAI)間のスワップを促進し、アイドル流動性をAaveに転送するComposable Stable Poolです。基盤となるlinearプールは次のとおりです。

  • bb-a-USDC(USDCとラップされたaUSDCで構成)
  • bb-a-USDT(USDTとラップされたaUSDTで構成)
  • bb-a-DAI(DAIとラップされたaDAIで構成)

具体的には、bb-a-USDは、3つの異なるlinearプール(DAI、USDC、USDT)のプールトークンをそれぞれ含まれるComposable Stable Poolのコレクションです。以下の図は、公式ドキュメント[5]から提供されたbb-a-USDの構造を示しています。

0x1.3 BPTの価格の計算方法

特定の量のBPT(amountIn)を別のトークン(amountOut)と交換する際に、BPTの価格をどのように決定するかという重要な疑問が自然に生じます。

Balancerは、さまざまなプールで採用されている数学的公式について詳細な説明を提供しています[6, 7]。単純化のため、最も関連性の高い概念を抽象化して要約します。

linearプールを例にとると、BPTの価格はLinearPoolコントラクトのonSwap関数で計算されます。

計算は次のように要約できます。

$$amountOut=amountIn*tokenRate$$

ここでtokenRateは次の式で計算されます。

$_INITIAL_BPT_SUPPLY$は定数です:$2^{112} - 1$。

上記の式では、分子はmainトークンの残高とwrappedトークンの残高の合計として簡略化できます。一方、分母は定義済みの値(_INITIAL_BPT_SUPPLY)とBPTの残高との差です。

計算を実行する前に、すべての関連トークンの残高は正規化される必要があることに注意する価値があります。なぜなら、異なるトークンは異なる小数を持つ可能性があるからです。具体的には、指定されたトークンの実際の残高は、対応するアップスケールファクターに掛けられます。これは_scalingFactors関数によって決定されます。

(1) `Linear`プールのスケーリングファクター

BPTmainトークンは、両方とも通常の一定のスケーリングファクターを持っています。

(2) `bb-a-USD`のような`Boosted`プールのスケーリングファクター

boostedプールの計算は少し複雑です。具体的には、返されるスケーリングファクターは、生の(例:1e18)スケーリングファクターとトークンレートの積であり、これはキャッシュされたトークンレート(存在する場合)から取得されます。

キャッシュされたトークンレートはどこから来るのでしょうか?_updateTokenRateCacheというプライベート関数が存在します。明らかに、この関数は最初にトークンのgetRate関数を呼び出してレートを取得し、それをキャッシュします。

再度、bb-a-USDCを例にとると、対応するgetRate関数のコアロジックは、前述の計算式に従います。

onSwap関数を経由するパスに加えて、_updateTokenRateCache関数をトリガーする3つの可能なパスがあります。

さらに、onSwap関数を経由するパスの更新を実行する際に、有効期限チェックが配置されています。

0x2 脆弱性分析

根本原因は、linearプールのonSwap関数における切り捨てロジックによって引き起こされる価格操作にあります。これは、結果としてboostedプールによって使用されるキャッシュされたトークンレートに不適切に影響を与えます。

具体的には、_downscaleDown関数が呼び出される際にamountOutは切り捨てられます。したがって、amountOutscalingFactors[indexOut]の間に大きな差がある場合、_downscaleDown関数の戻り値はゼロになる可能性があります。

例えば、bb-a-USDC(BPTとして)を使用してbb-a-USDCプールでUSDC(mainトークンとして)をスワップする場合、amountOutが1,000,000,000,000未満の場合、戻り値は常にゼロに切り捨てられます。これにより、bb-a-USDCの残高が増加します。なぜなら、これはbb-a-USDCの流動性を一方的に追加していると見なされる可能性があるためです。

結果として、BPTがスワップに使用されたトークンである場合、分子は同じままで分母が減少することを考慮すると、レート計算式に従ってそのレートが上昇します。このバグは、(巨大な)価格差につながるために悪用される可能性があります。

0x3 攻撃分析

攻撃トランザクションは、以下の攻撃ステップで構成されています。

  1. Aaveからフラッシュローンで300,000 USDCを借入。
  2. bb-a-USDCプールで1.067753 USDCを0.970495 aUSDCにスワップ。
  3. bb-a-USDCおよびbb-a-USDプールでbatchSwapを実行。つまり、42,203 USDCで15,628 bb-a-USDC、139,431 bb-a-DAI、および248,868 bb-a-USDTを回収。詳細なステップを以下の表にまとめます(小数付き)。
  1. LPトークンを対応する基盤ステーブルコインにスワップ。
  • bb-a-DAIプールで139,431 bb-a-DAI -> 141,127 DAI
  • bb-a-USDCプールで15,628 bb-a-USDC -> 15,685 USDC
  • bb-a-USDTプールで248,868 bb-a-USDT -> 253,461 USDT
  1. フラッシュローンを返済し、最終的な利益は次のとおりです。
  • 114,324 DAI
  • 253,461 USDT
  • 0.970495 aUSDC

攻撃者はステップ2でbb-a-USDCプールからUSDCaUSDCで使い果たしたことに注意する価値があります。これにより、ステップ3での価格操作がはるかに容易になります。つまり、攻撃者はUSDCbb-a-USDCにのみ集中すればよかったのです。

ここでステップ3が重要な役割を果たします。このステップの詳細を掘り下げて、攻撃者が利益を上げられた理由を理解しましょう。具体的には、

  • ステップ3.1は、bb-a-USDCプールからbb-a-USDCUSDCを使い果たすために使用されます。
  • ステップ3.3および3.4は、bb-a-USDCからbb-a-DAIへ、ステップ3.5はbb-a-USDCからbb-a-USDTへスワップするために使用されます。
  • ステップ3.7は、bb-a-USDCプールからUSDCbb-a-USDCにスワップするために使用されます。

ここで、ステップ3.2と3.6は、前述の切り捨てによりターゲットトークン(USDC)をスワップバックしないため、スワップ後のターゲットトークンの残高は変更されません。これは、bb-a-USDCプールへの追加のbb-a-USDC流動性の追加と見なすことができます。

明らかに、異常なスワップは主にステップ3.4、3.5、および3.7で発生します。以下では、これらの各ステップの詳細を順に見ていきます。

(1) bb-a-USDC -> bb-a-DAI

ステップ3.3では、bb-a-USDCbb-a-DAIの交換レートはほぼ1ですが、ステップ3.4では、交換レートは19になります。

  • ステップ3.3:1,000,339,378,515,783,699 / 1,000,000,000,000,000,000 = 1.00
  • ステップ3.4:139,430,482,942,020,211,267,110 / 7,300,000,000,000,000,000,000 = 19.10

前述のコードロジックを思い出すと、ステップ3.3では、キャッシュされたトークンレートを返してスケーリングファクター(1,012,181,365,780,643,700)を計算した後、レートを更新して新しい値(40,240,000,000,000,000,000)を計算します。この更新された値は、新しいスケーリングファクターとしてステップ3.4で使用されます。生の(例:1e18)スケーリングファクターは変更されないため、これは新しいレートが古いレートよりも約40倍高いことを意味します。

しかし、この大幅な増加はどこから来るのでしょうか?tokenRateの計算式に戻りましょう。ステップ2でaUSDCの残高が枯渇したため、tokenRateの計算は次のように簡略化できます。

ここで、nominalMainBalanceの実際の値は、ステップ3.2で発生した切り捨てによるものです。

(2) bb-a-USDC -> bb-a-USDT

ステップ3.5は同じトリックを使用して、より多くのbb-a-USDTを取得します。bb-a-USDCbb-a-USDTの交換レートは12倍以上になります。

  • 248,868,905,733,352,246,491,156 / 20,000,000,000,000,000,000,000 = 12.44

(3) USDC -> bb-a-USDC

さらに、ステップ3.6でbptBalanceが増加し、ステップ3.7でbptSupplyがゼロになります。これにより、ほぼ1:1の交換レートでUSDCbb-a-USDCにスワップすることが可能になります。

0x4 攻撃と利益の要約

執筆時点までに、私たちは数多くの攻撃が実際に行われていることを確認しており、212万ドルを超える損失が発生しています。要約すると、これらの攻撃は、次の3つの異なるアカウントによって実行されました。

Balancerはこの脆弱性により、約100万ドルの総損失を被りました。Balancerへの最初の攻撃から12時間未満で、そのフォークプロトコルであるBeethoven Xも同様の攻撃を受け、推定約110万ドルの損失が発生しました。Beethoven XはBalancerよりも大きな損失を被りました! このセキュリティインシデントによる累積損失は、約212万ドルに達しました。

これらの攻撃トランザクションの完全なリストは、私たちが準備したドキュメントにまとめられています。詳細については、そちらを参照してください。

攻撃者に関するいくつかの観察結果

各ネットワークによって開始されたトランザクションを分析した結果、Fantom https://app.blocksec.com/explorer/tx/ftm/0x9388c13b5abeecb0a67159af60ca2a223293d587d12ba6b474480019b4440cabでの攻撃トランザクションのトレースは、Ethereum https://app.blocksec.com/explorer/tx/eth/0x945cd2720316a7b9b43d0099818dc9c38f882aafe3211bbf63474b4d22e58398およびOptimism https://app.blocksec.com/explorer/tx/optimism/0xbbd83630207dacc27d5608bd6c9bb2f9f5d45bcca8955ab58834cc154a2dabf6とは著しい違いがあることがわかりました。

具体的には、主要な関数の顕著な違いに加えて、Fantomの攻撃者はMEVボットによるフロントランニングを回避するために2つのユニークなトリックを利用しました。さらに、Fantomでの攻撃に使用された資金は、攻撃の163日前に準備されていました。

上記で詳述した観察結果から、次のことが推測できます。

0x5 結論

要約すると、これは切り捨てロジックに起因する巧妙な脆弱性です。しかし、この脆弱性を悪用することは容易ではありません。具体的には、攻撃者はlinearプールでの切り捨て問題を悪用してキャッシュされたトークンレートをインフレさせ、それによって対応するboostedプールでのトークン価格を操作することができました。

このインシデントはまた、脆弱なソースからフォークしたプロジェクトへのタイムリーな通知の重要性を強調しています。Balancerの警告にもかかわらず、フォークされたプロトコルを標的とした攻撃は続いており、これらのフォークされたプロジェクトがソースプロジェクトからのセキュリティアップデートに関する情報を入手することの必要性を浮き彫りにしています。しかし、これらのフォークされたプロジェクトが迅速な通知を受け取ることを保証することは、コミュニティにとって継続的な課題となっています。

さらに、連続した攻撃は、プロアクティブな脅威防止の重要性を強調しており、これは潜在的な損失を効果的に軽減するのに役立つ可能性があります。

参考文献

Sign up for the latest updates