💰️

DeFiで暗号資産を盗まれかけた私がホワイトハッカーとしてバグバウンティに投稿するまで

に公開

はじめに

「高金利」という言葉に、胸が高鳴った経験はありませんか?

これは、暗号資産投資家である私、kacky が、とある DeFi(分散型金融)で見つけた魅力的な金利に惹かれ、危うく虎の子の資産を失いかけたところから、ホワイトハッカーとしてバグバウンティ(脆弱性報奨金)に投稿するまでの一部始終を記録した、ノンフィクション・ドキュメンタリーです。

DeFi の世界に潜むリスクと、問題解決へのプロセスを、私の個人的な体験を通して共有できればと思います。少々長くなりますが、私のジェットコースターのような体験にお付き合いいただけますと幸いです。

1. 甘い誘惑:年利 75%との出会い

すべての始まりは、とある DeFi プロトコルで、異常な高金利を提示する通貨を見つけたことでした。

それは、米ドルに連動することを目指す、とある新興のステーブルコインでした。2025 年 3 月頃の市場環境を思い返すと、信頼性の高いステーブルコインを DeFi で運用した場合の利回りは、だいたい年利 3 ~ 5%が相場。少しリスクを取ったとしても、せいぜい年利 10%前後がちらほら見られる程度でした。

そんな中、私の目に飛び込んできたのは 「年利 75%」 という、にわかには信じがたい数字だったのです。新興のマイナーなステーブルコインであることは認識していましたが、その数字はあまりにも魅力的でした。多少のリスクは覚悟の上、いや、正直に言えば、その異常なリターンへの期待がリスクへの懸念を上回ってしまったのかもしれません。私は、誘惑に抗えず、そのステーブルコインを購入し、プロトコルに預け入れる(Deposit する)決断をしました。

2. 違和感

Deposit の操作自体は、特に問題なく完了しました。プロトコルのダッシュボードには、私の預けた資産額と、輝かしい「年利 75%」の表示が確かに現れています。複利で増えていくであろう将来の利益を想像し、少しばかり高揚感を覚えていたのも事実です。

しかし、その一方で、心のどこかで小さな、しかし無視できない違和感が燻っていました。高すぎる金利そのものが、潜在的なリスクへの警鐘を鳴らしていたのかもしれません。「本当にこんなうまい話があるのだろうか?」という疑念が、頭の片隅から離れませんでした。

「…あれ? これ、本当に返してもらえるのかな?」

年利 75%という法外な金利で借りているプレイヤーがいる。彼らは一体何を考えているのだろう? もしかして、最初から返済するつもりなどなく、借りた資金を踏み倒す前提で行動しているのではないだろうか?

急いで状況を調査しなければならない。そう直感しました。

3. 追跡開始:Etherscan に刻まれた攻撃者の足跡

いてもたってもいられず、私はブロックチェーンエクスプローラーである Etherscan を開きました。何が起きているのか、自分の目で確かめる必要があったのです。

幸い、多くの DeFi プロトコルでは、資産を貸し出すと、その証明として「預り証トークン」が発行されます。この預り証トークンの動きを Etherscan で追跡すれば、誰がいつ、どれくらいの資産を借り入れたのか、その資金がどこへ移動したのかを把握できるはずです。

私は、問題のステーブルコインに対応する預り証トークンのコントラクトアドレスを特定し、最近の貸出トランザクション(Borrow イベントなど)をいくつかピックアップしました。そして、それらのトランザクションを実行した借り手のウォレットアドレスを一つずつ追跡していきました。

すると…信じられない光景が、ブロックチェーンの記録として、はっきりと目に飛び込んできたのです。

etherscan-steal
脆弱性を利用して攻撃者の資産が 3509USDC から 3523USDC に増加した例

ログは、彼らが組織的に、あるいは同一人物が複数のアドレスを使って、プロトコルから計画的に資金を抜き取っていることを物語っていました。「やられた!」という衝撃とともに、私の不安は最悪の確信へと変わりました。これは単なる高金利での貸し借りではなく、プロトコルの脆弱性を突いた意図的な資産窃取(Attack)に他なりません。

もうこの時点では私のライフは瀕死です。しかし、落ち込んでいる暇はありません。何とかしなければ。

4. 脆弱性の特定:価格オラクルの歪み

なぜこのような攻撃が可能になってしまったのか? Etherscan のログとプロトコルのドキュメントを照らし合わせながら、原因の特定を進めました。

問題の核心は、プロトコルが担保価値や借入額を計算する際に参照していた「価格オラクル」にありました。

通常、DeFi プロトコルは、Chainlink や Pyth Network といった信頼性の高い分散型オラクルサービスを利用して、暗号資産の市場価格をリアルタイムに近い形で取得します。これにより、担保価値を正確に評価し、過剰な借入を防いでいます。

しかし、今回問題となったステーブルコインは、市場に出て間もないマイナーなコインだったためか、この DeFi プロトコルでは、信頼できる外部オラクルを参照せず、「1 コイン = 1 USD」という固定価格を内部的に適用していたのです。

ところが、実際の市場では、このステーブルコインは若干のプレミアムがついており、1 コイン = 約 1.07 USD で取引されていました。このプロトコル内部の価格(1 ドル)と市場価格(1.07 ドル)の乖離こそが、攻撃者に悪用された脆弱性でした。

攻撃者は、この価格差を利用して、以下のようなステップで利益を上げていたと考えられます。

  1. 担保預け入れ: まず、攻撃者は市場で 1000 USDC(1 USDC = 1 USD とする)を用意し、これをプロトコルに担保として預け入れます。プロトコルはこれを 1000 ドル相当の価値として認識します。
  2. 問題のコイン借入: 次に、この担保(1000 ドル相当)に基づき、問題のステーブルコインを借り入れます。仮に LTV(Loan to Value ratio: 担保価値に対する借入額の比率)の上限が 90% だとすると、900 ドル相当まで借りられます。プロトコルは「1 コイン = 1 ドル」と認識しているため、攻撃者は 900 コインを借りることができます。
  3. 市場で売却: 攻撃者は、借り入れた 900 コインをすぐに市場(DEX など)で売却します。市場価格は 1 コイン = 1.07 USD なので、900 コイン × 1.07 USD/コイン = 963 USDC(963 ドル) を手に入れます。
  4. 追加の利益 (オプション): さらに巧妙なことに、このプロトコルの設計では、担保として預けている資産の一部(預り証トークン)を、清算されない範囲(例えば担保価値の 95%に達するまで)で別のアドレスに送金し、それを即座に引き出すことが可能でした。仮に 50 ドル相当の預り証トークン(ここでは aUSDC とします)を送金・換金できたとすると、追加で 50 USDC を得られます。
  5. 利益確定: 結果として、攻撃者は 963 USDC + 50 USDC = 1013 USDC を手にします。元手は 1000 USDC でしたので、この一連の操作で 13 USDC の利益を得ることに成功します。

攻撃者はこのプロセスを繰り返す、あるいは自動化することで、プロトコルから安全かつ確実に資金を抜き取り、その結果として、私のような一般の預金者が資金を引き出せない状況(流動性の枯渇)を引き起こしていたのです。

5. 報告への道:閉ざされた扉と一条の光

攻撃のメカニズムを特定した私は、一刻も早くこの脆弱性をプロジェクトチームに報告し、対処してもらわなければならないと考えました。自分の資産を取り戻すためにも、そしてこれ以上の被害拡大を防ぐためにも。

しかし、ここからがまた困難の始まりでした。プロジェクトの公式ウェブサイトや関連情報をくまなく探しましたが、脆弱性を報告するための正式な窓口がどこにも見当たらないのです。

  • Discord: 運営のアナウンスや雑談ばかりでバグを報告できる場所が見つかりませんでした。
  • Forum (掲示板): 存在はしていましたが、最後の投稿が半年前で、完全に過疎化している様子でした。
  • メールアドレスや問い合わせフォーム: そのようなものは見当たりませんでした。

途方に暮れながら、「smart contract bug report」といったキーワードで Google 検索を続けていたところ、immunefi というプラットフォームを発見しました。ここは、スマートコントラクトの脆弱性に対する報奨金プログラム(バグバウンティ)を専門に扱っている、業界ではよく知られた場所のようでした。

ここなら、私の発見した脆弱性を適切に報告できるかもしれない。しかも、もし報告が認められれば、報奨金までもらえる可能性がある…! 一縷の望みを胸に、私は早速アカウントを作成し、発見した脆弱性の内容と攻撃シナリオを、DeepL 翻訳などを駆使しながら必死に英文で記述し、レポートとして Submit しました。まずは問題を解決してもらいたい、その一心でした。

6. 最初の壁:非情な Reject

レポートを Submit してから数時間後、immunefi からメッセージが届きました。期待と不安が入り混じる中、内容を確認すると…無情にも 「Closed」 の文字が。

ショックと落胆で、目の前が暗くなるような感覚でした。なぜだ? 攻撃シナリオは明確なはずなのに。メッセージの詳細を読み進めると、却下の理由は、報告内容そのものの評価ではなく、 「PoC (Proof of Concept) が添付されていないため」 という形式的なものでした。

PoC とは、報告された脆弱性が実際に悪用可能であることを、具体的なコード(この場合はスマートコントラクト)によって再現し、証明するものです。バグバウンティの世界では、開発者が問題を迅速かつ正確に理解し、修正作業に取り掛かるために、PoC の提出が要求されることが一般的です。

しかし、その時の私にとっては、「早くプログラムを直して、私の資金を引き出せるようにしてほしいだけなのに、なぜ追加でプログラムまで書かなければならないんだ…」という焦りと絶望感しかありませんでした。PoC を提出しない限り、immunefi は私の報告を受け付けすらしてくれない。振り出しに戻ってしまったのです。

ここで諦めてしまうべきか…? 一瞬、そんな考えが頭をよぎりました。

しかし、その時、ふと記憶の片隅にあったことを思い出しました。そういえば、暗号資産ブームに沸いた 2017 年頃、スマートコントラクト言語である Solidity を少しだけかじって、Qiita に記事を投稿したことがあったのです。

https://qiita.com/kackytw/items/b4cec532eb10340e4879 (※実際の記事へのリンクです)

微かな経験ではありましたが、ゼロではありません。「あきらめたら、そこで試合終了ですよ…?」どこからか安西先生の声が聞こえた気がしました(もちろん気のせいです)。そうだ、まだやれることはある。めげずに PoC を作り上げて、この状況を何としてでも打ち破ろう。私は再び立ち上がる決意を固めました。

7. PoC 制作:よなよなデバッグ

PoC、すなわち攻撃を再現するスマートコントラクトを作成する作業に取り掛かりました。これは、脆弱性の存在を疑いの余地なく証明するために不可欠なステップです。

immunefi のドキュメントでは、スマートコントラクトのテストフレームワークである Foundry の一部である forge を使用した PoC テンプレート forge-poc-templates が推奨されていました。私は手元に Linux 環境を持っていなかったため、まずは Windows Subsystem for Linux 2 (WSL2) を使って Ubuntu 24.04 環境を構築し、そこへ forge をインストールするところから始めました。

次に、forge の強力な機能の一つであるメインネット・フォークを利用します。これにより、実際の Ethereum メインネットの状態を特定のブロック番号でコピーしたローカルテスト環境を立ち上げることができます。本番環境と全く同じ状態で、かつ安全に、脆弱性を再現するテストを実行できるため、PoC 開発には必須の機能です。メインネットへの接続には、以前利用したことのある Infura の RPC エンドポイントを利用しました。(最近 Infura と Metamask が Consensys 傘下で統合されていたのは、この時知ったちょっとした発見でした。)

環境が整い、いよいよ Solidity で攻撃コードの実装に着手しました。やるべきことは明確です。先に特定した攻撃ステップ(担保預け入れ、問題のコイン借入、市場でのスワップ、追加の担保引き出し)を、スマートコントラクトのコードとして正確に再現し、最終的に攻撃者が利益を得ることを forge のテストケース内でアサート(証明)すること。

しかし、この攻撃ロジックを Solidity コードに落とし込む作業は、久しぶりということもあり、予想以上に難航しました。特に、対象となる DeFi プロトコルの複数のコントラクト関数(レンディングコントラクト、価格オラクルコントラクト、預り証トークンコントラクトなど)を正しい順序で、適切なパラメータと共に呼び出し、状態を正確に遷移させていく必要がありました。どの関数をどのタイミングで叩くのか、どのイベントを確認すれば良いのか、ドキュメントや Etherscan での実際のトランザクションを何度も見比べながら、試行錯誤を繰り返しました。深夜まで続くデバッグ作業は、まさに暗闇の中を手探りで進むような感覚でした。

数時間にわたる格闘の末、ようやく攻撃シーケンス全体を再現するコードが完成しました。ローカルのフォーク環境でテストを実行し、期待通りに攻撃が成功することを確認できた時は、深い安堵感を覚えました。

ここで、私はもう一手間加えることにしました。コードの可読性を高め、immunefi のレビュアーやプロジェクト開発者が迅速に内容を理解できるようにするためです。そこで、Google AI Studio (現 Google AI Gemini) の力を借りることにしました。

完成した Solidity のテストコード全体を AI Studio に渡し、具体的な指示を出しました。「このスマートコントラクトの各関数や主要な処理ブロックについて、その動作内容を説明するコメントを英語で追記してください」と。すると、驚くほど自然で的確なコメントが、コードの適切な箇所に自動で挿入されたのです。自分一人でコメントを書いていたら、さらに多くの時間がかかっていたでしょう。さらに、テスト実行時に出力されるログに関しても、AI Studio は非常に意図をわかりやすく整形してくれました。

攻撃ロジックそのものは自力で実装しましたが、コードの仕上げ(コメント付与)や結果の可視化(ログ整形)といった作業において、AI は有能なアシスタントとして機能してくれました今どきの開発において、こうしたツールを適切に活用しない手はないと痛感しました。


8. 証明完了:攻撃を再現するコード

3日間の格闘の末、ついに forge 上で攻撃を完全に再現し、攻撃者が利益を得ることを証明する PoC が完成しました! テストを実行すると、コンソールには成功を示すログが出力されました。

Ran 1 test for test/AttackTest.sol:AttackTest
[PASS] testAttackUSDCandyUSDC() (gas: 1339130)
Logs:

>>> Initial conditions set
  --- USDC balance of [0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f]:     1000.000000 ---

  Attacker supplied 1000 USDC
  ============ Attacker Account (Before Borrow) =============
  Total Collateral (Base Currency): 99996894000
  Total Debt (Base Currency): 0
  Available Borrows (Base Currency): 89997204600
  Health Factor: 115792089237316195423570985008687907853269984665640564039457584007913129639935
  ===========================================================
  Victim supplied 1000 yUSD
  Supply phase complete
  Attacker borrowed 899 yUSD
  Attacker swapped yUSD for USDC amount: 961930000
  Swap complete
  Attacker transferred 50 zAUSDC to WithdrawContract
  Attacker zAUSDC balance after transfer: 949999999
  ============ Attacker Account (After Borrow/Transfer) =====
  Total Collateral (Base Currency): 94997049200
  Total Debt (Base Currency): 89900000000
  Available Borrows (Base Currency): 0
  Health Factor: 1003862032703003337
  ===========================================================
  WithdrawContract withdrew 50 USDC
  WithdrawContract sent 50 USDC back to AttackContract
  Attack sequence complete.
  Attacker final external USDC balance: 1011930000
  --- USDC balance of [0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f]:     1011.930000 ---

  ~~~ Profit for [0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f]
  -----------------------------------------------------------------------------------------
               Token address                    |       Symbol  |       Profit
  -----------------------------------------------------------------------------------------
  0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48    |       USDC    |       11.930000


Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 86.47s (68.44s CPU time)

Ran 1 test suite in 86.50s (86.47s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

このログは、攻撃者が初期資金 1000 USDC を使って一連の操作を行い、最終的に 1011.93 USDC を手に入れ、約 11.93 USDC の利益を出していることを明確に示しています。

PoC が完成した瞬間は、大きな達成感と安堵感に包まれました。これで、脆弱性の存在を疑いの余地なく証明できる。

9. 再提出、そしてエスカレーションへ

私は、完成した PoC のコードと実行結果を添付し、以前 Reject されたレポートを更新する形で、immunefi に再度 Submit しました。今度こそ受け付けてもらえるだろうか…わずかな不安を感じつつも、前回よりは確かな手応えを感じていました。

そして、その期待は裏切られませんでした。

しばらくして、immunefi から新たなメッセージが届きました。内容は、「あなたの報告はレビューを通過し、関連するプロジェクトチームへエスカレーションされました」というもの!

やった…! ようやく私の声が、問題を解決できる当事者へと届いたのです。脆弱性を発見してから、実に 3 日ほどの時間が経過していましたが、諦めずに PoC を作成した甲斐がありました。自分がやるべき役割は果たせた、という安堵感。同時に、プロジェクトチームが迅速に対応してくれるだろうか、という期待と少しの不安。なんだかドキドキして眠れないような、でも PoC 作成の夜なべでひどく疲れているような、そんな不思議な気持ちで、その夜は眠りにつきました。

10. 脆弱性修正と資産奪還

翌朝のことです。何気なく、例の DeFi プロトコルのダッシュボードを確認して、私は目を疑いました。

昨日まで「1 コイン = 1 USD」で固定されていた問題のステーブルコインの価格が、市場価格(約 1.07 USD)を反映した正しい価格に更新されていたのです!

プロジェクトチームが、私の報告を受けて迅速に対応してくれたのです!

価格オラクルの問題が修正されたことで、攻撃者はもはや価格差を利用した利益を得ることはできなくなりました。それどころか、借り入れ額が担保価値を上回る危険性が生じます。

興奮冷めやらぬ中、私はすぐに自分の資産の引き出しを試みました。すると…昨日まで「流動性不足」でびくともしなかった資金が、全額引き出すことに成功したのです!

安堵感で、全身の力が抜けるようでした。自分の資産が無事に戻ってきたこと、そして、脆弱性の報告が問題解決に繋がったことへの喜びが込み上げてきました。

その後、Etherscan で攻撃者が利用していたアドレスの動向を追ってみると、価格修正の影響で Health Factor (健全性スコア) が急激に悪化し、担保資産が清算(ロスカット)されているログが確認できました。彼らの不正な試みは、ここで終焉を迎えたのです。

etherscan-liquidation
ロスカットの様子

しかし、残念ながら、清算された担保だけでは、彼らがプロトコルから抜き取った(そして返済されなかった)負債の全額をカバーするには至らなかったようで、プロトコル全体としては、いくらかの債務不履行が発生してしまったようでした。私の資産は守られましたが、問題の完全解決には至らなかったという現実は、少し複雑な気持ちにさせられました。

11. オチ:音信不通

自分の資産を取り戻し、一段落…あとはバグバウンティから報奨金の連絡を待つだけでした。

しかし待てども暮せども運営元からの連絡は一切ありません。SLA で示された 14 日間を経過しても一言の応答もない状況です。そして私を苦しめたこのファンドはさらっと予告なく凍結されていました。このような脆弱性を放置していた運営らしい雑な幕引きでした。

ただ、正直なところ、報奨金は全く期待していませんでした。自分の資産を取り戻したい、問題を解決したい一心での行動でしたし、こうして技術ブログのネタにもできたのでヨシとします。(でもちょっとおいしいごはんが食べたかった...)

まとめ

今回のジェットコースターのような体験から、私が得た教訓は以下の通りです。

  • 高すぎるリターンには、相応のリスクが潜んでいることを肝に銘じるべし。 市場の相場から大きくかけ離れた金利には、必ず裏があります。安易にお金を預ける前に、なぜそのような高金利が実現できているのか、その仕組みとリスクを徹底的に調査することが重要です。年利 75%なんて高金利は詐欺を疑いましょう。
  • 「何かおかしい」と感じたら、直感 を信じてすぐに行動すべし。 違和感を無視せず、立ち止まって状況を確認し、必要であれば資金を引き揚げる勇気を持つことが、致命的な損失を避ける鍵となります。
  • 技術は身を助ける。ブロックチェーンとスマートコントラクトの知識は、投資家自身を守る武器にもなりうる。 Etherscan でトランザクションを追跡したり、簡単なコードを読んだりするだけでも、リスクを早期に発見できる可能性が高まります。そして、もし可能であれば、実際にコードを書くスキルがあれば、問題解決に直接貢献できて、おいしいお肉が食べられるくらいのお金が稼げるかもしれません。(もちろん、これは稀なケースですが)

このブログが、DeFi や暗号資産投資に関わる皆さんの、何かしらの参考になれば幸いです。

最後になりますが、くれぐれも投資は自己責任でお願いします。十分な情報収集とリスク管理を怠らないようにしてください。

Discussion