T29 · Protocol Security
攻撃者は脆弱性を使わず、資金だけで何ができるか?
- 練習する能力
- BuilderFinancial LiteracySystem Thinking
- 実践
- 自分のプロトコルの攻撃コストを計算する。必要な資金、ガス、時間と得られる利益を求める。
- AI Lab
- AI に攻撃者として攻撃計画を書かせ、費用対効果で順位を付けて防御策を示す。
現実の問題
プロトコルを公開しました。T28 の確認は全て通り、再入はなく、権限境界は明確で、丸め方向も正しく、upgradeにはtimelockがあります。監査報告に重大な問題はありません。
一か月後、ある午後の十数分で、多額の資金を失いました。
取引を振り返るほど、分からなくなります。
- 想定外に呼ばれた関数はない
- 権限を越えた操作はない
- 書いたコードと異なる動作は一行もない
- 取引を個別に見れば全て合法攻撃者は大金を借り、取引所で価格を極端な位置へ動かし、その価格でプロトコルと規則通りの取引をして差額を得て、借金を返しました。全て一つの取引です。
何も破っていません。計算して利益が出ると分かり、その通りに実行しただけです。
T28 のCEI、アクセス制御、不変条件は、コードがすべきでないことをする問題を防ぎます。今回は、コードは全てすべきことをしたため、防御は一つも起動しません。
前章とは異なる問題を扱います。
脆弱性が不要で、利益が出るだけで攻撃できるとき、どう防ぐか?
思考実験
コードから離れ、casinoを考えます。
明確で公平な規則を持ち、胴元が不正をしないtableがあります。直近一時間のchipの賭けられた方向から、oddsをリアルタイムで計算します。 多く賭けられる側へoddsが偏ります。
規則は問題なさそうでしたが、ある日、一人がchipを荷車で運んできました。
半分を一分間で一方向に集中させ、規則に忠実なtableのoddsを極端にします。残り半分は、自分で作ったoddsで逆方向に賭けます。
精算すると、oddsを動かした費用以上に勝ち、chipを持ち帰ります。
casinoは騙されていません。 全規則を厳密に守り、全ての賭けは合法でした。破れたのは暗黙の仮定です。
chipの方向は一人の資金でなく、多くの人の独立した判断を表すという仮定です。
普段は一人がそんなに多く持たないため成立します。壊す条件は、一度に十分な資金を出せる人がいることだけです。
casinoをプロトコルへ、chipを、借りて使った直後に返すほぼ無料の資金へ置き換えると、二点が見えます。
第一、あなたのプロトコルにも、普段誰もその資金を持たないという仮定があります。poolを一度で買い切れない、過半数の票を瞬時に取れない、一blockで価格がそこまで動かない、などです。
第二、暗号資産の世界では、十分な大金を一度に出す前提が、ほぼ不可能から一取引で可能へ変わります。
思考実験を一文にします。
普段誰もできないという全仮定を探し、今、無限の資金を持つ人がいるとして、本当にできないかを問い直します。
あなたが決める
価格を必要とするプロトコルは、どこから取得しますか?この選択が攻撃の採算を大きく左右します。
結果を観察する
四方式を、破るための費用で比較します。
| 攻略費用の目安 | 資金を借りられるか | 主な副作用 | |
|---|---|---|---|
| pool即時価格 | 目標価格へ動かす資金 | flash loanで可能 | 最も安く、破られやすい |
| 時間加重 | 一定期間の偏り維持費 | 一部可能 | 遅れ、急変時の不具合 |
| 複数source中央値 | 独立sourceの過半数同時操作 | 独立性による | 冗長性が見せかけの場合 |
| 担保とchallenge | challenge機構を破る費用 | 通常不可 | 遅い、challenge参加に依存 |
第二、第三列から第一の結論が出ます。
安全性とは、攻略費用が攻略利益を上回ることです。暗号資産では、その費用の資金部分を借りられることが多いです。
安全の計算が変わります。T28 では鍵と起動可能な経路が能力の制限でした。ここでは採算が合うかです。flash loanにより、一取引の中では一億を持つ人と百元を持つ人に差がなく、元本の欄にほぼ無限の数を入れられます。
第二の結論が、この章で学ぶ中心動作です。
安全性を判定するとき、攻撃者の立場で採算を計算します。
式は単純で、各項を正しく埋めることが難しいです。
攻撃利益 - 攻撃費用 = 攻撃者の純利益
内訳:
攻撃費用 = 資金費用 + Gas費用 + 時間費用 + リスク費用
資金費用 = flash loan可能なら、ほぼその手数料(極小)
= 不可なら、資金を拘束する機会費用正なら攻撃は発生します。可能性ではなく、必ずです。オンチェーンは公開され、世界中が同じ計算をし、あなたは一度間違えれば十分だからです。
モデルを作る
攻撃費用の一覧:各項を正しく埋める
どの攻撃案にも、この表を埋め、純利益の符号を見ます。
| 費用項目 | 推定方法 | 間違えやすい点 |
|---|---|---|
| 資金 | flash loanなら手数料、それ以外は機会費用 | 既定で借入可能と考える。経路に一晩を越える段階があるか確認 |
| Gas | 取引の複雑さ×その時のGas価格 | 複雑な攻撃はblock Gas上限に当たる |
| 時間 | 必要block数と取引数 | 持ち越す攻撃は、手数料から機会費用へ跳ね上がる |
| リスク | 途中失敗、front run、逆方向裁定の確率 | atomic取引ではほぼ0、多段階では急増 |
次に利益を埋めます。
| 利益項目 | 推定方法 |
|---|---|
| 直接引き出せる資産 | その経路が触れられるprotocol資金の上限 |
| 操作後の裁定差額 | 価格変化幅×取れるpositionの大きさ |
| 安く買える担保 | 誤清算・誤価格設定される資産量 |
資金を借りられるかが最重要です。 数十元の手数料か、数千万の元本の機会費用か、六、七桁の差を決めます。
基準は一つです。
攻撃全体が一取引で完了し、最後に借入資金を全額返せるならflash loanを使えます。
その場合、攻撃者の資金は、全chainの融資protocolが今貸し出せる合計と仮定します。大げさな仮定ではなく、下限です。
flash loan:元本の欄を消す
この章全体を増幅する仕組みを説明します。
一つの取引で:
1. 無担保で大金を借りる
2. 資金で何でも行う
3. 取引終了前に、元本と手数料を返す
3を実行しなければ全取引がrevertし、1と2もなかったことになる貸し手は返済できなければ全体を戻せるので安全ですが、protocolの安全には大きな影響があります。
新たな攻撃を作るのでなく、大金が必要な全攻撃の条件を、資金保有から採算が合うことへ下げます。
flash loan自身ではなく、増幅された経済的仮定が脆弱性です。利用者を禁止するのは不可能で無意味であり、攻撃者に資金が足りないという仮定へ依存しないことが防御です。
コードへ書ける直接の帰結です。
同じ取引内で価格、share、weightを変えてから使う場所では、その変化は借りた資金による操作と仮定します。
T28 の使用より状態更新が遅い問題の経済版です。前章は再入、ここは操作です。同じ構造が異なる層に現れます。
四種類の経済攻撃を計算する
- 資金を借りる
- 歪んだ状態を作る
- 歪んだ状態でprotocolと取引
- 状態を戻す
- 返済し、差額を得る
第一:oracle操作。 第二段階で価格を歪めます。
利益 = 歪んだ価格でprotocolから取得する資産
費用 = flash loan手数料 + Gas ≈ 極小
安全条件 = 目標へ価格を動かす費用 > 取得できる利益四つの価格方式を組み合わせ、深い流動性、時間加重、本当に独立した複数sourceで価格操作の費用を上げます。
第二:ガバナンス攻撃。 歪めるのは投票権です。
利益 = 悪意ある提案で移せる資金
費用 = 過半数票を借りる費用 + timelock待ちの費用
安全条件 = 借票と待機の合計費用 > 提案で移せる資金決定的なのは投票権を借りられるか、timelockがあるかです。tokenをflash loanでき、投票が即時発効なら、借票、投票、実行、返票を一取引で行えます。提案前のsnapshotで借票を除き、重要提案にtimelockを置いてcommunityの反応時間を確保します。 第24章 の一投票で資金を持ち出せるかへの技術的答えです。
第三:流動性攻撃。 poolの構成を歪めます。
利益 = 歪んだpoolから取得する価値、または誤清算positionの安い取得
費用 = 大資金の一時拘束 + Gas
安全条件 = poolを戻す逆方向費用 > 取得する価値T28 の最初の預金者は特例です。一般には、浅いpoolの価格やweightを大金で一時変更し、その状態に依存するロジックを起動します。
第四:インセンティブ攻撃。 protocolが認識する貢献を歪めます。
利益 = 不正取得する補助金、報酬、airdrop
費用 = 貢献の偽造費用(多くは極小)
安全条件 = 貢献一件の偽造費用 > 一件の報酬攻撃らしく見えませんが、最も広くあります。出来高による返金、自己売買の順位操作、一万addressでairdropを取るSybilなどです。有効な利用者を装う費用が報酬以下なら、必ず大量に装う人がいます。 T27 のPaymaster枯渇も同じです。
完全な計算例
poolの即時価格で担保を評価する冒頭の場面を計算します。
攻撃案:
1. flash loanで大量の基礎資産を借りる
2. poolの担保価格を上げる
3. 高く評価された担保で真の価値以上の資産を借りる
4. pool価格を戻す(一部の資金は回収される)
5. flash loanを返し、過剰借入分を持ち帰る
費用:
資金 = 一取引で可能 → 極小のflash loan手数料
Gas = 複雑な取引で中程度、利益よりずっと小さい
時間 = atomicな一取引、持ち越し費用なし
リスク = 成功または全revertなので≈0
利益:
= (上げた担保価格 - 真の担保価格)× 借入可能な資産量
= 正で、通常は費用を大きく上回る
結論:純利益が正 → 攻撃は起きる防御の一変数を変えます。十分長い時間加重価格を使います。
第二段階:平均を上げるため、複数blockで十分長く高価格を維持する。
資金 = flash loan不可(持ち越す)→ 大きな元本の機会費用
時間 = 複数block、裁定者が偏りを逆方向に削る
リスク = ≈0から、維持中の裁定とfront runの現実的リスクへ
結論:費用が手数料から「大元本 + 時間 + リスク」へ跳ね上がり、
多くは利益以上になって、割に合わなくなる脆弱性を修正せず、費用の一項を借入可能から借入不可へ変えました。 道を塞ぐのでなく、高くすることが中心手法です。
システムの視点:依存先のリスクを継承する
protocolは独立せず、poolの価格、bridge資産、別protocolへの利息運用などを使います。
全依存先の経済的仮定を継承します。 攻撃は接続部を好みます。
- 担保がbridgeのwrapped資産なら、価値はbridgeの無事故に依存します(T26)。
- 他protocolへ預けて利息を得るなら、その経済的仮定もあなたのものになります。
- 清算人の採算に依存するなら、急変時は全員が割に合わなくなるかもしれません(T22 の清算速度の安全境界)。
依存図を描き、全ての外部依存へ問います。
その経済的仮定が破られると、自分に何が連鎖するか?隔離できるか?
この図が、次章の脅威モデルの骨格です。
何と呼ぶか
コードの脆弱性でなく、経済的仮定で利益を得る攻撃です。
判定はbugの有無でなく採算です。防御も修正だけでなく、費用を利益以上に引き上げます。
一取引で借り、使い、返す無担保融資です。返せなければ全revertします。
攻撃自身でなく増幅器であり、大金保有の条件を採算の条件へ下げます。攻撃評価で最初に問うのは、資金を借りられるかです。
依存する価格を資金で歪め、その価格でprotocolと取引します。
費用は価格sourceを目標へ動かす資金にほぼ等しく、flash loan可能な即時pool価格ならほぼゼロです。深い流動性、時間加重、独立sourceで操作費用を上げます。
十分な票を取り、自分へ資金を渡す提案を通します。
決定的なのは借票可能性と実行前のtimelockです。提案前snapshotは借票を除き、timelockはcommunityの反応と退出の時間を確保します。どちらかが欠けると、一投票で金庫を失い得ます。
一人が多数の独立利用者を装い、人数や独立貢献による報酬を取得します。
偽装費用が報酬より低ければ大量偽装が起きます。独立sourceやguardianの冗長性も破ります。冗長性は真の独立性がある場合だけ成立します。
各参加者にとって、誠実な行動が最適になるよう報酬と罰を設計します。
各立場で不誠実な行動の利益が誠実な行動以上かを計算します。そうなら、コードを一行も間違えていなくても、インセンティブに穴があります。
実践
自作protocolを、自分のFork環境だけで対象にします。 成果物は採算reportで、実際の攻撃ではありません。公開済みの実在コントラクトを試すことは範囲外であり、認められません。
成果物は、全ての普段できないという仮定と、その攻撃費用・利益の表です。
全ての経済的仮定を列挙します。
各関数を読み、普段起きないという暗黙の条件を書きます。
- poolを一度で買い切る、または極端に買う人はいない
- 一blockで価格は特定位置まで動かない
- 過半数票を瞬時に取得できない
- 最初の入金は1 weiではない
- 清算人にとって清算は常に有利
- 報酬目的で大量accountを作る人はいない各行に「今、無限の資金を持つ人がいたら」を加えます。 列挙が不完全なら、後の計算が正しくても役立たない、Labの土台です。
各仮定で、最初に借入可能性を判定します。
一取引で攻撃を完了し、最後に全額返せるか? を使います。
可能ならflash loan手数料で、ほぼ無視できます。 不可なら持ち越しが必要な段階を特定し、元本の機会費用を入れます。
表で最重要の判定で、費用の桁を決めます。
残り三項を埋めます。
Gas :取引の複雑さ×やや高いGas価格
時間 :必要block数と取引数
リスク :atomicなら≈0、多段階では途中失敗、front run、逆方向裁定の確率Forkで実際に取引し、Gasを推定から実測へ変えます。思い込みを多く修正できます。
利益を埋めます。
仮定が破られたとき、直接取得する資金、裁定差額、安く引き取る担保を計算します。
上限を計算します。攻撃者は許された最大positionを取るからです。
純利益で並べます。
純利益 = 利益 - 費用高い順に並べ、先頭が最も事故の起きそうな箇所です。 この段階から全課題へ添える項目がこれです。
純利益が正の全項目をForkで検証します。
自分のFork、自作コントラクトだけです。 攻撃案をテストにし、本当に正の利益が出るか確認します。
T28 と同じく、Forkで利益を出すテストが、経路の実在を証明します。 計算が美しくても実行できなければ、どこかの項が間違っているので探します。
先頭の攻撃の防御変数を一つ変え、再計算します。
即時価格から時間加重へ、ガバナンスにtimelockとsnapshot、shareに仮想offset、報酬に費用の閾値などを加えます。
純利益を正から負へ変えることが目標です。 Forkで攻撃テストを再実行し、利益が出なくなったことを確認します。
変更前後の二つの計算表を並べた成果物は、脆弱性を塞ぐだけでなく、経路を高くした証拠です。
AI Lab
AIの得意な攻撃案の列挙を任せ、苦手な精密な計算と順位付けは自分で行います。三段階です。
第一段階:
私のprotocolです(契約、価格source、ガバナンス、インセンティブ、外部依存を渡す)。
コードの脆弱性でなく、資金だけを使う攻撃者になってください。
少なくとも六つの案で、次を示してください。
- 何を借り、何をし、どの順序か
- 私のどの経済的仮定を利用するか
第二段階:
各案の資金(flash loan可否)、Gas、時間、リスクを埋め、利益は上限で計算。
資金は、一取引で完了して返済できるかを明示してください。
第三段階:
各案の防御を示してください。
特定経路を塞ぐことと、経済的仮定自体を取り除くことを区別します。
前者なら、どの変数を変えると迂回できるか説明してください。第一段階は通常良好です。案の列挙は発散的な仕事で、モデルの得意分野です。 思いつかなかった組み合わせを示すことが最大の価値です。
第二段階では、資金項を誤りやすいです。 blockをまたぐ攻撃をflash loan可能として何桁も過小評価し、またはatomicで可能なのに大元本が必要として過大評価し、安全と思わせます。全項目を自分で再確認します。基準は一取引で完了して返せるかだけです。
第三段階が分岐点です。価格偏差確認などの特定経路を塞ぐ案は、入口を変えると迂回されます。真の防御は、時間加重へ変更して価格操作全体を高くするなど、仮定自体を消します。変えると迂回できる変数を説明させると、違いが見えます。
最後は自分で純利益順に並べ、第一の防御前後を計算します。モデルの順位は数値より感覚に寄ることが多く、核心能力を任せられません。案を考える補助はできますが、最も危険な案と、防御で本当に負になったかは自分が確認します。 T28 と同じ、AIは手掛かりを生成し、結論はあなたが出します。
AI の回答後、自分で必ず検証すること
- 各攻撃の資金項でflash loan可能性を正しく判定したか
- 持ち越す攻撃をflash loan可能と誤認し、費用を何桁も下げていないか
- 利益は上限か。最大positionを取るので、典型例は過小評価になる
- 自分のForkで正の利益を出せるか。不可なら計算が誤るか、既に防いでいる
- 防御は特定経路を塞ぐか、経済的仮定を消すか。前者は変数変更で迂回される
- 継承する外部依存の経済的仮定も考慮したか。ここは特に漏れやすい
- 自分で純利益順に並べ、第一の攻撃の防御前後を計算する
実例
融資protocolがpool即時価格を担保に使い、借りた大金で担保価格を上げ、真の価値以上を借り、価格を戻し返済する操作を一取引で行いました。
コードは一行も異常動作していません。pool価格が真の市場を表すという仮定を、借りた資金で破りました。
冒頭の場面です。防御は価格取得方式を変え、操作費用を取得利益以上にすることです。
借入可能なガバナンスtokenと即時実行、timelockなしのprotocolで、大量tokenを借り、自分へ金庫資産を移す提案を通し、実行して返しました。
規則を完全に守り、投票者は実際に借りた過半数票を持ち、提案は過半数で通りました。
提案前の投票権snapshotと、実行前timelockが二つの防御です。第24章 の一投票で資金を失う問いがこれです。
T28 のコード側の問題を経済側から見ると、極小初回入金と残高による総資産を使い、直接送金でshare価格を上げる流動性攻撃です。
費用は小額入金と一送金で、利益は後続の全入金です。状態を偽造し、その状態で取引し、差額を得る五段階です。
内部記帳、仮想share、dead shareと、0 shareならrevertする防御を組み合わせます。
出来高で返金するprotocolで、scriptの自己売買によりpoolを使い切りました。Gasと手数料だけが費用で、それも返金されました。
脆弱性は利用していません。出来高で払う約束を守っただけで、その指標を無料で偽造できることが問題です。
一取引量を作る純費用が返金より低ければ、無限に作る人がいます。 設計はコードを見るだけでなく、この採算で検証します。
変数を一つ変える
資金は手数料から、大元本と機会費用へ、リスクはほぼ0から維持中の裁定・front runへ変わります。
多くの瞬間操作は割に合わなくなりますが、攻撃を消しません。低流動性なら平均も徐々に曲げられ、遅れにより急変時の清算を逃すかもしれません。
無料の防御はなく、より耐えられる場所へリスクを移します。
一時的な借票ができず、購入しても提案時に間に合わないため、その経路はほぼ閉じます。
代償は柔軟性です。正常な大口移転、委任、liquid stakingを再考します。長期潜伏する過半数保有者にはsnapshotが効かず、timelockとcommunityの警戒に依存します。
借票制限、snapshot、timelock、community監督を組み合わせ、各層で費用を上げます。
自分のコードは同じでも費用表が下がり、借入額が増えて、以前は資金不足で割に合わなかった攻撃が有利になります。
他人のparameter変更で、自分の安全性が通知なしに下がります。 これが特に重要で直感に反する点です。
poolの深さ、借入枠、Gas価格は動くため、定期的に、特に依存先の変更時は再計算します。 次章で継続監視にする理由です。
持ち帰る問い
損失は流動性提供者や預金者が分担し、悪いことをせず、採算の合う攻撃があるsystemへ預けただけです。protocol側は全経路を負にする責任があります。コードの脆弱性がないことは、経済的脆弱性がないことではありません。
インセンティブは安全性の問題でもあります。全て、攻撃者の側から、不誠実な行動が誠実な行動より儲かるか計算します。 そうなら攻撃面です。出来高偽造、Sybil、自己売買は採算設計の誤りです。
Tokenがゼロでも動くかに加え、依存する価格、流動性、外部protocolの仮定が破られると、何が連鎖するか? 依存図の全nodeへ問うと、systemic risk一覧と次章の脅威モデルの起点になります。
章末チェック
利益から費用を引いて正かです。正なら、公開されたchainで世界中が計算しているため必ず起き、あなたは一度間違えれば十分です。
最重要の資金項は借りられることが多く、資金保有より、借りた資金で完了できるかが問題です。
費用を利益以上に上げ、通常は一項を借入可能から不可へ変えます。
返せなければ全revertするので貸し手には安全で、新しい攻撃を作りません。
大金保有を採算の条件へ変えます。一取引で完了して返せるなら、攻撃者の資金は全chainの借入可能合計と仮定します。
最初に問うのは、資金を借りられるか? で、数十元か数千万かを決めます。
即時pool価格:目標へ動かす資金をflash loanできるので、実費はほぼ手数料で、最も安く破られやすいです。
時間加重:複数blockで維持し、flash loan不可となり、元本の機会費用と裁定リスクが増えます。ただし浅い資産は徐々に操作され得ます。
複数source中央値:過半数の同時操作が必要ですが、本当に独立することが前提です。同じpoolなら一操作で多数を動かせます。
通常は組み合わせ、価格を動かす費用を上げます。
提案前snapshot:提案時に借りた票は過去に持っていないので、投票できません。
実行前timelock:票を集めて通しても待機が必要で、communityが見て撤資、否決、緊急介入できます。
snapshotだけでは長期保有者が即時実行し、timelockだけでは借票が最初を通れます。両方で一投票の攻略費用を上げます。
標準解答はありません。次を確認します。
- 全経済的仮定を列挙し、無限資金の問いを加えたか。
- 各項で一取引内の完了と返済によりflash loan可能性を判定したか。
- 最大positionを前提に利益の上限を計算したか。
- 純利益順に並べ、先頭を課題の最も危険な項目にしたか。
- Forkで正の利益を出したか。不可なら計算項の誤りを探す。
- 防御は仮定自体を消したか、特定経路だけか。後者は変数で迂回される。
- 外部依存の仮定とparameter変化を含めたか。
第六、第七が理解を分けます。真の防御は道を高くすることです。特定の門だけを塞ぐのでなく、依存する世界の変化で再計算します。
要点
経済攻撃にコードの脆弱性は不要で、利益が出る計算だけあればよい。