Bybit の事件と、攻撃が通った経路
2025 年 2 月 21 日、Bybit は Safe のマルチシグのコールドウォレットから約 15 億ドルを失いました。この業界の歴史上最大の盗難であり、丁寧に読む価値があります。たったひとつを除いて、ほとんどすべてが正しかったからです。
何が起きたか
公開された事後分析をもとにした要約です。
- 攻撃者が
Safe{Wallet}の開発者のマシンを侵害し、Safe{Wallet}のフロントエンドを配信していた AWS S3 バケットに悪意のある JavaScript を仕込みました。コードは 2 月 19 日に仕込まれ、2 月 21 日に、Bybit の特定の Safe を狙って発動しました。 - Bybit の署名者たちはインターフェースを開き、ごく普通に見える取引を確認しました。
- 実際に彼らのハードウェアウォレットへ送られたのは、その取引ではありませんでした。Safe プロキシの
masterCopy(スロット 0)を上書きし、アカウントの実装をまるごと攻撃者のものに置き換えるdelegatecallでした。 - 署名者は承認しました。署名は有効でした。コントラクトは、指示されたとおりのことを正確に実行しました。
攻撃は北朝鮮と関係する活動によるものと公表されています(FBI は TraderTraitor と呼ばれるグループを名指ししました)。
破られていなかったもの
- Safe のコントラクトではありません。有効に署名された命令を実行しただけです。Safe のバグが突かれたわけではありません。
- 暗号でもありません。どの署名も本物でした。
- ハードウェアウォレットでもありません。Ledger の端末は経路の中にあり、それでも署名しました。ハードウェアウォレットは渡されたものを表示するだけで、渡されたのが悪意のある内容だったからです。
delegatecallを人が判断できる形にデコードできない端末が守るのは鍵であって、判断ではありません。
破られたのは、あらゆるウォレットのインターフェースの根底にある前提です。取引を説明している画面と、署名されているバイト列は同じものだ、という前提です。
これが特殊な事件ではなく、一般的な問題である理由
ウェブウォレットで作られる署名のほとんどは、この前提の上に成り立っています。インターフェースが中身を組み立て、インターフェースが要約を描き、両者が一致しているかを独立に確かめるものは何もありません。そのインターフェースを配信するコードが置き換えられれば(侵害されたビルドパイプライン、乗っ取られた CDN、悪意のある依存パッケージ、盗まれたデプロイ用の認証情報によって)、要約は攻撃者の望むとおりのものになり、それでもあなたの署名は本物です。
Vela の署名の設計が狙っているのは、このリスクです。フィッシングでも、鍵の流出でもありません。あなたに嘘をつく署名画面です。
Vela の取り組み
calldata まで踏み込んだクリア署名。すべての取引は、承認の前に人が読める意図にデコードされます。金額、受取人、その呼び出しが実際に何をするのか(ERC-7730)。デコードできない呼び出しは、問題がないかのように静かに表示されるのではなく、デコードできないと明示されます。Bybit の攻撃の中身は、実装のアドレスを差し替える delegatecall でした。それこそ署名者を即座に立ち止まらせるべき形であり、親しみやすい要約の裏に隠れていたからこそ、止まらなかったのです。
正確に書いておくべきことが 2 つあります。dApp は Vela に delegatecall を直接求めることはできません。ページが出せるリクエストからは通常の呼び出ししか生まれないので、Bybit の攻撃の中身そのものがこの経路で届くことはありません。そして、あなたの Safe から Safe 自身への呼び出しを求めるページは、デコードされるだけでなく拒否されます。enableModule、addOwnerWithThreshold、swapOwner、setFallbackHandler、setGuard とその仲間は、宛先があなた自身のウォレットであればブロックされます。バッチの中でも MultiSend の中でも同じです。宛先が何であれ delegatecall を含む要素も、SafeTx の型付きデータへの署名も同様です。ウォレットはどの呼び出しを拒否したのかを伝え、署名できるものは何も出しません。そのどれかに一度署名すれば、Bybit の攻撃と同じくらい完全にアカウントを明け渡すことになります。有効にされたモジュールは、その後、自分で delegatecall を実行できるからです。そして、Safe{Wallet} のように Vela 自身のコードが置き換えられれば、デコードも攻撃者のものになります。次の点は、そのためのものです。
インターフェースを検算できる独立した経路。Vela の Trusted Signer は、sign.getvela.app にある単一ファイルのページです。リクエストを自分でデコードし、WebAuthn の署名も自分で行います。端から端まで読むことも、1 バイトも違わずに再ビルドすることも、自分で配信することもできるページです。本体のアプリとサプライチェーンを共有しないセカンドオピニオンで、アプリはリクエストを渡すだけ、何に署名するのかはページが自分で計算します。状況:公開済みです。デスクトップ、iPhone、Android のアプリは、ウォレットの作成時やサインイン時にこれを選んだ端末で、これを使います。デスクトップアプリはページを自分に組み込まれたバージョンと比較しますが、一致しなくてもログに記録するだけで、スマートフォンのアプリはまだこの確認をしていません。
私たちが奪われうる管理者権限はありません。Vela のアカウントは改変していない Safe v1.4.1で、Vela はそこに特権的な権限を一切持ちません。管理者鍵も、私たちが強要されたり侵害されたりして使ってしまうようなアップグレードの経路もありません。ただし、これでなくならないものもはっきりさせておきます。Bybit の攻撃者が使った仕組み、つまり所有者の署名でアカウントの実装を書き換える delegatecall は、Vela のものも含め、すべての Safe にいまも存在します(Vela 自身のバッチ取引も、Safe の MultiSend への delegatecall を使っています)。それには、あなたの鍵のどれかによる有効な署名が必要です。その署名をだまし取られないための守りが、上で述べたデコードと、独立した確認です。
署名のたびに新たに確認。署名には毎回、鍵そのものによる確認が必要です。Face ID、指紋、PIN、またはセキュリティキーへのタッチと PIN です。長期間有効なセッション鍵はないので、あなたがその場にいないのに何かが代わりに署名できる時間は存在しません。
最後の砦としてのセルフホスティング。アプリもバックエンドのサービスもオープンソースです。私たちのビルドパイプラインをまったく信頼したくないなら、拡張機能やアプリを自分でビルドし、必要なサービスを動かしてください。手順はセルフホスティングガイドにあります。これで私たちのビルドパイプラインは信頼の連鎖から外れます。自分でビルドするコードは引き続き信頼することになるので、読んでください。
Vela が主張しないこと
Vela のフロントエンドも、Safe{Wallet} と同じやり方で侵害される可能性があります。私たちのコードは監査を受けていません。それ以上に聞こえのいいことを言うのは、この事件で終わりにすべきだった類いの安心材料そのものです。
この設計がしようとしているのは、経路を狭めることです。攻撃の中身を不透明なままにせず読めるようにし、あなたに対して悪用されうる管理者権限を持たず、私たちではないもので確かめる手段を渡すこと。正直にまとめるなら、この種の攻撃は設計によって緩和されてはいても、なくなってはいません。さらに守りを固める部分は、未完成のまま監査と既知の問題に挙げています。